@sentry/wizard
Version:
Sentry wizard helping you to configure your project
155 lines (138 loc) • 4.17 kB
text/typescript
import * as fs from 'fs';
import * as path from 'path';
import * as templates from './templates';
import { askForItemSelection } from '../utils/clack-utils';
// @ts-ignore - clack is ESM and TS complains about that. It works though
import clack from '@clack/prompts';
export function fastFile(projectPath: string): string | null {
const fastlanePath = path.join(projectPath, 'fastlane', 'Fastfile');
return fs.existsSync(fastlanePath) ? fastlanePath : null;
}
function findIOSPlatform(
content: string,
): { index: number; length: number } | null {
const platformRegex = /^ *platform\s+:([^ ]+)[^\n]*\n/gim;
let match = platformRegex.exec(content);
if (!match) {
// No platform found, treat whole file as one platform.
return { index: 0, length: content.length };
}
let index = -1;
while (match) {
if (match[1] === 'ios') {
index = match.index + match[0].length;
break;
}
match = platformRegex.exec(content);
}
if (index === -1) {
return null;
}
//After finding the platform, we need to find the end of the platform block.
//This solution has the assumption that the file is well formed,
//which is not a perfect solution, but it's good enough assumption.
const platformEndRegex = /^end[^\n]*/gim;
match = platformEndRegex.exec(content.slice(index));
if (!match) {
return null;
}
return { index, length: match.index };
}
function findLanes(
content: string,
): { index: number; length: number; name: string }[] | null {
const laneRegex = /^ {2}lane\s+:([^ ]+)[^\n]*\n/gim;
let match = laneRegex.exec(content);
if (!match) {
return null;
}
const lanes: { index: number; length: number; name: string }[] = [];
while (match) {
const laneEnd = /^ {2}end/m.exec(
content.slice(match.index + match[0].length),
);
if (laneEnd === null) {
return null;
}
lanes.push({
index: match.index + match[0].length,
length: laneEnd.index,
name: match[1],
});
match = laneRegex.exec(content);
}
return lanes;
}
function addSentryToLane(
content: string,
lane: { index: number; length: number; name: string },
org: string,
project: string,
): string {
const laneContent = content.slice(lane.index, lane.index + lane.length);
const sentryCLIMatch = /sentry_cli\s*\([^)]+\)/gim.exec(laneContent);
if (sentryCLIMatch) {
// Sentry already added to lane. Update it.
return (
content.slice(0, sentryCLIMatch.index + lane.index) +
templates.getFastlaneSnippet(org, project).trim() +
content.slice(
sentryCLIMatch.index + sentryCLIMatch[0].length + lane.index,
)
);
}
// Sentry not added to lane. Add it.
return (
content.slice(0, lane.index + lane.length) +
'\n' +
templates.getFastlaneSnippet(org, project) +
'\n' +
content.slice(lane.index + lane.length)
);
}
export async function addSentryToFastlane(
projectPath: string,
org: string,
project: string,
): Promise<boolean> {
const fastFilePath = fastFile(projectPath);
if (!fastFilePath) {
return false;
}
const fileContent = fs.readFileSync(fastFilePath, 'utf8');
const platform = findIOSPlatform(fileContent);
if (!platform) {
return false;
}
const platformContent = fileContent.slice(
platform.index,
platform.index + platform.length,
);
const lanes = findLanes(platformContent);
lanes?.forEach((l) => (l.index += platform.index));
if (!lanes || lanes.length === 0) {
clack.log.warn('No suitable lanes in your Fastfile.');
return false;
}
let newFileContent: string | undefined;
if (lanes.length === 1) {
newFileContent = addSentryToLane(fileContent, lanes[0], org, project);
} else {
const laneNames = lanes.map((l) => l.name);
const selectedLane = await askForItemSelection(
laneNames,
'Select lane to add Sentry to:',
);
if (selectedLane === undefined) {
return false;
}
newFileContent = addSentryToLane(
fileContent,
lanes[selectedLane.index],
org,
project,
);
}
fs.writeFileSync(fastFilePath, newFileContent, 'utf8');
return true;
}