Skip to content

Commit 8482c0a

Browse files
authored
feat(sveltekit): Auto-detect SvelteKit adapters (#8193)
First step for Vercel support: Detecting the used SvelteKit adapter. (This currently does nothing other than detecting the adapter; next step is to configure the source maps plugin correctly for the respective adapters) ref #8085
1 parent ec094db commit 8482c0a

File tree

5 files changed

+172
-1
lines changed

5 files changed

+172
-1
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { Package } from '@sentry/types';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
5+
/**
6+
* Supported @sveltejs/adapters-[adapter] SvelteKit adapters
7+
*/
8+
export type SupportedSvelteKitAdapters = 'node' | 'auto' | 'vercel' | 'other';
9+
10+
/**
11+
* Tries to detect the used adapter for SvelteKit by looking at the dependencies.
12+
* returns the name of the adapter or 'other' if no supported adapter was found.
13+
*/
14+
export async function detectAdapter(debug?: boolean): Promise<SupportedSvelteKitAdapters> {
15+
const pkgJson = await loadPackageJson();
16+
17+
const allDependencies = pkgJson ? { ...pkgJson.dependencies, ...pkgJson.devDependencies } : {};
18+
19+
let adapter: SupportedSvelteKitAdapters = 'other';
20+
if (allDependencies['@sveltejs/adapter-vercel']) {
21+
adapter = 'vercel';
22+
} else if (allDependencies['@sveltejs/adapter-node']) {
23+
adapter = 'node';
24+
} else if (allDependencies['@sveltejs/adapter-auto']) {
25+
adapter = 'auto';
26+
}
27+
28+
if (debug) {
29+
if (adapter === 'other') {
30+
// eslint-disable-next-line no-console
31+
console.warn(
32+
"[Sentry SvelteKit Plugin] Couldn't detect SvelteKit adapter. Please set the 'adapter' option manually.",
33+
);
34+
} else {
35+
// eslint-disable-next-line no-console
36+
console.log(`[Sentry SvelteKit Plugin] Detected SvelteKit ${adapter} adapter`);
37+
}
38+
}
39+
40+
return adapter;
41+
}
42+
43+
/**
44+
* Imports the pacakge.json file and returns the parsed JSON object.
45+
*/
46+
async function loadPackageJson(): Promise<Package | undefined> {
47+
const pkgFile = path.join(process.cwd(), 'package.json');
48+
49+
try {
50+
if (!fs.existsSync(pkgFile)) {
51+
throw new Error(`File ${pkgFile} doesn't exist}`);
52+
}
53+
54+
const pkgJsonContent = (await fs.promises.readFile(pkgFile, 'utf-8')).toString();
55+
56+
return JSON.parse(pkgJsonContent);
57+
} catch (e) {
58+
// eslint-disable-next-line no-console
59+
console.warn("[Sentry SvelteKit Plugin] Couldn't load package.json:", e);
60+
return undefined;
61+
}
62+
}

packages/sveltekit/src/vite/sentryVitePlugins.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { Plugin } from 'vite';
33

44
import type { AutoInstrumentSelection } from './autoInstrument';
55
import { makeAutoInstrumentationPlugin } from './autoInstrument';
6+
import type { SupportedSvelteKitAdapters } from './detectAdapter';
7+
import { detectAdapter } from './detectAdapter';
68
import { makeCustomSentryVitePlugin } from './sourceMaps';
79

810
type SourceMapsUploadOptions = {
@@ -39,6 +41,24 @@ export type SentrySvelteKitPluginOptions = {
3941
* @default false.
4042
*/
4143
debug?: boolean;
44+
45+
/**
46+
* Specify which SvelteKit adapter you're using.
47+
* By default, the SDK will attempt auto-detect the used adapter at build time and apply the
48+
* correct config for source maps upload or auto-instrumentation.
49+
*
50+
* Currently, the SDK supports the following adapters:
51+
* - node (@sveltejs/adapter-node)
52+
* - auto (@sveltejs/adapter-auto) only Vercel
53+
* - vercel (@sveltejs/adapter-auto) only Serverless functions, no edge runtime
54+
*
55+
* Set this option, if the SDK detects the wrong adapter or you want to use an adapter
56+
* that is not in this list. If you specify 'other', you'll most likely need to configure
57+
* source maps upload yourself.
58+
*
59+
* @default {} the SDK attempts to auto-detect the used adapter at build time
60+
*/
61+
adapter?: SupportedSvelteKitAdapters;
4262
} & SourceMapsUploadOptions &
4363
AutoInstrumentOptions;
4464

@@ -59,6 +79,7 @@ export async function sentrySvelteKit(options: SentrySvelteKitPluginOptions = {}
5979
const mergedOptions = {
6080
...DEFAULT_PLUGIN_OPTIONS,
6181
...options,
82+
adapter: options.adapter || (await detectAdapter(options.debug || false)),
6283
};
6384

6485
const sentryPlugins: Plugin[] = [];
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { vi } from 'vitest';
2+
3+
import { detectAdapter } from '../../src/vite/detectAdapter';
4+
5+
let existsFile = true;
6+
const pkgJson = {
7+
dependencies: {},
8+
};
9+
describe('detectAdapter', () => {
10+
beforeEach(() => {
11+
existsFile = true;
12+
vi.clearAllMocks();
13+
pkgJson.dependencies = {};
14+
});
15+
16+
vi.mock('fs', () => {
17+
return {
18+
existsSync: () => existsFile,
19+
promises: {
20+
readFile: () => {
21+
return Promise.resolve(JSON.stringify(pkgJson));
22+
},
23+
},
24+
};
25+
});
26+
27+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
28+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
29+
30+
it.each(['auto', 'vercel', 'node'])(
31+
'returns the adapter name (adapter %s) and logs it to the console',
32+
async adapter => {
33+
pkgJson.dependencies[`@sveltejs/adapter-${adapter}`] = '1.0.0';
34+
const detectedAdapter = await detectAdapter(true);
35+
expect(detectedAdapter).toEqual(adapter);
36+
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
37+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(`Detected SvelteKit ${adapter} adapter`));
38+
},
39+
);
40+
41+
it('returns "other" if no supported adapter was found', async () => {
42+
pkgJson.dependencies['@sveltejs/adapter-netlify'] = '1.0.0';
43+
const detectedAdapter = await detectAdapter();
44+
expect(detectedAdapter).toEqual('other');
45+
});
46+
47+
it('logs a warning if in debug mode and no supported adapter was found', async () => {
48+
pkgJson.dependencies['@sveltejs/adapter-netlify'] = '1.0.0';
49+
const detectedAdapter = await detectAdapter(true);
50+
expect(detectedAdapter).toEqual('other');
51+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
52+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("Couldn't detect SvelteKit adapter"));
53+
});
54+
55+
it('returns "other" if package.json isnt available and emits a warning log', async () => {
56+
existsFile = false;
57+
const detectedAdapter = await detectAdapter();
58+
expect(detectedAdapter).toEqual('other');
59+
60+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
61+
expect(consoleWarnSpy).toHaveBeenCalledWith(
62+
expect.stringContaining("Couldn't load package.json"),
63+
expect.any(Error),
64+
);
65+
});
66+
67+
it('prefers all other adapters over adapter auto', async () => {
68+
pkgJson.dependencies['@sveltejs/adapter-auto'] = '1.0.0';
69+
pkgJson.dependencies['@sveltejs/adapter-vercel'] = '1.0.0';
70+
pkgJson.dependencies['@sveltejs/adapter-node'] = '1.0.0';
71+
72+
const detectedAdapter = await detectAdapter();
73+
expect(detectedAdapter).toEqual('vercel');
74+
75+
delete pkgJson.dependencies['@sveltejs/adapter-vercel'];
76+
const detectedAdapter2 = await detectAdapter();
77+
expect(detectedAdapter2).toEqual('node');
78+
});
79+
});

packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ vi.mock('fs', async () => {
1717
};
1818
});
1919

20-
describe('sentryVite()', () => {
20+
vi.spyOn(console, 'log').mockImplementation(() => {
21+
/* noop */
22+
});
23+
vi.spyOn(console, 'warn').mockImplementation(() => {
24+
/* noop */
25+
});
26+
27+
describe('sentrySvelteKit()', () => {
2128
it('returns an array of Vite plugins', async () => {
2229
const plugins = await sentrySvelteKit();
2330

packages/types/src/package.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
export interface Package {
33
name: string;
44
version: string;
5+
dependencies?: Record<string, string>;
6+
devDependencies?: Record<string, string>;
57
}

0 commit comments

Comments
 (0)