Skip to content

Commit

Permalink
feat(sveltekit): Auto-detect SvelteKit adapters (#8193)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Lms24 committed May 30, 2023
1 parent ec094db commit 8482c0a
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 1 deletion.
62 changes: 62 additions & 0 deletions packages/sveltekit/src/vite/detectAdapter.ts
@@ -0,0 +1,62 @@
import type { Package } from '@sentry/types';
import * as fs from 'fs';
import * as path from 'path';

/**
* Supported @sveltejs/adapters-[adapter] SvelteKit adapters
*/
export type SupportedSvelteKitAdapters = 'node' | 'auto' | 'vercel' | 'other';

/**
* Tries to detect the used adapter for SvelteKit by looking at the dependencies.
* returns the name of the adapter or 'other' if no supported adapter was found.
*/
export async function detectAdapter(debug?: boolean): Promise<SupportedSvelteKitAdapters> {
const pkgJson = await loadPackageJson();

const allDependencies = pkgJson ? { ...pkgJson.dependencies, ...pkgJson.devDependencies } : {};

let adapter: SupportedSvelteKitAdapters = 'other';
if (allDependencies['@sveltejs/adapter-vercel']) {
adapter = 'vercel';
} else if (allDependencies['@sveltejs/adapter-node']) {
adapter = 'node';
} else if (allDependencies['@sveltejs/adapter-auto']) {
adapter = 'auto';
}

if (debug) {
if (adapter === 'other') {
// eslint-disable-next-line no-console
console.warn(
"[Sentry SvelteKit Plugin] Couldn't detect SvelteKit adapter. Please set the 'adapter' option manually.",
);
} else {
// eslint-disable-next-line no-console
console.log(`[Sentry SvelteKit Plugin] Detected SvelteKit ${adapter} adapter`);
}
}

return adapter;
}

/**
* Imports the pacakge.json file and returns the parsed JSON object.
*/
async function loadPackageJson(): Promise<Package | undefined> {
const pkgFile = path.join(process.cwd(), 'package.json');

try {
if (!fs.existsSync(pkgFile)) {
throw new Error(`File ${pkgFile} doesn't exist}`);
}

const pkgJsonContent = (await fs.promises.readFile(pkgFile, 'utf-8')).toString();

return JSON.parse(pkgJsonContent);
} catch (e) {
// eslint-disable-next-line no-console
console.warn("[Sentry SvelteKit Plugin] Couldn't load package.json:", e);
return undefined;
}
}
21 changes: 21 additions & 0 deletions packages/sveltekit/src/vite/sentryVitePlugins.ts
Expand Up @@ -3,6 +3,8 @@ import type { Plugin } from 'vite';

import type { AutoInstrumentSelection } from './autoInstrument';
import { makeAutoInstrumentationPlugin } from './autoInstrument';
import type { SupportedSvelteKitAdapters } from './detectAdapter';
import { detectAdapter } from './detectAdapter';
import { makeCustomSentryVitePlugin } from './sourceMaps';

type SourceMapsUploadOptions = {
Expand Down Expand Up @@ -39,6 +41,24 @@ export type SentrySvelteKitPluginOptions = {
* @default false.
*/
debug?: boolean;

/**
* Specify which SvelteKit adapter you're using.
* By default, the SDK will attempt auto-detect the used adapter at build time and apply the
* correct config for source maps upload or auto-instrumentation.
*
* Currently, the SDK supports the following adapters:
* - node (@sveltejs/adapter-node)
* - auto (@sveltejs/adapter-auto) only Vercel
* - vercel (@sveltejs/adapter-auto) only Serverless functions, no edge runtime
*
* Set this option, if the SDK detects the wrong adapter or you want to use an adapter
* that is not in this list. If you specify 'other', you'll most likely need to configure
* source maps upload yourself.
*
* @default {} the SDK attempts to auto-detect the used adapter at build time
*/
adapter?: SupportedSvelteKitAdapters;
} & SourceMapsUploadOptions &
AutoInstrumentOptions;

Expand All @@ -59,6 +79,7 @@ export async function sentrySvelteKit(options: SentrySvelteKitPluginOptions = {}
const mergedOptions = {
...DEFAULT_PLUGIN_OPTIONS,
...options,
adapter: options.adapter || (await detectAdapter(options.debug || false)),
};

const sentryPlugins: Plugin[] = [];
Expand Down
79 changes: 79 additions & 0 deletions packages/sveltekit/test/vite/detectAdapter.test.ts
@@ -0,0 +1,79 @@
import { vi } from 'vitest';

import { detectAdapter } from '../../src/vite/detectAdapter';

let existsFile = true;
const pkgJson = {
dependencies: {},
};
describe('detectAdapter', () => {
beforeEach(() => {
existsFile = true;
vi.clearAllMocks();
pkgJson.dependencies = {};
});

vi.mock('fs', () => {
return {
existsSync: () => existsFile,
promises: {
readFile: () => {
return Promise.resolve(JSON.stringify(pkgJson));
},
},
};
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

it.each(['auto', 'vercel', 'node'])(
'returns the adapter name (adapter %s) and logs it to the console',
async adapter => {
pkgJson.dependencies[`@sveltejs/adapter-${adapter}`] = '1.0.0';
const detectedAdapter = await detectAdapter(true);
expect(detectedAdapter).toEqual(adapter);
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(`Detected SvelteKit ${adapter} adapter`));
},
);

it('returns "other" if no supported adapter was found', async () => {
pkgJson.dependencies['@sveltejs/adapter-netlify'] = '1.0.0';
const detectedAdapter = await detectAdapter();
expect(detectedAdapter).toEqual('other');
});

it('logs a warning if in debug mode and no supported adapter was found', async () => {
pkgJson.dependencies['@sveltejs/adapter-netlify'] = '1.0.0';
const detectedAdapter = await detectAdapter(true);
expect(detectedAdapter).toEqual('other');
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("Couldn't detect SvelteKit adapter"));
});

it('returns "other" if package.json isnt available and emits a warning log', async () => {
existsFile = false;
const detectedAdapter = await detectAdapter();
expect(detectedAdapter).toEqual('other');

expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining("Couldn't load package.json"),
expect.any(Error),
);
});

it('prefers all other adapters over adapter auto', async () => {
pkgJson.dependencies['@sveltejs/adapter-auto'] = '1.0.0';
pkgJson.dependencies['@sveltejs/adapter-vercel'] = '1.0.0';
pkgJson.dependencies['@sveltejs/adapter-node'] = '1.0.0';

const detectedAdapter = await detectAdapter();
expect(detectedAdapter).toEqual('vercel');

delete pkgJson.dependencies['@sveltejs/adapter-vercel'];
const detectedAdapter2 = await detectAdapter();
expect(detectedAdapter2).toEqual('node');
});
});
9 changes: 8 additions & 1 deletion packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Expand Up @@ -17,7 +17,14 @@ vi.mock('fs', async () => {
};
});

describe('sentryVite()', () => {
vi.spyOn(console, 'log').mockImplementation(() => {
/* noop */
});
vi.spyOn(console, 'warn').mockImplementation(() => {
/* noop */
});

describe('sentrySvelteKit()', () => {
it('returns an array of Vite plugins', async () => {
const plugins = await sentrySvelteKit();

Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/package.ts
Expand Up @@ -2,4 +2,6 @@
export interface Package {
name: string;
version: string;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
}

0 comments on commit 8482c0a

Please sign in to comment.