Skip to content

Commit 5fc18dc

Browse files
chargomeclaude
andauthored
fix(remix): Pass the project when creating a release (#25079)
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 (#23398), `sentry-upload-sourcemaps` failed with `400 Bad Request` on every run. The unit test mocked the SDK, so it did not catch it. Reproduced against a real org. Both upload scripts take the project from `--project`, then `SENTRY_PROJECT`, and fail with a clear message when neither is set. `@sentry/bundler-plugins` already did this. The React Router Vite plugin has the same bug, fixed separately. Fixes #24989 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 40ad4ca commit 5fc18dc

3 files changed

Lines changed: 44 additions & 11 deletions

File tree

‎packages/remix/scripts/createRelease.js‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ 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(
32+
'[sentry] A project is required to create a release. Pass `--project` or set `SENTRY_PROJECT` as environment variable.',
33+
);
34+
}
35+
await sentry.release.create({ orgVersion: release, project });
2936

3037
try {
3138
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` as environment variable.');
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: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { beforeEach, describe, expect, it, vi } from 'vitest';
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
22

33
const createMock = vi.fn();
44
const uploadSourceMapsMock = vi.fn();
@@ -48,12 +48,16 @@ beforeEach(() => {
4848
proposeVersionMock.mockClear();
4949
});
5050

51+
afterEach(() => {
52+
vi.unstubAllEnvs();
53+
});
54+
5155
describe('createRelease', () => {
5256
it('should use release param when given', async () => {
53-
await createRelease({ release: '0.1.2.3' }, '~/build/', 'public/build');
57+
await createRelease({ release: '0.1.2.3', project: 'my-project' }, '~/build/', 'public/build');
5458

5559
expect(proposeVersionMock).not.toHaveBeenCalled();
56-
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3' });
60+
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3', project: 'my-project' });
5761
expect(uploadSourceMapsMock).toHaveBeenCalledWith({
5862
directory: 'public/build',
5963
release: '0.1.2.3',
@@ -64,10 +68,10 @@ describe('createRelease', () => {
6468
});
6569

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

6973
expect(proposeVersionMock).toHaveBeenCalled();
70-
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3.4' });
74+
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3.4', project: 'my-project' });
7175
expect(uploadSourceMapsMock).toHaveBeenCalledWith({
7276
directory: 'public/build',
7377
release: '0.1.2.3.4',
@@ -80,6 +84,7 @@ describe('createRelease', () => {
8084
it('should use given buildPath and urlPrefix over the defaults when given.', async () => {
8185
await createRelease(
8286
{
87+
project: 'my-project',
8388
urlPrefix: '~/build/',
8489
buildPath: 'public/build',
8590
},
@@ -88,7 +93,7 @@ describe('createRelease', () => {
8893
);
8994

9095
expect(proposeVersionMock).toHaveBeenCalled();
91-
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3.4' });
96+
expect(createMock).toHaveBeenCalledWith({ orgVersion: '0.1.2.3.4', project: 'my-project' });
9297
expect(uploadSourceMapsMock).toHaveBeenCalledWith({
9398
directory: 'public/build',
9499
release: '0.1.2.3.4',
@@ -101,7 +106,7 @@ describe('createRelease', () => {
101106
it('logs an error when uploadSourceMaps fails', async () => {
102107
uploadSourceMapsMock.mockRejectedValue(new Error('Failed to upload sourcemaps'));
103108

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

106111
expect(uploadSourceMapsMock).toHaveBeenCalledWith({
107112
directory: 'public/build',
@@ -118,10 +123,26 @@ describe('createRelease', () => {
118123
it('logs an error when finalize fails', async () => {
119124
finalizeMock.mockRejectedValue(new Error('Failed to finalize release'));
120125

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

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

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

0 commit comments

Comments
 (0)