diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index b60ea7d3..898d79c1 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -6,12 +6,10 @@ import { normalizeUserOptions, validateOptions } from "./options-mapping"; import { debugIdUploadPlugin } from "./plugins/debug-id-upload"; import { releaseManagementPlugin } from "./plugins/release-management"; import { telemetryPlugin } from "./plugins/telemetry"; -import { getSentryCli } from "./sentry/cli"; import { createLogger } from "./sentry/logger"; -import { createSentryInstance, allowedToSendTelemetry } from "./sentry/telemetry"; +import { allowedToSendTelemetry, createSentryInstance } from "./sentry/telemetry"; import { Options } from "./types"; import { - determineReleaseName, generateGlobalInjectorCode, getDependencies, getPackageJson, @@ -58,6 +56,14 @@ export function sentryUnpluginFactory({ return createUnplugin((userOptions, unpluginMetaContext) => { const options = normalizeUserOptions(userOptions); + if (unpluginMetaContext.watchMode || options.disable) { + return [ + { + name: "sentry-noop-plugin", + }, + ]; + } + const shouldSendTelemetry = allowedToSendTelemetry(options); const { sentryHub, sentryClient } = createSentryInstance( options, @@ -95,15 +101,6 @@ export function sentryUnpluginFactory({ ); } - const cli = getSentryCli(options, logger); - - const releaseName = options.release ?? determineReleaseName(); - if (!releaseName) { - handleRecoverableError( - new Error("Unable to determine a release name. Please set the `release` option.") - ); - } - if (process.cwd().match(/\\node_modules\\|\/node_modules\//)) { logger.warn( "Running Sentry plugin from within a `node_modules` folder. Some features may not work." @@ -121,53 +118,87 @@ export function sentryUnpluginFactory({ }) ); - if (options.injectRelease && releaseName) { + if (!options.release.inject) { + logger.debug("Release injection disabled via `release.inject`. Will not inject."); + } else if (!options.release.name) { + logger.warn("No release name provided. Will not inject release."); + } else { const injectionCode = generateGlobalInjectorCode({ - release: releaseName, + release: options.release.name, injectBuildInformation: options._experiments.injectBuildInformation || false, }); - plugins.push(releaseInjectionPlugin(injectionCode)); } - if (options.sourcemaps?.assets) { - plugins.push(debugIdInjectionPlugin()); - } - - if (releaseName) { + if (!options.release.name) { + logger.warn("No release name provided. Will not manage release."); + } else if (!options.authToken) { + logger.warn("No auth token provided. Will not manage release."); + } else if (!options.org) { + logger.warn("No org provided. Will not manage release."); + } else if (!options.project) { + logger.warn("No project provided. Will not manage release."); + } else { plugins.push( releaseManagementPlugin({ logger, - cliInstance: cli, - releaseName: releaseName, - shouldCleanArtifacts: options.cleanArtifacts, - shouldUploadSourceMaps: options.uploadSourceMaps, - shouldFinalizeRelease: options.finalize, - include: options.include, - setCommitsOption: options.setCommits, - deployOptions: options.deploy, - dist: options.dist, + releaseName: options.release.name, + shouldCreateRelease: options.release.create, + shouldCleanArtifacts: options.release.cleanArtifacts, + shouldFinalizeRelease: options.release.finalize, + include: options.release.uploadLegacySourcemaps, + setCommitsOption: options.release.setCommits, + deployOptions: options.release.deploy, + dist: options.release.dist, handleRecoverableError: handleRecoverableError, sentryHub, sentryClient, + sentryCliOptions: { + authToken: options.authToken, + org: options.org, + project: options.project, + silent: options.silent, + url: options.url, + vcsRemote: options.release.vcsRemote, + headers: options.headers, + }, }) ); } - if (!unpluginMetaContext.watchMode && options.sourcemaps?.assets !== undefined) { - plugins.push( - debugIdUploadPlugin({ - assets: options.sourcemaps.assets, - ignore: options.sourcemaps.ignore, - dist: options.dist, - releaseName: releaseName, - logger: logger, - cliInstance: cli, - handleRecoverableError: handleRecoverableError, - sentryHub, - sentryClient, - }) - ); + if (options.sourcemaps) { + if (!options.authToken) { + logger.warn("No auth token provided. Will not upload source maps."); + } else if (!options.org) { + logger.warn("No org provided. Will not upload source maps."); + } else if (!options.project) { + logger.warn("No project provided. Will not upload source maps."); + } else if (!options.sourcemaps.assets) { + logger.warn("No assets defined. Will not upload source maps."); + } else { + plugins.push(debugIdInjectionPlugin()); + plugins.push( + debugIdUploadPlugin({ + assets: options.sourcemaps.assets, + ignore: options.sourcemaps.ignore, + dist: options.release.dist, + releaseName: options.release.name, + logger: logger, + handleRecoverableError: handleRecoverableError, + sentryHub, + sentryClient, + sentryCliOptions: { + authToken: options.authToken, + org: options.org, + project: options.project, + silent: options.silent, + url: options.url, + vcsRemote: options.release.vcsRemote, + headers: options.headers, + }, + }) + ); + } } return plugins; diff --git a/packages/bundler-plugin-core/src/options-mapping.ts b/packages/bundler-plugin-core/src/options-mapping.ts index 562c3c99..ccaaaf95 100644 --- a/packages/bundler-plugin-core/src/options-mapping.ts +++ b/packages/bundler-plugin-core/src/options-mapping.ts @@ -1,196 +1,39 @@ import { Logger } from "./sentry/logger"; -import { IncludeEntry as UserIncludeEntry, Options as UserOptions } from "./types"; -import { arrayify } from "./utils"; +import { Options as UserOptions } from "./types"; +import { determineReleaseName } from "./utils"; -type RequiredInternalOptions = Required< - Pick< - UserOptions, - | "finalize" - | "dryRun" - | "debug" - | "silent" - | "cleanArtifacts" - | "telemetry" - | "_experiments" - | "injectRelease" - | "uploadSourceMaps" - > ->; - -type OptionalInternalOptions = Partial< - Pick< - UserOptions, - | "org" - | "project" - | "authToken" - | "url" - | "vcsRemote" - | "dist" - | "errorHandler" - | "setCommits" - | "deploy" - | "configFile" - | "headers" - | "sourcemaps" - | "release" - > ->; - -type NormalizedInternalOptions = { - releaseInjectionTargets: (string | RegExp)[] | ((filePath: string) => boolean) | undefined; - include: InternalIncludeEntry[]; -}; - -export type NormalizedOptions = RequiredInternalOptions & - OptionalInternalOptions & - NormalizedInternalOptions; - -type RequiredInternalIncludeEntry = Required< - Pick< - UserIncludeEntry, - "paths" | "ext" | "stripCommonPrefix" | "sourceMapReference" | "rewrite" | "validate" - > ->; - -type OptionalInternalIncludeEntry = Partial< - Pick ->; - -export type InternalIncludeEntry = RequiredInternalIncludeEntry & - OptionalInternalIncludeEntry & { - ignore: string[]; - }; +export type NormalizedOptions = ReturnType; export const SENTRY_SAAS_URL = "https://sentry.io"; export function normalizeUserOptions(userOptions: UserOptions) { const options = { - // include is the only strictly required option - // (normalizeInclude needs all userOptions to access top-level include options) - include: normalizeInclude(userOptions), - - // These options must be set b/c we need them for release injection. - // They can also be set as environment variables. Technically, they - // could be set in the config file but this would be too late for - // release injection because we only pass the config file path - // to the CLI org: userOptions.org ?? process.env["SENTRY_ORG"], project: userOptions.project ?? process.env["SENTRY_PROJECT"], - // Falling back to the empty string here b/c at a later point, we use - // Sentry CLI to determine a release if none was specified via options - // or env vars. In case we don't find one, we'll bail at that point. - release: userOptions.release ?? process.env["SENTRY_RELEASE"], - // We technically don't need the URL for anything release-specific - // but we want to make sure that we're only sending Sentry data - // of SaaS customers. Hence we want to read it anyway. + authToken: userOptions.authToken ?? process.env["SENTRY_AUTH_TOKEN"], url: userOptions.url ?? process.env["SENTRY_URL"] ?? SENTRY_SAAS_URL, - - // Options with default values - finalize: userOptions.finalize ?? true, - cleanArtifacts: userOptions.cleanArtifacts ?? false, - dryRun: userOptions.dryRun ?? false, + headers: userOptions.headers, debug: userOptions.debug ?? false, silent: userOptions.silent ?? false, + errorHandler: userOptions.errorHandler, telemetry: userOptions.telemetry ?? true, - injectRelease: userOptions.injectRelease ?? true, - uploadSourceMaps: userOptions.uploadSourceMaps ?? true, + disable: userOptions.disable ?? false, sourcemaps: userOptions.sourcemaps, + release: { + name: determineReleaseName(), + inject: true, + create: true, + finalize: true, + vcsRemote: process.env["SENTRY_VSC_REMOTE"] ?? "origin", + cleanArtifacts: false, + ...userOptions.release, + }, _experiments: userOptions._experiments ?? {}, - - // These options and can also be set via env variables or the config file. - // If they're set in the options, we simply pass them to the CLI constructor. - // Sentry CLI will internally query env variables and read its config file if - // the passed options are undefined. - authToken: userOptions.authToken, // env var: `SENTRY_AUTH_TOKEN` - - headers: userOptions.headers, - - vcsRemote: userOptions.vcsRemote, // env var: `SENTRY_VSC_REMOTE` - - // Optional options - setCommits: userOptions.setCommits, - deploy: userOptions.deploy, - releaseInjectionTargets: normalizeReleaseInjectionTargets(userOptions.releaseInjectionTargets), - dist: userOptions.dist, - errorHandler: userOptions.errorHandler, - configFile: userOptions.configFile, }; return options; } -/** - * Converts the user-facing `releaseInjectionTargets` option to the internal - * `releaseInjectionTargets` option - */ -function normalizeReleaseInjectionTargets( - userReleaseInjectionTargets: UserOptions["releaseInjectionTargets"] -): (string | RegExp)[] | ((filePath: string) => boolean) | undefined { - if (userReleaseInjectionTargets === undefined) { - return undefined; - } else if (typeof userReleaseInjectionTargets === "function") { - return userReleaseInjectionTargets; - } else { - return arrayify(userReleaseInjectionTargets); - } -} - -/** - * Converts the user-facing `include` option to the internal `include` option, - * resulting in an array of `InternalIncludeEntry` objects. This later on lets us - * work with only one type of include data structure instead of multiple. - * - * During the process, we hoist top-level include options (e.g. urlPrefix) into each - * object if they were not alrady specified in an `IncludeEntry`, making every object - * fully self-contained. This is also the reason why we pass the entire options - * object and not just `include`. - * - * @param userOptions the entire user-facing `options` object - * - * @return an array of `InternalIncludeEntry` objects. - */ -function normalizeInclude(userOptions: UserOptions): InternalIncludeEntry[] { - if (!userOptions.include) { - return []; - } - - return arrayify(userOptions.include) - .map((includeItem) => - typeof includeItem === "string" ? { paths: [includeItem] } : includeItem - ) - .map((userIncludeEntry) => normalizeIncludeEntry(userOptions, userIncludeEntry)); -} - -/** - * Besides array-ifying the `ignore` option, this function hoists top level options into the items of the `include` - * option. This is to simplify the handling of of the `include` items later on. - */ -function normalizeIncludeEntry( - userOptions: UserOptions, - includeEntry: UserIncludeEntry -): InternalIncludeEntry { - const ignoreOption = includeEntry.ignore ?? userOptions.ignore ?? ["node_modules"]; - const ignore = Array.isArray(ignoreOption) ? ignoreOption : [ignoreOption]; - - // We're prefixing all entries in the `ext` option with a `.` (if it isn't already) to align with Node.js' `path.extname()` - const ext = includeEntry.ext ?? userOptions.ext ?? ["js", "map", "jsbundle", "bundle"]; - const dotPrefixedExt = ext.map((extension) => `.${extension.replace(/^\./, "")}`); - - return { - paths: includeEntry.paths, - ignore, - ignoreFile: includeEntry.ignoreFile ?? userOptions.ignoreFile, - ext: dotPrefixedExt, - urlPrefix: includeEntry.urlPrefix ?? userOptions.urlPrefix, - urlSuffix: includeEntry.urlSuffix ?? userOptions.urlSuffix, - stripPrefix: includeEntry.stripPrefix ?? userOptions.stripPrefix, - stripCommonPrefix: includeEntry.stripCommonPrefix ?? userOptions.stripCommonPrefix ?? false, - sourceMapReference: includeEntry.sourceMapReference ?? userOptions.sourceMapReference ?? true, - rewrite: includeEntry.rewrite ?? userOptions.rewrite ?? true, - validate: includeEntry.validate ?? userOptions.validate ?? false, - }; -} - /** * Validates a few combinations of options that are not checked by Sentry CLI. * @@ -204,7 +47,7 @@ function normalizeIncludeEntry( * @returns `true` if the options are valid, `false` otherwise */ export function validateOptions(options: NormalizedOptions, logger: Logger): boolean { - const setCommits = options.setCommits; + const setCommits = options.release?.setCommits; if (setCommits) { if (!setCommits.auto && !(setCommits.repo && setCommits.commit)) { logger.error( @@ -222,7 +65,7 @@ export function validateOptions(options: NormalizedOptions, logger: Logger): boo } } - if (options.deploy && !options.deploy.env) { + if (options.release?.deploy && !options.release?.deploy.env) { logger.error( "The `deploy` option was specified but is missing the required `env` property.", "Please set the `env` property." diff --git a/packages/bundler-plugin-core/src/plugins/debug-id-upload.ts b/packages/bundler-plugin-core/src/plugins/debug-id-upload.ts index e7524404..62e4a947 100644 --- a/packages/bundler-plugin-core/src/plugins/debug-id-upload.ts +++ b/packages/bundler-plugin-core/src/plugins/debug-id-upload.ts @@ -6,12 +6,11 @@ import * as util from "util"; import { UnpluginOptions } from "unplugin"; import { Logger } from "../sentry/logger"; import { promisify } from "util"; -import { SentryCLILike } from "../sentry/cli"; import { Hub, NodeClient } from "@sentry/node"; +import SentryCli from "@sentry/cli"; interface DebugIdUploadPluginOptions { logger: Logger; - cliInstance: SentryCLILike; assets: string | string[]; ignore?: string | string[]; releaseName?: string; @@ -19,23 +18,35 @@ interface DebugIdUploadPluginOptions { handleRecoverableError: (error: unknown) => void; sentryHub: Hub; sentryClient: NodeClient; + sentryCliOptions: { + url: string; + authToken: string; + org: string; + project: string; + vcsRemote: string; + silent: boolean; + headers?: Record; + }; } export function debugIdUploadPlugin({ assets, ignore, logger, - cliInstance, releaseName, dist, handleRecoverableError, sentryHub, sentryClient, + sentryCliOptions, }: DebugIdUploadPluginOptions): UnpluginOptions { return { name: "sentry-debug-id-upload-plugin", async writeBundle() { let folderToCleanUp: string | undefined; + + const cliInstance = new SentryCli(null, sentryCliOptions); + try { const tmpUploadFolder = await fs.promises.mkdtemp( path.join(os.tmpdir(), "sentry-bundler-plugin-upload-") diff --git a/packages/bundler-plugin-core/src/plugins/release-management.ts b/packages/bundler-plugin-core/src/plugins/release-management.ts index e6525467..e9cc402b 100644 --- a/packages/bundler-plugin-core/src/plugins/release-management.ts +++ b/packages/bundler-plugin-core/src/plugins/release-management.ts @@ -1,45 +1,57 @@ -import { SentryCliCommitsOptions, SentryCliNewDeployOptions } from "@sentry/cli"; +import SentryCli, { SentryCliCommitsOptions, SentryCliNewDeployOptions } from "@sentry/cli"; import { Hub, NodeClient } from "@sentry/node"; import { UnpluginOptions } from "unplugin"; -import { InternalIncludeEntry } from "../options-mapping"; -import { SentryCLILike } from "../sentry/cli"; import { Logger } from "../sentry/logger"; +import { IncludeEntry } from "../types"; +import { arrayify } from "../utils"; -interface DebugIdUploadPluginOptions { +interface ReleaseManagementPluginOptions { logger: Logger; - cliInstance: SentryCLILike; releaseName: string; + shouldCreateRelease: boolean; shouldCleanArtifacts: boolean; - shouldUploadSourceMaps: boolean; shouldFinalizeRelease: boolean; - include: InternalIncludeEntry[]; + include?: string | IncludeEntry | Array; setCommitsOption?: SentryCliCommitsOptions; deployOptions?: SentryCliNewDeployOptions; dist?: string; handleRecoverableError: (error: unknown) => void; sentryHub: Hub; sentryClient: NodeClient; + sentryCliOptions: { + url: string; + authToken: string; + org: string; + project: string; + vcsRemote: string; + silent: boolean; + headers?: Record; + }; } export function releaseManagementPlugin({ - cliInstance, releaseName, include, dist, setCommitsOption, + shouldCreateRelease, shouldCleanArtifacts, - shouldUploadSourceMaps, shouldFinalizeRelease, deployOptions, handleRecoverableError, sentryHub, sentryClient, -}: DebugIdUploadPluginOptions): UnpluginOptions { + sentryCliOptions, +}: ReleaseManagementPluginOptions): UnpluginOptions { return { name: "sentry-debug-id-upload-plugin", async writeBundle() { try { - await cliInstance.releases.new(releaseName); + const cliInstance = new SentryCli(null, sentryCliOptions); + + if (shouldCreateRelease) { + await cliInstance.releases.new(releaseName); + } if (shouldCleanArtifacts) { await cliInstance.releases.execute( @@ -48,8 +60,24 @@ export function releaseManagementPlugin({ ); } - if (shouldUploadSourceMaps) { - await cliInstance.releases.uploadSourceMaps(releaseName, { include, dist }); + if (include) { + const normalizedInclude = arrayify(include) + .map((includeItem) => + typeof includeItem === "string" ? { paths: [includeItem] } : includeItem + ) + .map((includeEntry) => ({ + ...includeEntry, + validate: includeEntry.validate ?? false, + ext: includeEntry.ext + ? includeEntry.ext.map((extension) => `.${extension.replace(/^\./, "")}`) + : [".js", ".map", ".jsbundle", ".bundle"], + ignore: includeEntry.ignore ? arrayify(includeEntry.ignore) : undefined, + })); + + await cliInstance.releases.uploadSourceMaps(releaseName, { + include: normalizedInclude, + dist, + }); } if (setCommitsOption) { diff --git a/packages/bundler-plugin-core/src/sentry/cli.ts b/packages/bundler-plugin-core/src/sentry/cli.ts deleted file mode 100644 index 2e67ca51..00000000 --- a/packages/bundler-plugin-core/src/sentry/cli.ts +++ /dev/null @@ -1,75 +0,0 @@ -import SentryCli, { SentryCliReleases } from "@sentry/cli"; -import { NormalizedOptions } from "../options-mapping"; -import { Logger } from "./logger"; - -type SentryDryRunCLI = { - releases: Omit; - execute: (args: string[], live: boolean) => Promise; -}; -export type SentryCLILike = SentryCli | SentryDryRunCLI; - -/** - * Creates a new Sentry CLI instance. - * - * In case, users selected the `dryRun` options, this returns a stub - * that makes no-ops out of most CLI operations - */ -export function getSentryCli(internalOptions: NormalizedOptions, logger: Logger): SentryCLILike { - const { silent, org, project, authToken, url, vcsRemote, headers } = internalOptions; - const cli = new SentryCli(internalOptions.configFile, { - url, - authToken, - org, - project, - vcsRemote, - silent, - headers, - }); - - if (internalOptions.dryRun) { - logger.info("In DRY RUN Mode"); - return getDryRunCLI(cli, logger); - } - - return cli; -} - -function getDryRunCLI(cli: SentryCli, logger: Logger): SentryDryRunCLI { - return { - releases: { - proposeVersion: () => - cli.releases.proposeVersion().then((version) => { - logger.info("Proposed version:\n", version); - return version; - }), - new: (release: string) => { - logger.info("Creating new release:\n", release); - return Promise.resolve(release); - }, - uploadSourceMaps: (release: string, config: unknown) => { - logger.info("Calling upload-sourcemaps with:\n", config); - return Promise.resolve(release); - }, - finalize: (release: string) => { - logger.info("Finalizing release:\n", release); - return Promise.resolve(release); - }, - setCommits: (release: string, config: unknown) => { - logger.info("Calling set-commits with:\n", config); - return Promise.resolve(release); - }, - newDeploy: (release: string, config: unknown) => { - logger.info("Calling deploy with:\n", config); - return Promise.resolve(release); - }, - execute: (args: string[], live: boolean) => { - logger.info("Executing", args, "live:", live); - return Promise.resolve(""); - }, - }, - execute: (args: string[], live: boolean) => { - logger.info("Executing", args, "live:", live); - return Promise.resolve("Executed"); - }, - }; -} diff --git a/packages/bundler-plugin-core/src/sentry/telemetry.ts b/packages/bundler-plugin-core/src/sentry/telemetry.ts index df0e96fa..a926a4bf 100644 --- a/packages/bundler-plugin-core/src/sentry/telemetry.ts +++ b/packages/bundler-plugin-core/src/sentry/telemetry.ts @@ -60,45 +60,39 @@ export function createSentryInstance( } export function setTelemetryDataOnHub(options: NormalizedOptions, hub: Hub, bundler: string) { - const { - org, - project, - cleanArtifacts, - finalize, - setCommits, - dryRun, - errorHandler, - deploy, - include, - sourcemaps, - } = options; - - hub.setTag("include", include.length > 1 ? "multiple-entries" : "single-entry"); + const { org, project, release, errorHandler, sourcemaps } = options; + + if (release.uploadLegacySourcemaps) { + hub.setTag( + "uploadLegacySourcemapsEntries", + Array.isArray(release.uploadLegacySourcemaps) ? release.uploadLegacySourcemaps.length : 1 + ); + } // Optional release pipeline steps - if (cleanArtifacts) { + if (release.cleanArtifacts) { hub.setTag("clean-artifacts", true); } - if (setCommits) { - hub.setTag("set-commits", setCommits.auto === true ? "auto" : "manual"); + if (release.setCommits) { + hub.setTag("set-commits", release.setCommits.auto === true ? "auto" : "manual"); } - if (finalize) { + if (release.finalize) { hub.setTag("finalize-release", true); } - if (deploy) { + if (release.deploy) { hub.setTag("add-deploy", true); } // Miscelaneous options - if (dryRun) { - hub.setTag("dry-run", true); - } if (errorHandler) { hub.setTag("error-handler", "custom"); } if (sourcemaps?.assets) { hub.setTag("debug-id-upload", true); } + if (sourcemaps?.deleteAfterUpload) { + hub.setTag("delete-after-upload", true); + } hub.setTag("node", process.version); @@ -112,27 +106,23 @@ export function setTelemetryDataOnHub(options: NormalizedOptions, hub: Hub, bund } export async function allowedToSendTelemetry(options: NormalizedOptions): Promise { - const { silent, org, project, authToken, url, vcsRemote, headers, telemetry, dryRun } = options; + const { silent, org, project, authToken, url, headers, telemetry, release } = options; // `options.telemetry` defaults to true if (telemetry === false) { return false; } - if (dryRun) { - return false; - } - if (url === SENTRY_SAAS_URL) { return true; } - const cli = new SentryCli(options.configFile, { + const cli = new SentryCli(null, { url, authToken, org, project, - vcsRemote, + vcsRemote: release.vcsRemote, silent, headers, }); diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 02ca5e86..9230541e 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -1,257 +1,6 @@ -import { Hub, Span } from "@sentry/node"; -import { SentryCLILike } from "./sentry/cli"; -import { createLogger } from "./sentry/logger"; - -/** - * The main options object holding all plugin options available to users - */ -export type Options = Omit & { - /* --- authentication/identification: */ - - /** - * The slug of the Sentry organization associated with the app. - * - * This value can also be specified via the `SENTRY_ORG` environment variable. - */ - org?: string; - - /** - * The slug of the Sentry project associated with the app. - * - * This value can also be specified via the `SENTRY_PROJECT` environment variable. - */ - project?: string; - - /** - * The authentication token to use for all communication with Sentry. - * Can be obtained from https://sentry.io/settings/account/api/auth-tokens/. - * Required scopes: project:releases (and org:read if setCommits option is used). - * - * This value can also be specified via the `SENTRY_AUTH_TOKEN` environment variable. - */ - authToken?: string; - - /** - * The base URL of your Sentry instance. Use this if you are using a self-hosted - * or Sentry instance other than sentry.io. - * - * This value can also be set via the `SENTRY_URL` environment variable. - * - * Defaults to https://sentry.io/, which is the correct value for SaaS customers. - */ - url?: string; - - /* --- release properties: */ - - /** - * Unique identifier for the release. - * - * This value can also be specified via the `SENTRY_RELEASE` environment variable. - * - * Defaults to the output of the sentry-cli releases propose-version command, - * which automatically detects values for Cordova, Heroku, AWS CodeBuild, CircleCI, - * Xcode, and Gradle, and otherwise uses the git `HEAD`'s commit SHA. (the latter - * requires access to git CLI and for the root directory to be a valid repository). - */ - release?: string; - - /** - * Unique identifier for the distribution, used to further segment your release. - * Usually your build number. - */ - dist?: string; - - /** - * Filter for modules that the release should be injected in. - * - * This option takes a string, a regular expression, or an array containing strings, - * regular expressions, or both. It's also possible to provide a filter function - * that takes the absolute path of a processed module. It should return `true` - * if the release should be injected into the module and `false` otherwise. String - * values of this option require a full match with the absolute path of the module. - * - * By default, the release will be injected into all entrypoints. If release - * injection should be disabled, provide an empty array here. - * - * @deprecated This option will be removed in the next major version. - */ - releaseInjectionTargets?: (string | RegExp)[] | RegExp | string | ((filePath: string) => boolean); - - /** - * Determines if the Sentry release record should be automatically finalized - * (meaning a date_released timestamp is added) after artifact upload. - * - * Defaults to `true`. - */ - finalize?: boolean; - - /* --- source maps properties: */ - - /** - * One or more paths that Sentry CLI should scan recursively for sources. - * It will upload all .map files and match associated .js files. Other file - * types can be uploaded by using the `ext` option. - * Each path can be given as a string or an object with path-specific options - * - * @deprecated Use the `sourcemaps` option instead. - */ - include?: string | IncludeEntry | Array; - - /* --- other properties: */ - - /** - * Version control system remote name. - * - * This value can also be specified via the `SENTRY_VSC_REMOTE` environment variable. - * - * Defaults to 'origin'. - */ - vcsRemote?: string; - - /** - * Headers added to every outgoing network request. - */ - headers?: Record; - - /** - * Attempts a dry run (useful for dev environments), making release creation - * a no-op. - * - * Defaults to `false`, but may be automatically set to `true` in development environments - * by some framework integrations (Next.JS, possibly others). - */ - dryRun?: boolean; - - /** - * Print useful debug information. - * - * Defaults to `false`. - */ - debug?: boolean; - - /** - * Suppresses all logs. - * - * Defaults to `false`. - */ - silent?: boolean; - - /** - * Remove all the artifacts in the release before the upload. - * - * Defaults to `false`. - */ - cleanArtifacts?: boolean; - - /** - * When an error occurs during rlease 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; - - /** - * Associates the release with its commits in Sentry. - */ - setCommits?: SetCommitsOptions; - - /** - * Adds deployment information to the release in Sentry. - */ - deploy?: DeployOptions; - - /** - * If set to true, internal plugin errors and performance data will be sent to Sentry. - * - * At Sentry 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; - - /** - * Path to Sentry CLI config properties, as described in - * https://docs.sentry.io/product/cli/configuration/#configuration-file. - * - * By default, the config file is looked for upwards from the current path, and - * defaults from ~/.sentryclirc are always loaded - */ - configFile?: string; - - /** - * Whether the plugin should inject release information into the build. - * - * Defaults to `true`. - */ - injectRelease?: boolean; - - /** - * Whether the plugin should upload source maps to Sentry. - * - * Defaults to `true`. - */ - uploadSourceMaps?: boolean; - - /** - * Options for source maps uploading. - */ - sourcemaps?: { - /** - * A glob or an array of globs that specify the build artifacts that should be uploaded to Sentry. - * Leave this option undefined if you do not want to upload source maps to Sentry. - * - * The globbing patterns follow the implementation of the `glob` package. (https://www.npmjs.com/package/glob) - * - * Use the `debug` option to print information about which files end up being uploaded. - */ - assets: string | string[]; - - /** - * A glob or an array of globs that specify which build artifacts should not be uploaded to Sentry. - * - * Default: `[]` - * - * The globbing patterns follow the implementation of the `glob` package. (https://www.npmjs.com/package/glob) - * - * Use the `debug` option to print information about which files end up being uploaded. - */ - ignore?: string | string[]; - }; - - /** - * Options that are considered experimental and subject to change. - * - * @experimental API may change in any release - */ - _experiments?: { - /** - * If set to true, the plugin will inject an additional `SENTRY_BUILD_INFO` variable. - * This contains information about the build, e.g. dependencies, node version and other useful data. - * - * Defaults to `false`. - */ - injectBuildInformation?: boolean; - }; -}; - -export interface NewOptions { +export interface Options { /** * The slug of the Sentry organization associated with the app. - * - * This value can also be specified via the `SENTRY_ORG` environment variable. */ org?: string; @@ -449,27 +198,24 @@ export interface NewOptions { */ deploy?: DeployOptions; + /** + * Remove all previously uploaded artifacts for this release on Sentry before the upload. + * + * Defaults to `false`. + */ + cleanArtifacts?: boolean; + /** * Legacy method of uploading source maps. (not recommended unless necessary) * + * One or more paths that should be scanned recursively for sources. + * + * Each path can be given as a string or an object with more specific options. + * * The modern version of doing source maps upload is more robust and way easier to get working but has to inject a very small snippet of JavaScript into your output bundles. * In situations where this leads to problems (e.g subresource integrity) you can use this option as a fallback. */ - legacySourcemaps?: { - /** - * One or more paths that should be scanned recursively for sources. - * - * Each path can be given as a string or an object with more specific options. - */ - include?: string | IncludeEntry | Array; - - /** - * Remove all the artifacts in the release before the upload. - * - * Defaults to `false`. - */ - cleanArtifacts?: boolean; - }; + uploadLegacySourcemaps?: string | IncludeEntry | Array; }; /** @@ -664,14 +410,3 @@ type DeployOptions = { */ url?: string; }; - -/** - * Holds data for internal purposes - * (e.g. telemetry and logging) - */ -export type BuildContext = { - hub: Hub; - parentSpan?: Span; - logger: ReturnType; - cli: SentryCLILike; -}; diff --git a/packages/bundler-plugin-core/test/option-mappings.test.ts b/packages/bundler-plugin-core/test/option-mappings.test.ts index 901cfae4..2470e670 100644 --- a/packages/bundler-plugin-core/test/option-mappings.test.ts +++ b/packages/bundler-plugin-core/test/option-mappings.test.ts @@ -7,34 +7,26 @@ describe("normalizeUserOptions()", () => { org: "my-org", project: "my-project", authToken: "my-auth-token", - release: "my-release", // we have to define this even though it is an optional value because of auto discovery - include: "./out", + release: { name: "my-release", uploadLegacySourcemaps: "./out" }, // we have to define this even though it is an optional value because of auto discovery }; expect(normalizeUserOptions(userOptions)).toEqual({ authToken: "my-auth-token", - cleanArtifacts: false, - debug: false, - dryRun: false, - finalize: true, - include: [ - { - ext: [".js", ".map", ".jsbundle", ".bundle"], - ignore: ["node_modules"], - paths: ["./out"], - rewrite: true, - sourceMapReference: true, - stripCommonPrefix: false, - validate: false, - }, - ], org: "my-org", project: "my-project", - release: "my-release", + debug: false, + disable: false, + release: { + name: "my-release", + finalize: true, + inject: true, + cleanArtifacts: false, + create: true, + vcsRemote: "origin", + uploadLegacySourcemaps: "./out", + }, silent: false, telemetry: true, - injectRelease: true, - uploadSourceMaps: true, _experiments: {}, url: "https://sentry.io", }); @@ -45,41 +37,43 @@ describe("normalizeUserOptions()", () => { org: "my-org", project: "my-project", authToken: "my-auth-token", - release: "my-release", // we have to define this even though it is an optional value because of auto discovery - - // include options - include: [{ paths: ["./output", "./files"], ignore: ["./files"] }], - rewrite: true, - sourceMapReference: false, - stripCommonPrefix: true, - // It is intentional that only foo has a `.`. We're expecting all extensions to be prefixed with a dot. - ext: ["js", "map", ".foo"], + release: { + name: "my-release", // we have to define this even though it is an optional value because of auto discovery + uploadLegacySourcemaps: { + paths: ["./output", "./files"], + ignore: ["./files"], + rewrite: true, + sourceMapReference: false, + stripCommonPrefix: true, + ext: ["js", "map", ".foo"], + }, + }, }; expect(normalizeUserOptions(userOptions)).toEqual({ authToken: "my-auth-token", - cleanArtifacts: false, + org: "my-org", + project: "my-project", debug: false, - dryRun: false, - finalize: true, - include: [ - { - ext: [".js", ".map", ".foo"], + disable: false, + release: { + name: "my-release", + vcsRemote: "origin", + finalize: true, + create: true, + inject: true, + cleanArtifacts: false, + uploadLegacySourcemaps: { + ext: ["js", "map", ".foo"], ignore: ["./files"], paths: ["./output", "./files"], rewrite: true, sourceMapReference: false, stripCommonPrefix: true, - validate: false, }, - ], - org: "my-org", - project: "my-project", - release: "my-release", + }, silent: false, telemetry: true, - injectRelease: true, - uploadSourceMaps: true, _experiments: {}, url: "https://sentry.io", }); @@ -118,7 +112,7 @@ describe("validateOptions", () => { }); it("should return `false` if `setCommits` is set but neither auto nor manual options are set", () => { - const options = { setCommits: {} } as Partial; + const options = { release: { setCommits: {} } } as Partial; expect(validateOptions(options as unknown as NormalizedOptions, mockedLogger)).toBe(false); expect(mockedLogger.error).toHaveBeenCalledWith( @@ -128,7 +122,7 @@ describe("validateOptions", () => { }); it("should return `true` but warn if `setCommits` is set and both auto nor manual options are set", () => { - const options = { setCommits: { auto: true, repo: "myRepo", commit: "myCommit" } }; + const options = { release: { setCommits: { auto: true, repo: "myRepo", commit: "myCommit" } } }; expect(validateOptions(options as unknown as NormalizedOptions, mockedLogger)).toBe(true); expect(mockedLogger.error).not.toHaveBeenCalled(); @@ -140,7 +134,7 @@ describe("validateOptions", () => { }); it("should return `false` if `deploy`is set but `env` is not provided", () => { - const options = { deploy: {} } as Partial; + const options = { release: { deploy: {} } } as Partial; expect(validateOptions(options as unknown as NormalizedOptions, mockedLogger)).toBe(false); expect(mockedLogger.error).toHaveBeenCalledWith( diff --git a/packages/bundler-plugin-core/test/sentry/cli.test.ts b/packages/bundler-plugin-core/test/sentry/cli.test.ts deleted file mode 100644 index 3e1e3dd1..00000000 --- a/packages/bundler-plugin-core/test/sentry/cli.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import SentryCli from "@sentry/cli"; -import { getSentryCli } from "../../src/sentry/cli"; - -describe("getSentryCLI", () => { - it("returns a valid CLI instance if dryRun is not specified", () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any - const cli = getSentryCli({} as any, {} as any); - expect(cli).toBeInstanceOf(SentryCli); - }); - - it("returns a dry run CLI stub if `dryRun` is set to true", () => { - const logger = { info: jest.fn() }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any - const cli = getSentryCli({ dryRun: true } as any, logger as any); - expect(logger.info).toHaveBeenCalledWith(expect.stringMatching("DRY RUN")); - expect(cli).not.toBeInstanceOf(SentryCli); - }); -}); diff --git a/packages/bundler-plugin-core/test/sentry/telemetry.test.ts b/packages/bundler-plugin-core/test/sentry/telemetry.test.ts index 83e3d78c..c6d2b826 100644 --- a/packages/bundler-plugin-core/test/sentry/telemetry.test.ts +++ b/packages/bundler-plugin-core/test/sentry/telemetry.test.ts @@ -20,14 +20,14 @@ describe("shouldSendTelemetry", () => { mockCliExecute.mockImplementation( () => "Sentry Server: https://selfhostedSentry.io \nsomeotherstuff\netc" ); - expect(await allowedToSendTelemetry({} as NormalizedOptions)).toBe(false); + expect(await allowedToSendTelemetry({ release: {} } as NormalizedOptions)).toBe(false); }); it("should return true if CLI returns sentry.io as a URL", async () => { mockCliExecute.mockImplementation( () => "Sentry Server: https://sentry.io \nsomeotherstuff\netc" ); - expect(await allowedToSendTelemetry({} as NormalizedOptions)).toBe(true); + expect(await allowedToSendTelemetry({ release: {} } as NormalizedOptions)).toBe(true); }); }); @@ -39,7 +39,7 @@ describe("addPluginOptionTagsToHub", () => { }; const defaultOptions = { - include: [], + release: { uploadLegacySourcemaps: [] }, }; beforeEach(() => { @@ -52,21 +52,21 @@ describe("addPluginOptionTagsToHub", () => { mockedHub as unknown as Hub, "rollup" ); - expect(mockedHub.setTag).toHaveBeenCalledWith("include", "single-entry"); + expect(mockedHub.setTag).toHaveBeenCalledWith("uploadLegacySourcemapsEntries", 0); }); it("should set include tag according to number of entries (multiple entries)", () => { setTelemetryDataOnHub( - normalizeUserOptions({ include: ["", "", ""] }), + normalizeUserOptions({ release: { uploadLegacySourcemaps: ["", "", ""] } }), mockedHub as unknown as Hub, "rollup" ); - expect(mockedHub.setTag).toHaveBeenCalledWith("include", "multiple-entries"); + expect(mockedHub.setTag).toHaveBeenCalledWith("uploadLegacySourcemapsEntries", 3); }); it("should set deploy tag to true if the deploy option is specified", () => { setTelemetryDataOnHub( - normalizeUserOptions({ ...defaultOptions, deploy: { env: "production" } }), + normalizeUserOptions({ ...defaultOptions, release: { deploy: { env: "production" } } }), mockedHub as unknown as Hub, "rollup" ); @@ -91,7 +91,7 @@ describe("addPluginOptionTagsToHub", () => { (expectedValue, commitOptions) => { setTelemetryDataOnHub( // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - normalizeUserOptions({ ...defaultOptions, setCommits: commitOptions as any }), + normalizeUserOptions({ ...defaultOptions, release: { setCommits: commitOptions as any } }), mockedHub as unknown as Hub, "rollup" ); @@ -103,9 +103,10 @@ describe("addPluginOptionTagsToHub", () => { setTelemetryDataOnHub( normalizeUserOptions({ ...defaultOptions, - cleanArtifacts: true, - finalize: true, - dryRun: true, + release: { + cleanArtifacts: true, + finalize: true, + }, }), mockedHub as unknown as Hub, "rollup" @@ -113,7 +114,6 @@ describe("addPluginOptionTagsToHub", () => { expect(mockedHub.setTag).toHaveBeenCalledWith("clean-artifacts", true); expect(mockedHub.setTag).toHaveBeenCalledWith("finalize-release", true); - expect(mockedHub.setTag).toHaveBeenCalledWith("dry-run", true); }); it("shouldn't set any tags other than include if no opional options are specified", () => { @@ -123,8 +123,7 @@ describe("addPluginOptionTagsToHub", () => { "rollup" ); - expect(mockedHub.setTag).toHaveBeenCalledTimes(3); - expect(mockedHub.setTag).toHaveBeenCalledWith("include", "single-entry"); + expect(mockedHub.setTag).toHaveBeenCalledWith("uploadLegacySourcemapsEntries", 0); expect(mockedHub.setTag).toHaveBeenCalledWith("finalize-release", true); expect(mockedHub.setTag).toHaveBeenCalledWith("node", expect.any(String)); }); diff --git a/packages/e2e-tests/scenarios/basic-upload/basic-upload.test.ts b/packages/e2e-tests/scenarios/basic-upload/basic-upload.test.ts index 3283fda0..6a3f90b8 100644 --- a/packages/e2e-tests/scenarios/basic-upload/basic-upload.test.ts +++ b/packages/e2e-tests/scenarios/basic-upload/basic-upload.test.ts @@ -4,7 +4,7 @@ import { getSentryReleaseFiles } from "../../utils/releases"; describe("Simple Sourcemaps Upload (one string include + default options)", () => { it.each(BUNDLERS)("uploads the correct files using %s", async (bundler) => { - const release = `${pluginConfig.release || ""}-${bundler}`; + const release = `${pluginConfig.release?.name || ""}-${bundler}`; const sentryFiles = await getSentryReleaseFiles(release); diff --git a/packages/e2e-tests/scenarios/basic-upload/config.ts b/packages/e2e-tests/scenarios/basic-upload/config.ts index 4544cb91..c869c8d0 100644 --- a/packages/e2e-tests/scenarios/basic-upload/config.ts +++ b/packages/e2e-tests/scenarios/basic-upload/config.ts @@ -5,8 +5,10 @@ import * as path from "path"; * The Sentry bundler plugin config object used for this test */ export const pluginConfig: Options = { - release: "basic-upload", - include: path.resolve(__dirname, "out"), + release: { + name: "basic-upload", + uploadLegacySourcemaps: path.resolve(__dirname, "out"), + }, authToken: process.env["SENTRY_AUTH_TOKEN"] || "", org: "sentry-sdks", project: "js-bundler-plugin-e2e-tests", diff --git a/packages/e2e-tests/scenarios/basic-upload/setup.ts b/packages/e2e-tests/scenarios/basic-upload/setup.ts index 25a01f52..f6c4a4f4 100644 --- a/packages/e2e-tests/scenarios/basic-upload/setup.ts +++ b/packages/e2e-tests/scenarios/basic-upload/setup.ts @@ -4,7 +4,7 @@ import { pluginConfig } from "./config"; import { deleteAllReleases } from "../../utils/releases"; import { createCjsBundles } from "../../utils/create-cjs-bundles"; -deleteAllReleases(pluginConfig.release || "") +deleteAllReleases(pluginConfig.release?.name || "") .then(() => { const entryPointPath = path.resolve(__dirname, "input", "index.js"); const outputDir = path.resolve(__dirname, "out"); diff --git a/packages/e2e-tests/utils/create-cjs-bundles.ts b/packages/e2e-tests/utils/create-cjs-bundles.ts index 593e149d..c434311b 100644 --- a/packages/e2e-tests/utils/create-cjs-bundles.ts +++ b/packages/e2e-tests/utils/create-cjs-bundles.ts @@ -37,8 +37,12 @@ export function createCjsBundles( plugins: [ sentryVitePlugin({ ...sentryUnpluginOptions, - release: `${sentryUnpluginOptions.release}-vite`, - include: `${sentryUnpluginOptions.include as string}/vite`, + release: { + name: `${sentryUnpluginOptions.release.name!}-vite`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/vite`, + }, }), ], }); @@ -49,8 +53,12 @@ export function createCjsBundles( plugins: [ sentryRollupPlugin({ ...sentryUnpluginOptions, - release: `${sentryUnpluginOptions.release}-rollup`, - include: `${sentryUnpluginOptions.include as string}/rollup`, + release: { + name: `${sentryUnpluginOptions.release.name!}-rollup`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/rollup`, + }, }), ], }) @@ -70,8 +78,12 @@ export function createCjsBundles( plugins: [ sentryEsbuildPlugin({ ...sentryUnpluginOptions, - release: `${sentryUnpluginOptions.release}-esbuild`, - include: `${sentryUnpluginOptions.include as string}/esbuild`, + release: { + name: `${sentryUnpluginOptions.release.name!}-esbuild`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/esbuild`, + }, }), ], minify: true, @@ -93,8 +105,12 @@ export function createCjsBundles( plugins: [ sentryWebpackPlugin({ ...sentryUnpluginOptions, - release: `${sentryUnpluginOptions.release}-webpack4`, - include: `${sentryUnpluginOptions.include as string}/webpack4`, + release: { + name: `${sentryUnpluginOptions.release.name!}-webpack4`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/webpack4`, + }, }), ], }, @@ -120,8 +136,12 @@ export function createCjsBundles( plugins: [ sentryWebpackPlugin({ ...sentryUnpluginOptions, - release: `${sentryUnpluginOptions.release}-webpack5`, - include: `${sentryUnpluginOptions.include as string}/webpack5`, + release: { + name: `${sentryUnpluginOptions.release.name!}-webpack5`, + uploadLegacySourcemaps: `${ + sentryUnpluginOptions.release.uploadLegacySourcemaps as string + }/webpack5`, + }, }), ], }, diff --git a/packages/integration-tests/fixtures/basic-release-injection/setup.ts b/packages/integration-tests/fixtures/basic-release-injection/setup.ts index 5f0dc5fb..f7da397a 100644 --- a/packages/integration-tests/fixtures/basic-release-injection/setup.ts +++ b/packages/integration-tests/fixtures/basic-release-injection/setup.ts @@ -1,4 +1,3 @@ -import { Options } from "@sentry/bundler-plugin-core"; import * as path from "path"; import { createCjsBundles } from "../../utils/create-cjs-bundles"; @@ -6,7 +5,5 @@ const entryPointPath = path.resolve(__dirname, "input", "entrypoint.js"); const outputDir = path.resolve(__dirname, "out"); createCjsBundles({ index: entryPointPath }, outputDir, { - release: "I AM A RELEASE!", - include: outputDir, - dryRun: true, -} as Options); + release: { name: "I AM A RELEASE!", create: false }, +}); diff --git a/packages/integration-tests/fixtures/build-information-injection/setup.ts b/packages/integration-tests/fixtures/build-information-injection/setup.ts index 5112dea0..23b6a0a2 100644 --- a/packages/integration-tests/fixtures/build-information-injection/setup.ts +++ b/packages/integration-tests/fixtures/build-information-injection/setup.ts @@ -1,4 +1,3 @@ -import { Options } from "@sentry/bundler-plugin-core"; import * as path from "path"; import { createCjsBundles } from "../../utils/create-cjs-bundles"; @@ -6,7 +5,5 @@ const entryPointPath = path.resolve(__dirname, "input", "entrypoint.js"); const outputDir = path.resolve(__dirname, "out"); createCjsBundles({ index: entryPointPath }, outputDir, { - include: outputDir, - dryRun: true, _experiments: { injectBuildInformation: true }, -} as Options); +}); diff --git a/packages/integration-tests/fixtures/disabled-release-injection/setup.ts b/packages/integration-tests/fixtures/disabled-release-injection/setup.ts index 5f082124..759db2f6 100644 --- a/packages/integration-tests/fixtures/disabled-release-injection/setup.ts +++ b/packages/integration-tests/fixtures/disabled-release-injection/setup.ts @@ -1,4 +1,3 @@ -import { Options } from "@sentry/bundler-plugin-core"; import * as path from "path"; import { createCjsBundles } from "../../utils/create-cjs-bundles"; @@ -6,10 +5,7 @@ const entryPointPath = path.resolve(__dirname, "input", "entrypoint.js"); const outputDir = path.resolve(__dirname, "out"); createCjsBundles({ index: entryPointPath }, outputDir, { - release: "I AM A RELEASE!", + release: { name: "I AM A RELEASE!", inject: false, create: false }, project: "releasesProject", org: "releasesOrg", - include: outputDir, - dryRun: true, - injectRelease: false, -} as Options); +});