This repository has been archived by the owner on Dec 12, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Svelte Config rewrite transformer
- Loading branch information
Showing
8 changed files
with
431 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import path from 'node:path'; | ||
import url from 'url'; | ||
|
||
import { runJscodeshift } from '../../utils/jscodeshift.js'; | ||
|
||
/** @type {import('types').Transformer} */ | ||
export default { | ||
name: 'Rewrite Svelte Config', | ||
async transform(files, options) { | ||
if (!options.sdk || !['@sentry/svelte', '@sentry/sveltekit'].includes(options.sdk)) { | ||
// No need to run this transformer if the SDK is not a Svelte SDK (or not specified at all) | ||
return; | ||
} | ||
|
||
// The only file this transformer touches svelte.config.js. | ||
// Using filter for the remote chance that there's more than one such a file (monorepos?) | ||
const svelteConfigs = files.filter(file => path.basename(file) === 'svelte.config.js'); | ||
|
||
if (!svelteConfigs.length) { | ||
return; | ||
} | ||
|
||
const transformPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), './transform.cjs'); | ||
|
||
await runJscodeshift(transformPath, svelteConfigs, options); | ||
}, | ||
}; |
172 changes: 172 additions & 0 deletions
172
src/transformers/rewriteSvelteConfig/rewriteSvelteConfig.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { afterEach, describe, it } from 'node:test'; | ||
import * as assert from 'node:assert'; | ||
import { rmSync } from 'node:fs'; | ||
|
||
import { getDirFileContent, getFixturePath, makeTmpDir } from '../../../test-helpers/testPaths.js'; | ||
import { assertStringEquals } from '../../../test-helpers/assert.js'; | ||
|
||
import rewriteSvelteConfig from './index.js'; | ||
|
||
describe('transformers | rewriteSvelteConfig', () => { | ||
let tmpDir = ''; | ||
|
||
afterEach(() => { | ||
if (tmpDir) { | ||
rmSync(tmpDir, { force: true, recursive: true }); | ||
tmpDir = ''; | ||
} | ||
}); | ||
|
||
it('has correct name', () => { | ||
assert.equal(rewriteSvelteConfig.name, 'Rewrite Svelte Config'); | ||
}); | ||
|
||
it('no-ops with app without Sentry', async () => { | ||
tmpDir = makeTmpDir(getFixturePath('noSentry')); | ||
await rewriteSvelteConfig.transform([tmpDir], { filePatterns: [] }); | ||
|
||
const actual = getDirFileContent(tmpDir, 'app.js'); | ||
assert.equal(actual, getDirFileContent(`${process.cwd()}/test-fixtures/noSentry`, 'app.js')); | ||
}); | ||
|
||
it('no-ops if no SDK is specified', async () => { | ||
tmpDir = makeTmpDir(getFixturePath('svelteAppNamed')); | ||
await rewriteSvelteConfig.transform([tmpDir], { filePatterns: [] }); | ||
|
||
const actual = getDirFileContent(tmpDir, 'svelte.config.js'); | ||
assert.equal(actual, getDirFileContent(`${process.cwd()}/test-fixtures/svelteAppNamed`, 'svelte.config.js')); | ||
}); | ||
|
||
it('no-ops if a non-Svelte SDK is specified', async () => { | ||
tmpDir = makeTmpDir(getFixturePath('svelteAppNamed')); | ||
await rewriteSvelteConfig.transform([tmpDir], { filePatterns: [], sdk: '@sentry/react' }); | ||
|
||
const actual = getDirFileContent(tmpDir, 'svelte.config.js'); | ||
assert.equal(actual, getDirFileContent(`${process.cwd()}/test-fixtures/svelteAppNamed`, 'svelte.config.js')); | ||
}); | ||
|
||
it('no-ops if `componentTrackingPreprocessor` is not used in the file', async () => { | ||
tmpDir = makeTmpDir(getFixturePath('svelteAppUnchanged')); | ||
await rewriteSvelteConfig.transform([tmpDir], { filePatterns: [], sdk: '@sentry/svelte' }); | ||
|
||
const actual = getDirFileContent(tmpDir, 'svelte.config.js'); | ||
assert.equal(actual, getDirFileContent(`${process.cwd()}/test-fixtures/svelteAppUnchanged`, 'svelte.config.js')); | ||
}); | ||
|
||
it('works with a svelte config with named imports', async () => { | ||
tmpDir = makeTmpDir(getFixturePath('svelteAppNamed')); | ||
await rewriteSvelteConfig.transform([`${tmpDir}/svelte.config.js`], { filePatterns: [], sdk: '@sentry/svelte' }); | ||
|
||
const actual = getDirFileContent(tmpDir, 'svelte.config.js'); | ||
|
||
assertStringEquals( | ||
actual, | ||
`import adapter from '@sveltejs/adapter-vercel'; | ||
import preprocess from 'svelte-preprocess'; | ||
import { mdsvex } from 'mdsvex'; | ||
import { withSentryConfig } from "@sentry/svelte"; | ||
/** @type {import('@sveltejs/kit').Config} */ | ||
const config = { | ||
// Consult https://github.com/sveltejs/svelte-preprocess | ||
// for more information about preprocessors | ||
preprocess: [preprocess({ postcss: true }), mdsvex({ | ||
extensions: ['.md'] | ||
})], | ||
extensions: ['.svelte', '.md'], | ||
kit: { | ||
adapter: adapter(), | ||
files: { | ||
lib: './src/lib' | ||
} | ||
}, | ||
vitePlugin: { | ||
inspector: true | ||
} | ||
}; | ||
export default withSentryConfig(config);` | ||
); | ||
}); | ||
|
||
it('works with a svelte config with a namespace Sentry import', async () => { | ||
tmpDir = makeTmpDir(getFixturePath('svelteAppNamespace')); | ||
await rewriteSvelteConfig.transform([`${tmpDir}/svelte.config.js`], { filePatterns: [], sdk: '@sentry/sveltekit' }); | ||
|
||
const actual = getDirFileContent(tmpDir, 'svelte.config.js'); | ||
|
||
assertStringEquals( | ||
actual, | ||
`import adapter from '@sveltejs/adapter-vercel'; | ||
import preprocess from 'svelte-preprocess'; | ||
import { mdsvex } from 'mdsvex'; | ||
import * as Sentry from '@sentry/sveltekit'; | ||
/** @type {import('@sveltejs/kit').Config} */ | ||
const config = { | ||
// Consult https://github.com/sveltejs/svelte-preprocess | ||
// for more information about preprocessors | ||
preprocess: [preprocess({ postcss: true }), mdsvex({ | ||
extensions: ['.md'] | ||
})], | ||
extensions: ['.svelte', '.md'], | ||
kit: { | ||
adapter: adapter(), | ||
files: { | ||
lib: './src/lib' | ||
} | ||
}, | ||
vitePlugin: { | ||
inspector: true | ||
} | ||
}; | ||
export default Sentry.withSentryConfig(config);` | ||
); | ||
}); | ||
|
||
it('only removes the preprocessor if the wrapper is already applied', async () => { | ||
tmpDir = makeTmpDir(getFixturePath('svelteAppOnlyRemove')); | ||
await rewriteSvelteConfig.transform([`${tmpDir}/svelte.config.js`], { filePatterns: [], sdk: '@sentry/svelte' }); | ||
|
||
const actual = getDirFileContent(tmpDir, 'svelte.config.js'); | ||
|
||
assertStringEquals( | ||
actual, | ||
`import adapter from '@sveltejs/adapter-vercel'; | ||
import preprocess from 'svelte-preprocess'; | ||
import { mdsvex } from 'mdsvex'; | ||
import * as Sentry from '@sentry/svelte'; | ||
/** @type {import('@sveltejs/kit').Config} */ | ||
const config = { | ||
// Consult https://github.com/sveltejs/svelte-preprocess | ||
// for more information about preprocessors | ||
preprocess: [preprocess({ postcss: true }), mdsvex({ | ||
extensions: ['.md'] | ||
})], | ||
extensions: ['.svelte', '.md'], | ||
kit: { | ||
adapter: adapter(), | ||
files: { | ||
lib: './src/lib' | ||
} | ||
}, | ||
vitePlugin: { | ||
inspector: true | ||
} | ||
}; | ||
export default Sentry.withSentryConfig(config);` | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
const { hasSentryImportOrRequire } = require('../../utils/jscodeshift.cjs'); | ||
|
||
/** | ||
* This transformer rewrites the `svelte.config.js` file to use the `withSentryConfig` function | ||
* | ||
* Replaces the `componentTrackingPreprocessor` function call with wrapping `withSentryConfig` around the | ||
* default exprt of the config file. | ||
* | ||
* @param {import('jscodeshift').FileInfo} fileInfo | ||
* @param {import('jscodeshift').API} api | ||
* @param {import('jscodeshift').Options & { sentry: import('types').RunOptions & {sdk: string} }} options | ||
*/ | ||
module.exports = function transform(fileInfo, api, options) { | ||
const j = api.jscodeshift; | ||
const source = fileInfo.source; | ||
|
||
// We're already filtering beforehand for @sentry/svelte or @sentry/sveltekit | ||
// so `options.sentry.sdk` is guaranteed to be one of those two | ||
if (!hasSentryImportOrRequire(fileInfo.source, options.sentry.sdk)) { | ||
return undefined; | ||
} | ||
|
||
const root = j(source, options); | ||
|
||
const sentryNamespaceImports = root | ||
.find(j.ImportDeclaration, { source: { value: options.sentry.sdk } }) | ||
.filter( | ||
importPath => !!importPath.node.specifiers?.some(specifier => specifier.type === 'ImportNamespaceSpecifier') | ||
); | ||
|
||
/** { @type import('jscodeshift').ASTPath<import('jscodeshift').ImportDeclaration> | undefined */ | ||
const sentryNamespaceImport = sentryNamespaceImports.length ? sentryNamespaceImports.get() : undefined; | ||
const sentryNameSpace = sentryNamespaceImport?.node.specifiers?.[0].local?.name; | ||
|
||
// 1. remove `sentryComponentTrackingPreprocessor()` | ||
|
||
if (sentryNameSpace) { | ||
const preprocCalls = root.find(j.CallExpression, { | ||
callee: { | ||
type: 'MemberExpression', | ||
object: { type: 'Identifier', name: sentryNameSpace }, | ||
property: { type: 'Identifier', name: 'componentTrackingPreprocessor' }, | ||
}, | ||
}); | ||
preprocCalls.remove(); | ||
} else { | ||
const preprocCalls = root.find(j.CallExpression, { callee: { name: 'componentTrackingPreprocessor' } }); | ||
preprocCalls.remove(); | ||
} | ||
|
||
// 2. wrap withSentryConfig around the default export | ||
|
||
/** { @type import('jscodeshift').ASTPath<import('jscodeshift').ExportDefaultDeclaration> */ | ||
const defaultExport = root.find(j.ExportDefaultDeclaration).get(0); | ||
|
||
const oldDefaultExportValue = defaultExport.node.declaration; | ||
|
||
// if the old declaration value already has withSentryConfig, we are done | ||
if (j(oldDefaultExportValue).toSource().includes('withSentryConfig(')) { | ||
return root.toSource(); | ||
} | ||
|
||
if (sentryNameSpace) { | ||
const newDefaultExportDeclaration = j.callExpression( | ||
j.memberExpression(j.identifier(sentryNameSpace), j.identifier('withSentryConfig')), | ||
// @ts-ignore there's probably a few edge cases which is why TS is complaining. I'm lazy :) | ||
[oldDefaultExportValue] | ||
); | ||
defaultExport.node.declaration = newDefaultExportDeclaration; | ||
} else { | ||
// @ts-ignore there's probably a few edge cases which is why TS is complaining. I'm lazy :) | ||
const newDefaultExportDeclaration = j.callExpression(j.identifier('withSentryConfig'), [oldDefaultExportValue]); | ||
defaultExport.node.declaration = newDefaultExportDeclaration; | ||
|
||
root | ||
.find(j.ImportDeclaration, { source: { value: options.sentry.sdk } }) | ||
.insertAfter( | ||
j.importDeclaration([j.importSpecifier(j.identifier('withSentryConfig'))], j.literal(options.sentry.sdk)) | ||
); | ||
|
||
root.find(j.ImportDeclaration, { source: { value: options.sentry.sdk } }).forEach(importPath => { | ||
importPath.node.specifiers = importPath.node.specifiers?.filter( | ||
specifier => specifier.type !== 'ImportSpecifier' || specifier.imported.name !== 'componentTrackingPreprocessor' | ||
); | ||
|
||
if (!importPath.node.specifiers?.length) { | ||
importPath.prune(); | ||
} | ||
}); | ||
} | ||
return root.toSource(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import adapter from '@sveltejs/adapter-vercel'; | ||
import preprocess from 'svelte-preprocess'; | ||
import { mdsvex } from 'mdsvex'; | ||
import { componentTrackingPreprocessor } from '@sentry/svelte'; | ||
|
||
/** @type {import('@sveltejs/kit').Config} */ | ||
const config = { | ||
// Consult https://github.com/sveltejs/svelte-preprocess | ||
// for more information about preprocessors | ||
preprocess: [ | ||
preprocess({ postcss: true }), | ||
componentTrackingPreprocessor({ | ||
trackComponents: ['Header', 'App', 'Footer'], | ||
trackInit: true, | ||
trackUpdates: false, | ||
}), | ||
mdsvex({ | ||
extensions: ['.md'] | ||
}), | ||
], | ||
|
||
extensions: ['.svelte', '.md'], | ||
|
||
kit: { | ||
adapter: adapter(), | ||
files: { | ||
lib: './src/lib' | ||
} | ||
}, | ||
|
||
vitePlugin: { | ||
inspector: true | ||
} | ||
}; | ||
|
||
export default config; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import adapter from '@sveltejs/adapter-vercel'; | ||
import preprocess from 'svelte-preprocess'; | ||
import { mdsvex } from 'mdsvex'; | ||
import * as Sentry from '@sentry/sveltekit'; | ||
|
||
/** @type {import('@sveltejs/kit').Config} */ | ||
const config = { | ||
// Consult https://github.com/sveltejs/svelte-preprocess | ||
// for more information about preprocessors | ||
preprocess: [ | ||
preprocess({ postcss: true }), | ||
mdsvex({ | ||
extensions: ['.md'] | ||
}), | ||
Sentry.componentTrackingPreprocessor({ | ||
trackComponents: ['Header', 'App', 'Footer'], | ||
trackInit: true, | ||
trackUpdates: false, | ||
}), | ||
], | ||
|
||
extensions: ['.svelte', '.md'], | ||
|
||
kit: { | ||
adapter: adapter(), | ||
files: { | ||
lib: './src/lib' | ||
} | ||
}, | ||
|
||
vitePlugin: { | ||
inspector: true | ||
} | ||
}; | ||
|
||
export default config; |
Oops, something went wrong.