Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Feature

- feat(apple): Add `fastlane-plugin-sentry` to Gemfile + use debug upload ([#1113](https://github.com/getsentry/sentry-wizard/pull/1113))

## 6.6.1

fix(telemetry): Handle promise rejections during wizard cancellation ([#1111](https://github.com/getsentry/sentry-wizard/pull/1111))
Expand Down
23 changes: 23 additions & 0 deletions src/apple/configure-fastlane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import chalk from 'chalk';
import { traceStep } from '../telemetry';
import { debug } from '../utils/debug';
import * as fastlane from './fastlane';
import * as gemfile from './gemfile';

export async function configureFastlane({
projectDir,
Expand Down Expand Up @@ -41,6 +42,28 @@ export async function configureFastlane({
debug(`Fastlane added: ${chalk.cyan(added.toString())}`);

if (added) {
debug(`Gemfile found, asking user if they want to configure Gemfile`);
const shouldAddGemfile = await clack.confirm({
message:
'Found a Gemfile in your project. Do you want to add the fastlane-plugin-sentry gem to your Gemfile?',
});
debug(
`User wants to add Gemfile: ${chalk.cyan(shouldAddGemfile.toString())}`,
);
Sentry.setTag('gemfile-desired', shouldAddGemfile);

if (shouldAddGemfile) {
debug(`Adding fastlane-plugin-sentry action to Gemfile`);
const gemfileUpdated = gemfile.addSentryPluginToGemfile(projectDir);
debug(`Gemfile updated: ${chalk.cyan(gemfileUpdated.toString())}`);

if (!gemfileUpdated) {
clack.log.warn(
'Could not edit your Gemfile to add the fastlane-plugin-sentry gem. Please follow the instructions at https://docs.sentry.io/platforms/apple/guides/ios/dsym/#fastlane',
);
}
}

clack.log.step(
'A new step was added to your fastlane file. Now and you build your project with fastlane, debug symbols and source context will be uploaded to Sentry.',
);
Expand Down
4 changes: 3 additions & 1 deletion src/apple/fastlane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ function addSentryToLane(
project: string,
): string {
const laneContent = content.slice(lane.index, lane.index + lane.length);
const sentryCLIMatch = /sentry_cli\s*\([^)]+\)/gim.exec(laneContent);
const sentryCLIMatch = /sentry_debug_files_upload\s*\([^)]+\)/gim.exec(
laneContent,
);
if (sentryCLIMatch) {
// Sentry already added to lane. Update it.
return (
Expand Down
72 changes: 72 additions & 0 deletions src/apple/gemfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as fs from 'fs';
import * as path from 'path';

export function gemFile(projectPath: string): string | null {
const gemfilePath = path.join(projectPath, 'Gemfile');
return fs.existsSync(gemfilePath) ? gemfilePath : null;
}

export function addSentryPluginToGemfile(projectDir: string): boolean {
const gemfilePath = gemFile(projectDir);
if (!gemfilePath) {
return false;
}

const fileContent = fs.readFileSync(gemfilePath, 'utf8');

// Check if the sentry plugin is already in the Gemfile
const sentryPluginRegex = /gem\s+['"]fastlane-plugin-sentry['"]/;
if (sentryPluginRegex.test(fileContent)) {
// Sentry plugin already exists, no need to add it
return true;
}

// Find the best place to insert the gem
// Look for other fastlane plugins first, then fastlane gem, then add at the end
const fastlanePluginRegex = /gem\s+['"](fastlane-plugin-[^'"]+)['"]/;
const fastlaneGemRegex = /gem\s+['"]fastlane['"]/;

let insertionPoint: number;
let insertionContent: string;

const fastlanePluginMatch = fastlanePluginRegex.exec(fileContent);
const fastlaneGemMatch = fastlaneGemRegex.exec(fileContent);

if (fastlanePluginMatch) {
// Insert after the last fastlane plugin
const lines = fileContent.split('\n');
let lastPluginLine = -1;
for (let i = 0; i < lines.length; i++) {
if (fastlanePluginRegex.test(lines[i])) {
lastPluginLine = i;
}
}
const beforeInsert = lines.slice(0, lastPluginLine + 1);
const afterInsert = lines.slice(lastPluginLine + 1);
insertionContent = [
...beforeInsert,
"gem 'fastlane-plugin-sentry'",
...afterInsert,
].join('\n');
} else if (fastlaneGemMatch) {
// Insert after the fastlane gem
const endOfMatch = fastlaneGemMatch.index + fastlaneGemMatch[0].length;
const nextLineIndex = fileContent.indexOf('\n', endOfMatch);
if (nextLineIndex !== -1) {
insertionPoint = nextLineIndex + 1;
insertionContent =
fileContent.slice(0, insertionPoint) +
"gem 'fastlane-plugin-sentry'\n" +
fileContent.slice(insertionPoint);
} else {
// Add at the end of the file
insertionContent = fileContent + "\ngem 'fastlane-plugin-sentry'\n";
}
} else {
// Add at the end of the file
insertionContent = fileContent + "\ngem 'fastlane-plugin-sentry'\n";
}

fs.writeFileSync(gemfilePath, insertionContent, 'utf8');
return true;
}
2 changes: 1 addition & 1 deletion src/apple/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function getObjcSnippet(dsn: string, enableLogs: boolean): string {
}

export function getFastlaneSnippet(org: string, project: string): string {
return ` sentry_cli(
return ` sentry_debug_files_upload(
org_slug: '${org}',
project_slug: '${project}',
include_sources: true
Expand Down
16 changes: 8 additions & 8 deletions test/apple/fastfile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ end
});

describe('#addSentryToLane', () => {
describe('sentry_cli is not present', () => {
describe('sentry_debug_files_upload is not present', () => {
it('should return original content', () => {
// -- Arrange --
const content = `
Expand All @@ -293,7 +293,7 @@ platform :ios do
lane :test do
puts 'Hello, world!'

sentry_cli(
sentry_debug_files_upload(
org_slug: 'test-org',
project_slug: 'test-project',
include_sources: true
Expand All @@ -304,19 +304,19 @@ end
});
});

describe('sentry_cli is present', () => {
describe('sentry_debug_files_upload is present', () => {
it('should return updated content', () => {
// -- Arrange --
const content = `
platform :ios do
lane :test do
puts 'Hello, world!'

sentry_cli(org_slug: 'test-org', project_slug: 'test-project')
sentry_debug_files_upload(org_slug: 'test-org', project_slug: 'test-project')
end
end
`;
const lane = { index: 34, length: 92, name: 'test' };
const lane = { index: 34, length: 110, name: 'test' };

// -- Act --
const result = exportForTesting.addSentryToLane(
Expand All @@ -333,7 +333,7 @@ platform :ios do
lane :test do
puts 'Hello, world!'

sentry_cli(
sentry_debug_files_upload(
org_slug: 'test-org',
project_slug: 'test-project',
include_sources: true
Expand Down Expand Up @@ -446,7 +446,7 @@ platform :ios do
lane :test do
puts 'Hello, world!'

sentry_cli(
sentry_debug_files_upload(
org_slug: 'test-org',
project_slug: 'test-project',
include_sources: true
Expand Down Expand Up @@ -518,7 +518,7 @@ end
lane :beta do
puts 'Beta lane'

sentry_cli(
sentry_debug_files_upload(
org_slug: 'test-org',
project_slug: 'test-project',
include_sources: true
Expand Down
171 changes: 171 additions & 0 deletions test/apple/gemfile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { addSentryPluginToGemfile, gemFile } from '../../src/apple/gemfile';
import { describe, expect, it } from 'vitest';

describe('gemfile', () => {
describe('#gemFile', () => {
describe('file exists', () => {
it('should return path', () => {
// -- Arrange --
const projectPath = createProjectDir();
const gemfilePath = createGemfile(projectPath, 'gem "fastlane"');

// -- Act --
const result = gemFile(projectPath);

// -- Assert --
expect(result).toBe(gemfilePath);
});
});

describe('file does not exist', () => {
it('should return null', () => {
// -- Arrange --
const projectPath = createProjectDir();
// do not create Gemfile

// -- Act --
const result = gemFile(projectPath);

// -- Assert --
expect(result).toBeNull();
});
});
});

describe('#addSentryPluginToGemfile', () => {
describe('Gemfile not found', () => {
it('should return false', () => {
// -- Arrange --
const projectPath = createProjectDir();
// do not create Gemfile

// -- Act --
const result = addSentryPluginToGemfile(projectPath);

// -- Assert --
expect(result).toBe(false);
});
});

describe('sentry plugin already exists', () => {
it('should return true without modifying Gemfile', () => {
// -- Arrange --
const projectPath = createProjectDir();
const originalContent = `source 'https://rubygems.org'
gem 'fastlane-plugin-sentry'
gem 'fastlane'`;
const gemfilePath = createGemfile(projectPath, originalContent);

// -- Act --
const result = addSentryPluginToGemfile(projectPath);

// -- Assert --
expect(result).toBe(true);
expect(fs.readFileSync(gemfilePath, 'utf8')).toBe(originalContent);
});
});

describe('adds sentry plugin to Gemfile', () => {
describe('after other fastlane plugins', () => {
it('should add after the last fastlane plugin', () => {
// -- Arrange --
const projectPath = createProjectDir();
const originalContent = `source 'https://rubygems.org'
gem 'fastlane-plugin-badge'
gem 'fastlane-plugin-firebase_app_distribution'
gem 'fastlane'`;
const gemfilePath = createGemfile(projectPath, originalContent);

// -- Act --
const result = addSentryPluginToGemfile(projectPath);

// -- Assert --
expect(result).toBe(true);
expect(fs.readFileSync(gemfilePath, 'utf8'))
.toBe(`source 'https://rubygems.org'
gem 'fastlane-plugin-badge'
gem 'fastlane-plugin-firebase_app_distribution'
gem 'fastlane-plugin-sentry'
gem 'fastlane'`);
});
});

describe('after fastlane gem', () => {
it('should add after fastlane gem when no other plugins exist', () => {
// -- Arrange --
const projectPath = createProjectDir();
const originalContent = `source 'https://rubygems.org'
gem 'fastlane'
gem 'cocoapods'`;
const gemfilePath = createGemfile(projectPath, originalContent);

// -- Act --
const result = addSentryPluginToGemfile(projectPath);

// -- Assert --
expect(result).toBe(true);
expect(fs.readFileSync(gemfilePath, 'utf8'))
.toBe(`source 'https://rubygems.org'
gem 'fastlane'
gem 'fastlane-plugin-sentry'
gem 'cocoapods'`);
});
});

describe('at the end of file', () => {
it('should add at the end when no fastlane gems exist', () => {
// -- Arrange --
const projectPath = createProjectDir();
const originalContent = `source 'https://rubygems.org'
gem 'cocoapods'`;
const gemfilePath = createGemfile(projectPath, originalContent);

// -- Act --
const result = addSentryPluginToGemfile(projectPath);

// -- Assert --
expect(result).toBe(true);
expect(fs.readFileSync(gemfilePath, 'utf8'))
.toBe(`source 'https://rubygems.org'
gem 'cocoapods'
gem 'fastlane-plugin-sentry'
`);
});

it('should add at the end when fastlane gem is at the end of file', () => {
// -- Arrange --
const projectPath = createProjectDir();
const originalContent = `source 'https://rubygems.org'
gem 'cocoapods'
gem 'fastlane'`;
const gemfilePath = createGemfile(projectPath, originalContent);

// -- Act --
const result = addSentryPluginToGemfile(projectPath);

// -- Assert --
expect(result).toBe(true);
expect(fs.readFileSync(gemfilePath, 'utf8'))
.toBe(`source 'https://rubygems.org'
gem 'cocoapods'
gem 'fastlane'
gem 'fastlane-plugin-sentry'
`);
});
});
});
});
});

function createProjectDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'test-project'));
}

function createGemfile(projectPath: string, content: string) {
const gemfilePath = path.join(projectPath, 'Gemfile');
fs.writeFileSync(gemfilePath, content);
return gemfilePath;
}
2 changes: 1 addition & 1 deletion test/apple/templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ fi

// -- Assert --
expect(snippet).toBe(
` sentry_cli(
` sentry_debug_files_upload(
org_slug: 'test-org',
project_slug: 'test-project',
include_sources: true
Expand Down
Loading