Skip to content

Commit

Permalink
patch main with sentry init, fetch sentry_flutter version
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase committed Dec 3, 2024
1 parent 95a01e6 commit 8a6839e
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 24 deletions.
48 changes: 32 additions & 16 deletions src/flutter/code-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import {
sentryImport,
pubspecOptions,
sentryProperties,
initSnippet,
// testErrorSnippet,
} from './templates';
import { fetchSdkVersion } from '../utils/release-registry';

/**
* Recursively finds a file per name in subfolders.
Expand Down Expand Up @@ -37,7 +39,7 @@ export function findFile(dir: string, name: string): string | null {
return null;
}

export function patchPubspec(pubspecFile: string | null, project: string, org: string): boolean {
export async function patchPubspec(pubspecFile: string | null, project: string, org: string): Promise<boolean> {
if (!pubspecFile || !fs.existsSync(pubspecFile)) {
clack.log.warn('No pubspec.yaml source file found in filesystem.');
Sentry.captureException('No pubspec.yaml source file');
Expand All @@ -49,16 +51,16 @@ export function patchPubspec(pubspecFile: string | null, project: string, org: s

// TODO: Check if already added sentry:

const sentryFlutterVersion = await fetchSdkVersion("sentry.dart.flutter") ?? "any";
pubspecContent = pubspecContent.slice(0, dependenciesIndex) +
' sentry:\n' +
` sentry_flutter: ${sentryFlutterVersion ? `^${sentryFlutterVersion}` : "any"}\n` +
pubspecContent.slice(dependenciesIndex);

const devDependenciesIndex = getDevDependenciesLocation(pubspecContent);

// TODO: Check if already added sentry-dart-plugin:

pubspecContent = pubspecContent.slice(0, devDependenciesIndex) +
' sentry-dart-plugin:\n' +
' sentry_dart_plugin: any\n' + // TODO: There is no sentry dart plugin in https://release-registry.services.sentry.io/sdks
pubspecContent.slice(devDependenciesIndex);

// TODO: Check if already added sentry:
Expand Down Expand Up @@ -92,21 +94,20 @@ export function addProperties(pubspecFile: string | null, authToken: string) {
} else {
fs.writeFileSync(gitignoreFile, `${sentryPropertiesFileName}\n`, 'utf8');
}

return true;
} catch (e) {
return false;
}
}

export function patchMain(mainFile: string | null): boolean {
export function patchMain(mainFile: string | null, dsn: string): boolean {
if (!mainFile || !fs.existsSync(mainFile)) {
clack.log.warn('No main.dart source file found in filesystem.');
Sentry.captureException('No main.dart source file');
return false;
}

const mainContent = fs.readFileSync(mainFile, 'utf8');
let mainContent = fs.readFileSync(mainFile, 'utf8');

if (/import\s+['"]package[:]sentry_flutter\/sentry_flutter\.dart['"];?/i.test(mainContent)) {
// sentry is already configured
Expand All @@ -120,16 +121,9 @@ export function patchMain(mainFile: string | null): boolean {
return true;
}

const importIndex = getLastImportLineLocation(mainContent);
const newActivityContent = mainContent.slice(0, importIndex) +
sentryImport +
mainContent.slice(importIndex);

// TODO: @denis setup
mainContent = patchMainContent(dsn, mainContent);

// TODO: @denis snippet

fs.writeFileSync(mainFile, newActivityContent, 'utf8');
fs.writeFileSync(mainFile, mainContent, 'utf8');

clack.log.success(
chalk.greenBright(
Expand All @@ -142,6 +136,28 @@ export function patchMain(mainFile: string | null): boolean {
return true;
}

export function patchMainContent(dsn: string, mainContent: string): string {

const importIndex = getLastImportLineLocation(mainContent);
mainContent = mainContent.slice(0, importIndex) +
sentryImport +
mainContent.slice(importIndex);

// Find and replace `runApp(...)`
mainContent = mainContent.replace(
/runApp\(([\s\S]*?)\);/g, // Match the `runApp(...)` invocation
(_, runAppArgs) => initSnippet(dsn, runAppArgs as string)
);

// Make the `main` function async if it's not already
mainContent = mainContent.replace(
/void\s+main\(\)\s*\{/g,
'Future<void> main() async {'
);

return mainContent;
}

export function getLastImportLineLocation(sourceCode: string): number {
const importRegex = /import\s+['"].*['"].*;/gim;
return getLastReqExpLocation(sourceCode, importRegex);
Expand Down
22 changes: 15 additions & 7 deletions src/flutter/flutter-wizzard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ async function runFlutterWizzardWithTelemetry(
clack.log.step(
`Adding ${chalk.bold('Sentry')} to your apps ${chalk.cyan('pubspec.yaml',)} file.`,
);
const pubspecPatched = codetools.patchPubspec(
pubspecFile,
selectedProject.slug,
selectedProject.organization.slug
)
const pubspecPatched = await traceStep('Patch pubspec.yaml', () =>
codetools.patchPubspec(
pubspecFile,
selectedProject.slug,
selectedProject.organization.slug
),
);
if (!pubspecPatched) {
clack.log.warn(
"Could not add Sentry to your apps pubspec.yaml file. You'll have to add it manually.\nPlease follow the instructions at https://docs.sentry.io/platforms/flutter/#install",
Expand All @@ -65,7 +67,9 @@ async function runFlutterWizzardWithTelemetry(

// ======== STEP X. Add sentry.properties with auth token ============

const propertiesAdded = codetools.addProperties(pubspecFile, authToken);
const propertiesAdded = traceStep('Add sentry.properties', () =>
codetools.addProperties(pubspecFile, authToken),
);
if (!propertiesAdded) {
clack.log.warn(
`We could not add "sentry.properties" file in your project directory in order to provide an auth token for Sentry CLI. You'll have to add it manually, or you can set the SENTRY_AUTH_TOKEN environment variable instead. See https://docs.sentry.io/cli/configuration/#auth-token for more information.`,
Expand All @@ -81,8 +85,12 @@ async function runFlutterWizzardWithTelemetry(
clack.log.step(
`Patching ${chalk.bold('main.dart')} with setup and test error snippet.`,
);

const mainFile = findFile(projectDir, 'main.dart');
const dsn = selectedProject.keys[0].dsn.public;

const mainPatched = traceStep('Patch main.dart', () =>
codetools.patchMain(findFile(projectDir, 'main.dart')),
codetools.patchMain(mainFile, dsn),
);
if (!mainPatched) {
clack.log.warn(
Expand Down
19 changes: 19 additions & 0 deletions src/flutter/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,22 @@ export function pubspecOptions(project: string, org: string): string {
export function sentryProperties(authToken: string): string {
return `auth_token=${authToken}`;
}

export function initSnippet(dsn: string, runApp: string): string {
return `await SentryFlutter.init(
(options) {
options.dsn = '${dsn}';
// Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing.
// We recommend adjusting this value in production.
options.tracesSampleRate = 1.0;
// The sampling rate for profiling is relative to tracesSampleRate
// Setting to 1.0 will profile 100% of sampled transactions:
// Note: Profiling alpha is available for iOS and macOS since SDK version 7.12.0
options.profilesSampleRate = 1.0;
},
appRunner: () => runApp(${runApp}),
);
// TODO: Remove this line after sending the first sample event to sentry.
Sentry.captureMessage('This is a sample exception.');
`
}
92 changes: 91 additions & 1 deletion test/flutter/code-tools.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//@ts-ignore
import { getDependenciesLocation, getDevDependenciesLocation, getLastImportLineLocation } from '../../src/flutter/code-tools';
import { patchMainContent, getDependenciesLocation, getDevDependenciesLocation, getLastImportLineLocation } from '../../src/flutter/code-tools';
//@ts-ignore
import { initSnippet } from '../../src/flutter/templates';

describe('code-tools', () => {
const pubspec = `name: flutter_example
Expand All @@ -19,6 +21,94 @@ dev_dependencies:
flutter_lints: ^2.0.0
`;

const simpleRunApp = `import 'package:flutter/widgets.dart';
void main() {
runApp(const MyApp());
}
`;

const asyncRunApp = `import 'package:flutter/widgets.dart';
void main() {
runApp(const MyApp());
}
`;

const simpleRunAppPatched = `import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
${initSnippet('dsn', 'const MyApp()')}
}
`;

const paramRunApp = `import 'package:flutter/widgets.dart';
Future<void> main() async {
await someFunction();
runApp(MyApp(param: SomeParam()));
await anotherFunction();
}
`;

const paramRunAppPatched = `import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await someFunction();
${initSnippet('dsn', 'MyApp(param: SomeParam())')}
await anotherFunction();
}
`;

const multilineRunApp = `import 'package:flutter/widgets.dart';
void main() {
runApp(
MyApp(
param: Param(),
multi: Another(1),
line: await bites(the: "dust"),
),
);
anotherFunction();
}
`;

const multilineRunAppPatched = `import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
${initSnippet('dsn', `
MyApp(
param: Param(),
multi: Another(1),
line: await bites(the: "dust"),
),
`)}
anotherFunction();
}
`;

describe('patchMainContent', () => {
it('wraps simple runApp', () => {
expect(patchMainContent('dsn', simpleRunApp)).toBe(simpleRunAppPatched);
});

it('wraps async runApp', () => {
expect(patchMainContent('dsn', asyncRunApp)).toBe(simpleRunAppPatched);
});

it('wraps runApp with parameterized app', () => {
expect(patchMainContent('dsn', paramRunApp)).toBe(paramRunAppPatched);
});

it('wraps multiline runApp', () => {
expect(patchMainContent('dsn', multilineRunApp)).toBe(multilineRunAppPatched);
});
});

describe('pubspec', () => {
it('returns proper line index for dependencies', () => {
expect(getDependenciesLocation(pubspec)).toBe(
Expand Down

0 comments on commit 8a6839e

Please sign in to comment.