Skip to content

Commit bb4d70c

Browse files
fix(modules): Exit gracefully if source map file doesn't exist (#2655)
1 parent 98a6e43 commit bb4d70c

File tree

6 files changed

+154
-41
lines changed

6 files changed

+154
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [changelog](https://github.com/getsentry/sentry-wizard/blob/master/CHANGELOG.md#140)
99
- [diff](https://github.com/getsentry/sentry-wizard/compare/v1.2.17...v1.4.0)
1010
- Android builds without ext config, auto create assets dir for modules ([#2652](https://github.com/getsentry/sentry-react-native/pull/2652))
11+
- Exit gracefully if source map file for collecting modules doesn't exist ([#2655](https://github.com/getsentry/sentry-react-native/pull/2655))
1112

1213
### Dependencies
1314

scripts/collect-modules.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ else
4040
modulesPaths="$MODULES_PATHS"
4141
fi
4242

43-
$nodePath $collectModulesScript $sourceMap $modulesOutput $modulesPaths
43+
$nodePath "$collectModulesScript" "$sourceMap" "$modulesOutput" "$modulesPaths"

src/js/tools/ModulesCollector.ts

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { logger } from '@sentry/utils';
2-
import { existsSync, readFileSync } from 'fs';
2+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
33
import { posix, sep } from 'path';
4+
5+
logger.enable();
6+
47
const { dirname, join, resolve, sep: posixSep } = posix;
58

69
interface Package {
@@ -14,13 +17,17 @@ interface Package {
1417
export default class ModulesCollector {
1518

1619
/** Collect method */
17-
public static collect(sources: string[], modulesPaths: string[]): Record<string, string> {
20+
public static collect(sources: unknown[], modulesPaths: string[]): Record<string, string> {
1821
const normalizedModulesPaths = modulesPaths.map((modulesPath) => resolve(modulesPath.split(sep).join(posixSep)));
1922

2023
const infos: Record<string, string> = {};
2124
const seen: Record<string, true> = {};
2225

23-
sources.forEach((path: string) => {
26+
sources.forEach((path: unknown) => {
27+
if (typeof path !== 'string') {
28+
return;
29+
}
30+
2431
let dir = path; // included source file path
2532
let candidate: Package | null = null;
2633

@@ -60,7 +67,7 @@ export default class ModulesCollector {
6067
version: info.version,
6168
};
6269
} catch (error) {
63-
logger.warn(`Failed to read ${pkgPath}`);
70+
logger.error(`Failed to read ${pkgPath}`);
6471
}
6572

6673
return upDirSearch(); // processed package.json file, continue up search
@@ -72,4 +79,65 @@ export default class ModulesCollector {
7279
return infos;
7380
}
7481

82+
/**
83+
* Runs collection of modules.
84+
*/
85+
public static run({
86+
sourceMapPath,
87+
outputModulesPath,
88+
modulesPaths,
89+
collect,
90+
}: Partial<{
91+
sourceMapPath: string,
92+
outputModulesPath: string,
93+
modulesPaths: string[],
94+
collect: (sources: unknown[], modulesPaths: string[]) => Record<string, string>,
95+
}>): void {
96+
if (!sourceMapPath) {
97+
logger.error('First argument `source-map-path` is missing!');
98+
return;
99+
}
100+
if (!outputModulesPath) {
101+
logger.error('Second argument `modules-output-path` is missing!');
102+
return;
103+
}
104+
if (!modulesPaths || modulesPaths.length === 0) {
105+
logger.error('Third argument `modules-paths` is missing!');
106+
return;
107+
}
108+
109+
logger.info('Reading source map from', sourceMapPath);
110+
logger.info('Saving modules to', outputModulesPath);
111+
logger.info('Resolving modules from paths', outputModulesPath);
112+
113+
if (!existsSync(sourceMapPath)) {
114+
logger.error(`Source map file does not exist at ${sourceMapPath}`);
115+
return;
116+
}
117+
for (const modulesPath of modulesPaths) {
118+
if (!existsSync(modulesPath)) {
119+
logger.error(`Modules path does not exist at ${modulesPath}`);
120+
return;
121+
}
122+
}
123+
124+
const map: { sources?: unknown } = JSON.parse(readFileSync(sourceMapPath, 'utf8'));
125+
if (!map.sources || !Array.isArray(map.sources)) {
126+
logger.error(`Modules not collected. No sources found in the source map (${sourceMapPath})!`);
127+
return;
128+
}
129+
130+
const sources: unknown[] = map.sources;
131+
const modules = collect
132+
? collect(sources, modulesPaths)
133+
: ModulesCollector.collect(sources, modulesPaths);
134+
135+
const outputModulesDirPath = dirname(outputModulesPath);
136+
if (!existsSync(outputModulesDirPath)) {
137+
mkdirSync(outputModulesDirPath, { recursive: true });
138+
}
139+
writeFileSync(outputModulesPath, JSON.stringify(modules, null, 2));
140+
logger.info(`Modules collected and saved to: ${outputModulesPath}`);
141+
}
142+
75143
}

src/js/tools/collectModules.ts

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,12 @@
1-
import { logger } from '@sentry/utils';
2-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3-
import { dirname } from 'path';
4-
import { argv, exit } from 'process';
1+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
2+
import { argv } from 'process';
53

64
import ModulesCollector from './ModulesCollector';
75

8-
const sourceMapPath: string | undefined = argv[2]; // eslint-disable-line @typescript-eslint/no-unsafe-member-access
9-
const outputModulesPath: string | undefined = argv[3]; // eslint-disable-line @typescript-eslint/no-unsafe-member-access
10-
const modulesPaths: string[] = argv[4] // eslint-disable-line @typescript-eslint/no-unsafe-member-access
11-
? argv[4].split(',') // eslint-disable-line @typescript-eslint/no-unsafe-member-access
6+
const sourceMapPath: string | undefined = argv[2];
7+
const outputModulesPath: string | undefined = argv[3];
8+
const modulesPaths: string[] = argv[4]
9+
? argv[4].split(',')
1210
: [];
1311

14-
if (!sourceMapPath) {
15-
exitGracefully('First argument `source-map-path` is missing!');
16-
}
17-
if (!outputModulesPath) {
18-
exitGracefully('Second argument `modules-output-path` is missing!');
19-
}
20-
if (modulesPaths.length === 0) {
21-
exitGracefully('Third argument `modules-paths` is missing!');
22-
}
23-
24-
const map: { sources?: string[] } = JSON.parse(readFileSync(sourceMapPath, 'utf8'));
25-
if (!map.sources) {
26-
exitGracefully(`Modules not collected. No sources found in the source map (${sourceMapPath})!`);
27-
}
28-
29-
const sources: string[] = map.sources;
30-
const modules = ModulesCollector.collect(sources, modulesPaths);
31-
32-
const outputModulesDirPath = dirname(outputModulesPath);
33-
if (!existsSync(outputModulesDirPath)) {
34-
mkdirSync(outputModulesDirPath, { recursive: true });
35-
}
36-
writeFileSync(outputModulesPath, JSON.stringify(modules, null, 2));
37-
38-
function exitGracefully(message: string): never {
39-
logger.error(message);
40-
exit(0);
41-
};
12+
ModulesCollector.run({ sourceMapPath, outputModulesPath, modulesPaths });

test/tools/collectModules.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { existsSync, readFileSync, rmdirSync,unlinkSync } from 'fs';
2+
import { dirname } from 'path';
3+
14
import ModulesCollector from '../../src/js/tools/ModulesCollector';
25

36
describe('collectModules', () => {
@@ -42,4 +45,71 @@ describe('collectModules', () => {
4245
'module-2': 'unknown'
4346
});
4447
});
48+
49+
test('should skip non string source value', () => {
50+
const modules = ModulesCollector.collect(
51+
[1, {}],
52+
[
53+
`${__dirname}/fixtures/root-module/modules`,
54+
],
55+
);
56+
57+
expect(modules).toEqual({});
58+
});
59+
60+
test('should gracefully return if source map does not exist', () => {
61+
const mockCollect = jest.fn().mockReturnValue({});
62+
ModulesCollector.run({
63+
sourceMapPath: 'not-exist',
64+
outputModulesPath: `${__dirname}/fixtures/mock.json`,
65+
modulesPaths: [
66+
`${__dirname}/fixtures/root-module/modules`,
67+
],
68+
collect: mockCollect,
69+
});
70+
71+
expect(mockCollect).not.toHaveBeenCalled();
72+
});
73+
74+
test('should gracefully return if one of the modules paths does not exist', () => {
75+
const mockCollect = jest.fn().mockReturnValue({});
76+
ModulesCollector.run({
77+
sourceMapPath: `${__dirname}/fixtures/mock.map`,
78+
outputModulesPath: `${__dirname}/fixtures/mock.json`,
79+
modulesPaths: [
80+
`${__dirname}/fixtures/root-module/modules`,
81+
'not-exist',
82+
],
83+
collect: mockCollect,
84+
});
85+
86+
expect(mockCollect).not.toHaveBeenCalled();
87+
});
88+
89+
describe('write output json', () => {
90+
const outputModulesPath = `${__dirname}/assets/output.json`;
91+
92+
const cleanUp = () => {
93+
if (existsSync(outputModulesPath)) {
94+
unlinkSync(outputModulesPath);
95+
rmdirSync(dirname(outputModulesPath));
96+
}
97+
};
98+
99+
beforeEach(cleanUp);
100+
afterEach(cleanUp);
101+
102+
test('should write output to file', () => {
103+
ModulesCollector.run({
104+
sourceMapPath: `${__dirname}/fixtures/mock.map`,
105+
outputModulesPath,
106+
modulesPaths: [
107+
`${__dirname}/fixtures/root-module/modules`,
108+
],
109+
});
110+
111+
expect(existsSync(outputModulesPath)).toEqual(true);
112+
expect(readFileSync(outputModulesPath, 'utf8')).toEqual('{}');
113+
});
114+
});
45115
});

test/tools/fixtures/mock.map

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"sources": []
3+
}

0 commit comments

Comments
 (0)