Skip to content

Commit ce6eead

Browse files
chargomeclaude
andcommitted
fix(remix,react-router): Pass the project when creating a release
The release API rejects a release without a project, and the project given to the `sentry` SDK does not reach `release.create`. Since the move to that SDK, `sentry-upload-sourcemaps` failed with 400 on every run and the React Router Vite plugin logged a failed release creation. The scripts take the project from `--project`, then `SENTRY_PROJECT`, and fail with a clear message when neither is set. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 1e3cccd commit ce6eead

5 files changed

Lines changed: 50 additions & 13 deletions

File tree

‎packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export const sentryOnBuildEnd: BuildEndHook = async ({ reactRouterConfig, viteCo
6767
// check if release should be created
6868
if (release?.name) {
6969
try {
70-
await sentry.release.create({ orgVersion: release.name });
70+
// The release API requires a project, and the one given to the SDK does not reach this call.
71+
await sentry.release.create({ orgVersion: release.name, project });
7172
} catch (error) {
7273
// eslint-disable-next-line no-console
7374
console.error('[Sentry] Could not create release', error);

‎packages/react-router/test/vite/buildEnd/handleOnBuildEnd.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ describe('sentryOnBuildEnd', () => {
8585
// @ts-expect-error - mocking the React config
8686
await sentryOnBuildEnd(config);
8787

88-
expect(mockSentrySdkInstance.release.create).toHaveBeenCalledWith({ orgVersion: 'v1.0.0' });
88+
expect(mockSentrySdkInstance.release.create).toHaveBeenCalledWith({
89+
orgVersion: 'v1.0.0',
90+
project: 'test-project',
91+
});
8992
});
9093

9194
it('resolves root-level BuildTimeOptionsBase options for release creation and source map upload', async () => {
@@ -115,7 +118,7 @@ describe('sentryOnBuildEnd', () => {
115118
url: undefined,
116119
headers: undefined,
117120
});
118-
expect(mockSentrySdkInstance.release.create).toHaveBeenCalledWith({ orgVersion: '1.2.3' });
121+
expect(mockSentrySdkInstance.release.create).toHaveBeenCalledWith({ orgVersion: '1.2.3', project: 'my-project' });
119122
expect(mockSentrySdkInstance.sourcemap.upload).toHaveBeenCalledWith({
120123
directory: '/build',
121124
release: '1.2.3',

‎packages/remix/scripts/createRelease.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ async function createRelease(argv, URL_PREFIX, BUILD_PATH) {
2525
release = argv.release;
2626
}
2727

28-
await sentry.release.create({ orgVersion: release });
28+
// The release API requires a project, and the one given to the SDK does not reach this call.
29+
const project = argv.project ?? process.env.SENTRY_PROJECT;
30+
if (!project) {
31+
throw new Error('[sentry] A project is required to create a release. Pass `--project` or set `SENTRY_PROJECT`.');
32+
}
33+
await sentry.release.create({ orgVersion: release, project });
2934

3035
try {
3136
await sentry.sourcemap.upload({

‎packages/remix/scripts/sentry-remix-v3-upload-sourcemaps.mjs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ let keepOutput = false;
2121
try {
2222
const values = parseOptions();
2323
keepOutput = Boolean(values['keep-output'] || values['out-dir']);
24+
const org = values.org ?? process.env.SENTRY_ORG;
25+
const project = values.project ?? process.env.SENTRY_PROJECT;
26+
if (!values['dry-run'] && !project) {
27+
fail('a project is required for the upload. Pass `--project` or set `SENTRY_PROJECT`.');
28+
}
2429

2530
const { emitAssets } = await import('@sentry/remix/v3');
2631

@@ -63,11 +68,11 @@ try {
6368
console.log('[sentry] --dry-run, skipping upload');
6469
} else {
6570
const { createSentrySDK } = await import('sentry');
66-
const sentry = createSentrySDK({ url: values.url, org: values.org, project: values.project });
71+
const sentry = createSentrySDK({ url: values.url, org, project });
6772
const release = values.release ?? (await sentry.release['propose-version']()).version;
6873
try {
6974
// The project has to be named here; the SDK default does not reach release creation.
70-
await sentry.release.create({ orgVersion: release, project: values.project });
75+
await sentry.release.create({ orgVersion: release, project });
7176
// Modules keep their served names (`.ts`, `.tsx`), which the upload skips by default. Only module
7277
// extensions go here: the upload pairs each module with the `.map` sibling `emitAssets` wrote.
7378
const ext = [...new Set(emitted.map(asset => path.extname(asset.file)))].join(',');

‎packages/remix/test/scripts/upload-sourcemaps.test.ts‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ beforeEach(() => {
5050

5151
describe('createRelease', () => {
5252
it('should use release param when given', async () => {
53-
await createRelease({ release: '0.1.2.3' }, '~/build/', 'public/build');
53+
await createRelease({ release: '0.1.2.3', project: 'my-project' }, '~/build/', 'public/build');
5454

5555
expect(proposeVersionMock).not.toHaveBeenCalled();
56-
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3' });
56+
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3', project: 'my-project' });
5757
expect(uploadSourceMapsMock).toHaveBeenCalledWith({
5858
directory: 'public/build',
5959
release: '0.1.2.3',
@@ -64,10 +64,10 @@ describe('createRelease', () => {
6464
});
6565

6666
it('should call `proposeVersion` when release param is not given.', async () => {
67-
await createRelease({}, '~/build/', 'public/build');
67+
await createRelease({ project: 'my-project' }, '~/build/', 'public/build');
6868

6969
expect(proposeVersionMock).toHaveBeenCalled();
70-
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3.4' });
70+
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3.4', project: 'my-project' });
7171
expect(uploadSourceMapsMock).toHaveBeenCalledWith({
7272
directory: 'public/build',
7373
release: '0.1.2.3.4',
@@ -80,6 +80,7 @@ describe('createRelease', () => {
8080
it('should use given buildPath and urlPrefix over the defaults when given.', async () => {
8181
await createRelease(
8282
{
83+
project: 'my-project',
8384
urlPrefix: '~/build/',
8485
buildPath: 'public/build',
8586
},
@@ -88,7 +89,7 @@ describe('createRelease', () => {
8889
);
8990

9091
expect(proposeVersionMock).toHaveBeenCalled();
91-
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3.4' });
92+
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3.4', project: 'my-project' });
9293
expect(uploadSourceMapsMock).toHaveBeenCalledWith({
9394
directory: 'public/build',
9495
release: '0.1.2.3.4',
@@ -101,7 +102,7 @@ describe('createRelease', () => {
101102
it('logs an error when uploadSourceMaps fails', async () => {
102103
uploadSourceMapsMock.mockRejectedValue(new Error('Failed to upload sourcemaps'));
103104

104-
await createRelease({}, '~/build/', 'public/build');
105+
await createRelease({ project: 'my-project' }, '~/build/', 'public/build');
105106

106107
expect(uploadSourceMapsMock).toHaveBeenCalledWith({
107108
directory: 'public/build',
@@ -118,10 +119,32 @@ describe('createRelease', () => {
118119
it('logs an error when finalize fails', async () => {
119120
finalizeMock.mockRejectedValue(new Error('Failed to finalize release'));
120121

121-
await createRelease({}, '~/build/', 'public/build');
122+
await createRelease({ project: 'my-project' }, '~/build/', 'public/build');
122123

123124
expect(consoleWarnSpy).toHaveBeenCalledWith('[sentry] Failed to finalize release.');
124125
});
126+
127+
it('falls back to SENTRY_PROJECT for the release project', async () => {
128+
vi.stubEnv('SENTRY_PROJECT', 'env-project');
129+
try {
130+
await createRelease({ release: '0.1.2.3' }, '~/build/', 'public/build');
131+
} finally {
132+
vi.unstubAllEnvs();
133+
}
134+
135+
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3', project: 'env-project' });
136+
});
137+
138+
it('fails before creating a release when no project is known', async () => {
139+
vi.stubEnv('SENTRY_PROJECT', '');
140+
try {
141+
await expect(createRelease({ release: '0.1.2.3' }, '~/build/', 'public/build')).rejects.toThrow('--project');
142+
} finally {
143+
vi.unstubAllEnvs();
144+
}
145+
146+
expect(createMock).not.toHaveBeenCalled();
147+
});
125148
});
126149

127150
// To avoid `--isolatedModules` flag as we're not importing

0 commit comments

Comments
 (0)