@expo/fingerprint 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/bin/cli.js +26 -5
- package/build/sourcer/Bare.js +13 -7
- package/build/sourcer/Bare.js.map +1 -1
- package/package.json +16 -9
- package/.eslintignore +0 -1
- package/CHANGELOG.md +0 -55
- package/__mocks__/@expo/spawn-async.ts +0 -30
- package/__mocks__/fs/promises.ts +0 -17
- package/__mocks__/fs.ts +0 -2
- package/__mocks__/resolve-from.ts +0 -24
- package/babel.config.js +0 -6
- package/e2e/__tests__/__snapshots__/managed-test.ts.snap +0 -242
- package/e2e/__tests__/bare-test.ts +0 -72
- package/e2e/__tests__/managed-test.ts +0 -165
- package/e2e/jest.config.js +0 -11
- package/jest.config.js +0 -10
- package/scripts/createFixture.ts +0 -81
- package/src/Dedup.ts +0 -97
- package/src/Fingerprint.ts +0 -60
- package/src/Fingerprint.types.ts +0 -110
- package/src/Options.ts +0 -81
- package/src/Sort.ts +0 -22
- package/src/__tests__/Dedup-test.ts +0 -177
- package/src/__tests__/Fingerprint-test.ts +0 -143
- package/src/__tests__/Sort-test.ts +0 -56
- package/src/hash/Hash.ts +0 -203
- package/src/hash/__tests__/Hash-test.ts +0 -238
- package/src/index.ts +0 -2
- package/src/sourcer/Bare.ts +0 -115
- package/src/sourcer/Expo.ts +0 -223
- package/src/sourcer/ExpoConfigLoader.ts +0 -84
- package/src/sourcer/PatchPackage.ts +0 -18
- package/src/sourcer/Sourcer.ts +0 -58
- package/src/sourcer/Utils.ts +0 -62
- package/src/sourcer/__tests__/Bare-test.ts +0 -88
- package/src/sourcer/__tests__/Expo-test.ts +0 -305
- package/src/sourcer/__tests__/PatchPackage-test.ts +0 -57
- package/src/sourcer/__tests__/Sourcer-test.ts +0 -21
- package/src/sourcer/__tests__/Utils-test.ts +0 -41
- package/src/sourcer/__tests__/__snapshots__/Bare-test.ts.snap +0 -21
- package/src/sourcer/__tests__/__snapshots__/Expo-test.ts.snap +0 -139
- package/src/sourcer/__tests__/fixtures/BareReactNative70Project.json +0 -47
- package/src/sourcer/__tests__/fixtures/ExpoAutolinkingAndroid.json +0 -82
- package/src/sourcer/__tests__/fixtures/ExpoAutolinkingIos.json +0 -114
- package/src/sourcer/__tests__/fixtures/ExpoManaged47Project.json +0 -6
- package/src/sourcer/__tests__/fixtures/PatchPackage.json +0 -4
- package/src/sourcer/__tests__/fixtures/RncliAutoLinking.json +0 -165
- package/src/utils/Path.ts +0 -26
- package/src/utils/Profile.ts +0 -47
- package/src/utils/__tests__/Path-test.ts +0 -36
- package/src/utils/__tests__/Profile-test.ts +0 -11
- package/tsconfig.json +0 -9
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
|
-
import { vol } from 'memfs';
|
|
3
|
-
import pLimit from 'p-limit';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
import { HashSource } from '../../Fingerprint.types';
|
|
7
|
-
import { normalizeOptionsAsync } from '../../Options';
|
|
8
|
-
import {
|
|
9
|
-
createContentsHashResultsAsync,
|
|
10
|
-
createDirHashResultsAsync,
|
|
11
|
-
createFileHashResultsAsync,
|
|
12
|
-
createFingerprintFromSourcesAsync,
|
|
13
|
-
createFingerprintSourceAsync,
|
|
14
|
-
createSourceId,
|
|
15
|
-
} from '../Hash';
|
|
16
|
-
|
|
17
|
-
jest.mock('fs');
|
|
18
|
-
jest.mock('fs/promises');
|
|
19
|
-
|
|
20
|
-
describe(createFingerprintFromSourcesAsync, () => {
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
vol.reset();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('snapshot', async () => {
|
|
26
|
-
const filePath = 'assets/icon.png';
|
|
27
|
-
vol.mkdirSync('/app');
|
|
28
|
-
vol.mkdirSync('/app/assets');
|
|
29
|
-
vol.writeFileSync(path.join('/app', filePath), '{}');
|
|
30
|
-
|
|
31
|
-
const sources: HashSource[] = [
|
|
32
|
-
{ type: 'contents', id: 'foo', contents: 'HelloWorld', reasons: ['foo'] },
|
|
33
|
-
{ type: 'file', filePath, reasons: ['icon'] },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
expect(
|
|
37
|
-
await createFingerprintFromSourcesAsync(sources, '/app', await normalizeOptionsAsync('/app'))
|
|
38
|
-
).toMatchInlineSnapshot(`
|
|
39
|
-
{
|
|
40
|
-
"hash": "ca7d58cd60289daa5cddcf99fcaa1d339bfc2c1a",
|
|
41
|
-
"sources": [
|
|
42
|
-
{
|
|
43
|
-
"contents": "HelloWorld",
|
|
44
|
-
"hash": "db8ac1c259eb89d4a131b253bacfca5f319d54f2",
|
|
45
|
-
"id": "foo",
|
|
46
|
-
"reasons": [
|
|
47
|
-
"foo",
|
|
48
|
-
],
|
|
49
|
-
"type": "contents",
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"filePath": "assets/icon.png",
|
|
53
|
-
"hash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f",
|
|
54
|
-
"reasons": [
|
|
55
|
-
"icon",
|
|
56
|
-
],
|
|
57
|
-
"type": "file",
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
}
|
|
61
|
-
`);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe(createFingerprintSourceAsync, () => {
|
|
66
|
-
it('should merge hash value to original source', async () => {
|
|
67
|
-
const source: HashSource = {
|
|
68
|
-
type: 'contents',
|
|
69
|
-
id: 'foo',
|
|
70
|
-
contents: 'HelloWorld',
|
|
71
|
-
reasons: ['foo'],
|
|
72
|
-
};
|
|
73
|
-
const expectedResult = {
|
|
74
|
-
...source,
|
|
75
|
-
hash: 'db8ac1c259eb89d4a131b253bacfca5f319d54f2',
|
|
76
|
-
};
|
|
77
|
-
expect(
|
|
78
|
-
await createFingerprintSourceAsync(
|
|
79
|
-
source,
|
|
80
|
-
pLimit(1),
|
|
81
|
-
'/app',
|
|
82
|
-
await normalizeOptionsAsync('/app')
|
|
83
|
-
)
|
|
84
|
-
).toEqual(expectedResult);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe(createContentsHashResultsAsync, () => {
|
|
89
|
-
it('should return {id, hex} result', async () => {
|
|
90
|
-
const id = 'foo';
|
|
91
|
-
const contents = '{}';
|
|
92
|
-
const options = await normalizeOptionsAsync('/app');
|
|
93
|
-
const result = await createContentsHashResultsAsync(
|
|
94
|
-
{
|
|
95
|
-
type: 'contents',
|
|
96
|
-
id,
|
|
97
|
-
contents,
|
|
98
|
-
reasons: [id],
|
|
99
|
-
},
|
|
100
|
-
options
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const expectHex = createHash(options.hashAlgorithm).update(contents).digest('hex');
|
|
104
|
-
expect(result.id).toEqual(id);
|
|
105
|
-
expect(result.hex).toEqual(expectHex);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe(createFileHashResultsAsync, () => {
|
|
110
|
-
afterEach(() => {
|
|
111
|
-
vol.reset();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should return {id, hex} result', async () => {
|
|
115
|
-
const filePath = 'assets/icon.png';
|
|
116
|
-
const contents = '{}';
|
|
117
|
-
const limiter = pLimit(1);
|
|
118
|
-
const options = await normalizeOptionsAsync('/app');
|
|
119
|
-
vol.mkdirSync('/app');
|
|
120
|
-
vol.mkdirSync('/app/assets');
|
|
121
|
-
vol.writeFileSync(path.join('/app', filePath), contents);
|
|
122
|
-
|
|
123
|
-
const result = await createFileHashResultsAsync(filePath, limiter, '/app', options);
|
|
124
|
-
|
|
125
|
-
const expectHex = createHash(options.hashAlgorithm).update(contents).digest('hex');
|
|
126
|
-
expect(result?.id).toEqual(filePath);
|
|
127
|
-
expect(result?.hex).toEqual(expectHex);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should ignore file if it is in options.ignorePaths', async () => {
|
|
131
|
-
const filePath = 'app.json';
|
|
132
|
-
const contents = '{}';
|
|
133
|
-
const limiter = pLimit(1);
|
|
134
|
-
const options = await normalizeOptionsAsync('/app');
|
|
135
|
-
options.ignorePaths = ['*.json'];
|
|
136
|
-
vol.mkdirSync('/app');
|
|
137
|
-
vol.writeFileSync(path.join('/app', filePath), contents);
|
|
138
|
-
|
|
139
|
-
const result = await createFileHashResultsAsync(filePath, limiter, '/app', options);
|
|
140
|
-
expect(result).toBe(null);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe(createDirHashResultsAsync, () => {
|
|
145
|
-
afterEach(() => {
|
|
146
|
-
vol.reset();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('should return {id, hex} result', async () => {
|
|
150
|
-
const limiter = pLimit(3);
|
|
151
|
-
const options = await normalizeOptionsAsync('/app');
|
|
152
|
-
const volJSON = {
|
|
153
|
-
'/app/ios/Podfile': '...',
|
|
154
|
-
'/app/eas.json': '{}',
|
|
155
|
-
'/app/app.json': '{}',
|
|
156
|
-
'/app/android/build.gradle': '...',
|
|
157
|
-
};
|
|
158
|
-
vol.fromJSON(volJSON);
|
|
159
|
-
const result = await createDirHashResultsAsync('.', limiter, '/app', options);
|
|
160
|
-
|
|
161
|
-
expect(result?.id).toEqual('.');
|
|
162
|
-
expect(result?.hex).not.toBe('');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should ignore dir if it is in options.ignorePaths', async () => {
|
|
166
|
-
const limiter = pLimit(3);
|
|
167
|
-
const options = await normalizeOptionsAsync('/app');
|
|
168
|
-
options.ignorePaths = ['ios/**/*', 'android/**/*'];
|
|
169
|
-
const volJSON = {
|
|
170
|
-
'/app/ios/Podfile': '...',
|
|
171
|
-
'/app/eas.json': '{}',
|
|
172
|
-
'/app/app.json': '{}',
|
|
173
|
-
'/app/android/build.gradle': '...',
|
|
174
|
-
};
|
|
175
|
-
vol.fromJSON(volJSON);
|
|
176
|
-
|
|
177
|
-
const fingerprint1 = await createDirHashResultsAsync('.', limiter, '/app', options);
|
|
178
|
-
|
|
179
|
-
vol.reset();
|
|
180
|
-
const volJSONIgnoreNativeProjects = {
|
|
181
|
-
'/app/eas.json': '{}',
|
|
182
|
-
'/app/app.json': '{}',
|
|
183
|
-
};
|
|
184
|
-
vol.fromJSON(volJSONIgnoreNativeProjects);
|
|
185
|
-
const fingerprint2 = await createDirHashResultsAsync('.', limiter, '/app', options);
|
|
186
|
-
expect(fingerprint1).toEqual(fingerprint2);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('should return stable result from sorted files', async () => {
|
|
190
|
-
const limiter = pLimit(3);
|
|
191
|
-
const options = await normalizeOptionsAsync('/app');
|
|
192
|
-
const volJSON = {
|
|
193
|
-
'/app/ios/Podfile': '...',
|
|
194
|
-
'/app/eas.json': '{}',
|
|
195
|
-
'/app/app.json': '{}',
|
|
196
|
-
'/app/android/build.gradle': '...',
|
|
197
|
-
};
|
|
198
|
-
vol.fromJSON(volJSON);
|
|
199
|
-
const result = await createDirHashResultsAsync('.', limiter, '/app', options);
|
|
200
|
-
|
|
201
|
-
vol.reset();
|
|
202
|
-
const sortedVolJSON = {
|
|
203
|
-
'/app/app.json': '{}',
|
|
204
|
-
'/app/eas.json': '{}',
|
|
205
|
-
'/app/android/build.gradle': '...',
|
|
206
|
-
'/app/ios/Podfile': '...',
|
|
207
|
-
};
|
|
208
|
-
vol.fromJSON(sortedVolJSON);
|
|
209
|
-
const sortedResult = await createDirHashResultsAsync('.', limiter, '/app', options);
|
|
210
|
-
|
|
211
|
-
expect(result?.id).toEqual(sortedResult?.id);
|
|
212
|
-
expect(result?.hex).toEqual(sortedResult?.hex);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe(createSourceId, () => {
|
|
217
|
-
it(`should use filePath as id for file or dir`, () => {
|
|
218
|
-
const fileSource: HashSource = {
|
|
219
|
-
type: 'file',
|
|
220
|
-
filePath: '/app/app.json',
|
|
221
|
-
reasons: ['expoConfig'],
|
|
222
|
-
};
|
|
223
|
-
expect(createSourceId(fileSource)).toBe('/app/app.json');
|
|
224
|
-
|
|
225
|
-
const dirSource: HashSource = { type: 'dir', filePath: '/app/ios', reasons: ['bareNativeDir'] };
|
|
226
|
-
expect(createSourceId(dirSource)).toBe('/app/ios');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it(`should use given id for contents`, () => {
|
|
230
|
-
const source: HashSource = {
|
|
231
|
-
type: 'contents',
|
|
232
|
-
id: 'foo',
|
|
233
|
-
contents: 'HelloWorld',
|
|
234
|
-
reasons: ['foo'],
|
|
235
|
-
};
|
|
236
|
-
expect(createSourceId(source)).toBe('foo');
|
|
237
|
-
});
|
|
238
|
-
});
|
package/src/index.ts
DELETED
package/src/sourcer/Bare.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import spawnAsync from '@expo/spawn-async';
|
|
2
|
-
import assert from 'assert';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import resolveFrom from 'resolve-from';
|
|
6
|
-
|
|
7
|
-
import { getFileBasedHashSourceAsync } from './Utils';
|
|
8
|
-
import type { HashSource, NormalizedOptions } from '../Fingerprint.types';
|
|
9
|
-
|
|
10
|
-
const debug = require('debug')('expo:fingerprint:sourcer:Bare');
|
|
11
|
-
|
|
12
|
-
export async function getBareAndroidSourcesAsync(
|
|
13
|
-
projectRoot: string,
|
|
14
|
-
options: NormalizedOptions
|
|
15
|
-
): Promise<HashSource[]> {
|
|
16
|
-
if (options.platforms.includes('android')) {
|
|
17
|
-
const result = await getFileBasedHashSourceAsync(projectRoot, 'android', 'bareNativeDir');
|
|
18
|
-
if (result != null) {
|
|
19
|
-
debug(`Adding bare native dir - ${chalk.dim('android')}`);
|
|
20
|
-
return [result];
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function getBareIosSourcesAsync(
|
|
27
|
-
projectRoot: string,
|
|
28
|
-
options: NormalizedOptions
|
|
29
|
-
): Promise<HashSource[]> {
|
|
30
|
-
if (options.platforms.includes('ios')) {
|
|
31
|
-
const result = await getFileBasedHashSourceAsync(projectRoot, 'ios', 'bareNativeDir');
|
|
32
|
-
if (result != null) {
|
|
33
|
-
debug(`Adding bare native dir - ${chalk.dim('ios')}`);
|
|
34
|
-
return [result];
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function getPackageJsonScriptSourcesAsync(
|
|
41
|
-
projectRoot: string,
|
|
42
|
-
options: NormalizedOptions
|
|
43
|
-
) {
|
|
44
|
-
let packageJson;
|
|
45
|
-
try {
|
|
46
|
-
packageJson = require(resolveFrom(path.resolve(projectRoot), './package.json'));
|
|
47
|
-
} catch (e: unknown) {
|
|
48
|
-
debug(`Unable to read package.json from ${path.resolve(projectRoot)}/package.json: ` + e);
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
const results: HashSource[] = [];
|
|
52
|
-
if (packageJson.scripts) {
|
|
53
|
-
debug(`Adding package.json contents - ${chalk.dim('scripts')}`);
|
|
54
|
-
const id = 'packageJson:scripts';
|
|
55
|
-
results.push({
|
|
56
|
-
type: 'contents',
|
|
57
|
-
id,
|
|
58
|
-
contents: JSON.stringify(packageJson.scripts),
|
|
59
|
-
reasons: [id],
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
return results;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function getGitIgnoreSourcesAsync(projectRoot: string, options: NormalizedOptions) {
|
|
66
|
-
const result = await getFileBasedHashSourceAsync(projectRoot, '.gitignore', 'bareGitIgnore');
|
|
67
|
-
if (result != null) {
|
|
68
|
-
debug(`Adding file - ${chalk.dim('.gitignore')}`);
|
|
69
|
-
return [result];
|
|
70
|
-
}
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function getRncliAutolinkingSourcesAsync(
|
|
75
|
-
projectRoot: string,
|
|
76
|
-
options: NormalizedOptions
|
|
77
|
-
): Promise<HashSource[]> {
|
|
78
|
-
try {
|
|
79
|
-
const results: HashSource[] = [];
|
|
80
|
-
const { stdout } = await spawnAsync('npx', ['react-native', 'config'], { cwd: projectRoot });
|
|
81
|
-
const config = JSON.parse(stdout);
|
|
82
|
-
const { root } = config;
|
|
83
|
-
const reasons = ['bareRncliAutolinking'];
|
|
84
|
-
const autolinkingConfig: Record<string, any> = {};
|
|
85
|
-
for (const [depName, depData] of Object.entries<any>(config.dependencies)) {
|
|
86
|
-
stripRncliAutolinkingAbsolutePaths(depData, root);
|
|
87
|
-
const filePath = depData.root;
|
|
88
|
-
debug(`Adding react-native-cli autolinking dir - ${chalk.dim(filePath)}`);
|
|
89
|
-
results.push({ type: 'dir', filePath, reasons });
|
|
90
|
-
|
|
91
|
-
autolinkingConfig[depName] = depData;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
results.push({
|
|
95
|
-
type: 'contents',
|
|
96
|
-
id: 'rncliAutolinkingConfig',
|
|
97
|
-
contents: JSON.stringify(autolinkingConfig),
|
|
98
|
-
reasons,
|
|
99
|
-
});
|
|
100
|
-
return results;
|
|
101
|
-
} catch {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function stripRncliAutolinkingAbsolutePaths(dependency: any, root: string): void {
|
|
107
|
-
assert(dependency.root);
|
|
108
|
-
const dependencyRoot = dependency.root;
|
|
109
|
-
dependency.root = path.relative(root, dependencyRoot);
|
|
110
|
-
for (const platformData of Object.values<any>(dependency.platforms)) {
|
|
111
|
-
for (const [key, value] of Object.entries<any>(platformData)) {
|
|
112
|
-
platformData[key] = value.startsWith?.(dependencyRoot) ? path.relative(root, value) : value;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
package/src/sourcer/Expo.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import spawnAsync from '@expo/spawn-async';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import type { ExpoConfig, ProjectConfig } from 'expo/config';
|
|
4
|
-
import fs from 'fs/promises';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import resolveFrom from 'resolve-from';
|
|
8
|
-
|
|
9
|
-
import { getExpoConfigLoaderPath } from './ExpoConfigLoader';
|
|
10
|
-
import { getFileBasedHashSourceAsync, stringifyJsonSorted } from './Utils';
|
|
11
|
-
import type { HashSource, NormalizedOptions } from '../Fingerprint.types';
|
|
12
|
-
|
|
13
|
-
const debug = require('debug')('expo:fingerprint:sourcer:Expo');
|
|
14
|
-
|
|
15
|
-
export async function getExpoConfigSourcesAsync(
|
|
16
|
-
projectRoot: string,
|
|
17
|
-
options: NormalizedOptions
|
|
18
|
-
): Promise<HashSource[]> {
|
|
19
|
-
if (!resolveFrom.silent(path.resolve(projectRoot), 'expo/config')) {
|
|
20
|
-
return [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const results: HashSource[] = [];
|
|
24
|
-
let config: ProjectConfig;
|
|
25
|
-
let loadedModules: string[] = [];
|
|
26
|
-
const ignoredFile = await createTempIgnoredFileAsync(options);
|
|
27
|
-
try {
|
|
28
|
-
const { stdout } = await spawnAsync(
|
|
29
|
-
'node',
|
|
30
|
-
[getExpoConfigLoaderPath(), path.resolve(projectRoot), ignoredFile],
|
|
31
|
-
{ cwd: __dirname }
|
|
32
|
-
);
|
|
33
|
-
const stdoutJson = JSON.parse(stdout);
|
|
34
|
-
config = stdoutJson.config;
|
|
35
|
-
loadedModules = stdoutJson.loadedModules;
|
|
36
|
-
results.push({
|
|
37
|
-
type: 'contents',
|
|
38
|
-
id: 'expoConfig',
|
|
39
|
-
contents: normalizeExpoConfig(config.exp),
|
|
40
|
-
reasons: ['expoConfig'],
|
|
41
|
-
});
|
|
42
|
-
} catch (e: unknown) {
|
|
43
|
-
if (e instanceof Error) {
|
|
44
|
-
console.warn(`Cannot get Expo config from an Expo project - ${e.message}: `, e.stack);
|
|
45
|
-
}
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// external files in config
|
|
50
|
-
const isAndroid = options.platforms.includes('android');
|
|
51
|
-
const isIos = options.platforms.includes('ios');
|
|
52
|
-
const externalFiles = [
|
|
53
|
-
// icons
|
|
54
|
-
config.exp.icon,
|
|
55
|
-
isAndroid ? config.exp.android?.icon : undefined,
|
|
56
|
-
isIos ? config.exp.ios?.icon : undefined,
|
|
57
|
-
isAndroid ? config.exp.android?.adaptiveIcon?.foregroundImage : undefined,
|
|
58
|
-
isAndroid ? config.exp.android?.adaptiveIcon?.backgroundImage : undefined,
|
|
59
|
-
config.exp.notification?.icon,
|
|
60
|
-
|
|
61
|
-
// splash images
|
|
62
|
-
config.exp.splash?.image,
|
|
63
|
-
isAndroid ? config.exp.android?.splash?.image : undefined,
|
|
64
|
-
isAndroid ? config.exp.android?.splash?.mdpi : undefined,
|
|
65
|
-
isAndroid ? config.exp.android?.splash?.hdpi : undefined,
|
|
66
|
-
isAndroid ? config.exp.android?.splash?.xhdpi : undefined,
|
|
67
|
-
isAndroid ? config.exp.android?.splash?.xxhdpi : undefined,
|
|
68
|
-
isAndroid ? config.exp.android?.splash?.xxxhdpi : undefined,
|
|
69
|
-
isIos ? config.exp.ios?.splash?.image : undefined,
|
|
70
|
-
isIos ? config.exp.ios?.splash?.tabletImage : undefined,
|
|
71
|
-
|
|
72
|
-
// google service files
|
|
73
|
-
isAndroid ? config.exp.android?.googleServicesFile : undefined,
|
|
74
|
-
isIos ? config.exp.ios?.googleServicesFile : undefined,
|
|
75
|
-
].filter(Boolean) as string[];
|
|
76
|
-
const externalFileSources = (
|
|
77
|
-
await Promise.all(
|
|
78
|
-
externalFiles.map(async (file) => {
|
|
79
|
-
const result = await getFileBasedHashSourceAsync(
|
|
80
|
-
projectRoot,
|
|
81
|
-
file,
|
|
82
|
-
'expoConfigExternalFile'
|
|
83
|
-
);
|
|
84
|
-
if (result != null) {
|
|
85
|
-
debug(`Adding config external file - ${chalk.dim(file)}`);
|
|
86
|
-
}
|
|
87
|
-
return result;
|
|
88
|
-
})
|
|
89
|
-
)
|
|
90
|
-
).filter(Boolean) as HashSource[];
|
|
91
|
-
results.push(...externalFileSources);
|
|
92
|
-
|
|
93
|
-
// config plugins
|
|
94
|
-
const configPluginModules: HashSource[] = loadedModules.map((modulePath) => ({
|
|
95
|
-
type: 'file',
|
|
96
|
-
filePath: modulePath,
|
|
97
|
-
reasons: ['expoConfigPlugins'],
|
|
98
|
-
}));
|
|
99
|
-
results.push(...configPluginModules);
|
|
100
|
-
|
|
101
|
-
return results;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function normalizeExpoConfig(config: ExpoConfig): string {
|
|
105
|
-
// Deep clone by JSON.parse/stringify that assumes the config is serializable.
|
|
106
|
-
const normalizedConfig: ExpoConfig = JSON.parse(JSON.stringify(config));
|
|
107
|
-
delete normalizedConfig.runtimeVersion;
|
|
108
|
-
delete normalizedConfig._internal;
|
|
109
|
-
return stringifyJsonSorted(normalizedConfig);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Create a temporary file with ignored paths from options that will be read by the ExpoConfigLoader.
|
|
114
|
-
*/
|
|
115
|
-
async function createTempIgnoredFileAsync(options: NormalizedOptions): Promise<string> {
|
|
116
|
-
await fs.mkdtemp(path.join(os.tmpdir(), 'expo-fingerprint-'));
|
|
117
|
-
const ignoredFile = path.join(os.tmpdir(), '.fingerprintignore');
|
|
118
|
-
await fs.writeFile(ignoredFile, options.ignorePaths.join('\n'));
|
|
119
|
-
return ignoredFile;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export async function getEasBuildSourcesAsync(projectRoot: string, options: NormalizedOptions) {
|
|
123
|
-
const files = ['eas.json', '.easignore'];
|
|
124
|
-
const results = (
|
|
125
|
-
await Promise.all(
|
|
126
|
-
files.map(async (file) => {
|
|
127
|
-
const result = await getFileBasedHashSourceAsync(projectRoot, file, 'easBuild');
|
|
128
|
-
if (result != null) {
|
|
129
|
-
debug(`Adding eas file - ${chalk.dim(file)}`);
|
|
130
|
-
}
|
|
131
|
-
return result;
|
|
132
|
-
})
|
|
133
|
-
)
|
|
134
|
-
).filter(Boolean) as HashSource[];
|
|
135
|
-
return results;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export async function getExpoAutolinkingAndroidSourcesAsync(
|
|
139
|
-
projectRoot: string,
|
|
140
|
-
options: NormalizedOptions
|
|
141
|
-
): Promise<HashSource[]> {
|
|
142
|
-
if (!options.platforms.includes('android')) {
|
|
143
|
-
return [];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
const reasons = ['expoAutolinkingAndroid'];
|
|
148
|
-
const results: HashSource[] = [];
|
|
149
|
-
const { stdout } = await spawnAsync(
|
|
150
|
-
'npx',
|
|
151
|
-
['expo-modules-autolinking', 'resolve', '-p', 'android', '--json'],
|
|
152
|
-
{ cwd: projectRoot }
|
|
153
|
-
);
|
|
154
|
-
const config = sortExpoAutolinkingAndroidConfig(JSON.parse(stdout));
|
|
155
|
-
for (const module of config.modules) {
|
|
156
|
-
for (const project of module.projects) {
|
|
157
|
-
const filePath = path.relative(projectRoot, project.sourceDir);
|
|
158
|
-
project.sourceDir = filePath; // use relative path for the dir
|
|
159
|
-
debug(`Adding expo-modules-autolinking android dir - ${chalk.dim(filePath)}`);
|
|
160
|
-
results.push({ type: 'dir', filePath, reasons });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
results.push({
|
|
164
|
-
type: 'contents',
|
|
165
|
-
id: 'expoAutolinkingConfig:android',
|
|
166
|
-
contents: JSON.stringify(config),
|
|
167
|
-
reasons,
|
|
168
|
-
});
|
|
169
|
-
return results;
|
|
170
|
-
} catch {
|
|
171
|
-
return [];
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export async function getExpoAutolinkingIosSourcesAsync(
|
|
176
|
-
projectRoot: string,
|
|
177
|
-
options: NormalizedOptions
|
|
178
|
-
): Promise<HashSource[]> {
|
|
179
|
-
if (!options.platforms.includes('ios')) {
|
|
180
|
-
return [];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const reasons = ['expoAutolinkingIos'];
|
|
185
|
-
const results: HashSource[] = [];
|
|
186
|
-
const { stdout } = await spawnAsync(
|
|
187
|
-
'npx',
|
|
188
|
-
['expo-modules-autolinking', 'resolve', '-p', 'ios', '--json'],
|
|
189
|
-
{ cwd: projectRoot }
|
|
190
|
-
);
|
|
191
|
-
const config = JSON.parse(stdout);
|
|
192
|
-
for (const module of config.modules) {
|
|
193
|
-
for (const pod of module.pods) {
|
|
194
|
-
const filePath = path.relative(projectRoot, pod.podspecDir);
|
|
195
|
-
pod.podspecDir = filePath; // use relative path for the dir
|
|
196
|
-
debug(`Adding expo-modules-autolinking ios dir - ${chalk.dim(filePath)}`);
|
|
197
|
-
results.push({ type: 'dir', filePath, reasons });
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
results.push({
|
|
201
|
-
type: 'contents',
|
|
202
|
-
id: 'expoAutolinkingConfig:ios',
|
|
203
|
-
contents: JSON.stringify(config),
|
|
204
|
-
reasons,
|
|
205
|
-
});
|
|
206
|
-
return results;
|
|
207
|
-
} catch {
|
|
208
|
-
return [];
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Sort the expo-modules-autolinking android config to make it stable from hashing.
|
|
214
|
-
*/
|
|
215
|
-
export function sortExpoAutolinkingAndroidConfig(config: Record<string, any>): Record<string, any> {
|
|
216
|
-
for (const module of config.modules) {
|
|
217
|
-
// Sort the projects by project.name
|
|
218
|
-
module.projects.sort((a: Record<string, any>, b: Record<string, any>) =>
|
|
219
|
-
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
return config;
|
|
223
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A helper script to load the Expo config and loaded plugins from a project
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import fs from 'fs/promises';
|
|
6
|
-
import module from 'module';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import resolveFrom from 'resolve-from';
|
|
9
|
-
|
|
10
|
-
import { DEFAULT_IGNORE_PATHS } from '../Options';
|
|
11
|
-
import { isIgnoredPath } from '../utils/Path';
|
|
12
|
-
|
|
13
|
-
async function runAsync(programName: string, args: string[] = []) {
|
|
14
|
-
if (args.length < 1) {
|
|
15
|
-
console.log(`Usage: ${programName} <projectRoot> [ignoredFile]`);
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const projectRoot = path.resolve(args[0]);
|
|
20
|
-
const ignoredFile = args[1] ? path.resolve(args[1]) : null;
|
|
21
|
-
|
|
22
|
-
// @ts-expect-error: module internal _cache
|
|
23
|
-
const loadedModulesBefore = new Set(Object.keys(module._cache));
|
|
24
|
-
|
|
25
|
-
const { getConfig } = require(resolveFrom(path.resolve(projectRoot), 'expo/config'));
|
|
26
|
-
const config = await getConfig(projectRoot, { skipSDKVersionRequirement: true });
|
|
27
|
-
// @ts-expect-error: module internal _cache
|
|
28
|
-
const loadedModules = Object.keys(module._cache)
|
|
29
|
-
.filter((modulePath) => !loadedModulesBefore.has(modulePath))
|
|
30
|
-
.map((modulePath) => path.relative(projectRoot, modulePath));
|
|
31
|
-
|
|
32
|
-
const ignoredPaths = await loadIgnoredPathsAsync(ignoredFile);
|
|
33
|
-
const filteredLoadedModules = loadedModules.filter(
|
|
34
|
-
(modulePath) => !isIgnoredPath(modulePath, ignoredPaths)
|
|
35
|
-
);
|
|
36
|
-
console.log(JSON.stringify({ config, loadedModules: filteredLoadedModules }));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// If running from the command line
|
|
40
|
-
if (require.main?.filename === __filename) {
|
|
41
|
-
(async () => {
|
|
42
|
-
const programIndex = process.argv.findIndex((arg) => arg === __filename);
|
|
43
|
-
try {
|
|
44
|
-
await runAsync(process.argv[programIndex], process.argv.slice(programIndex + 1));
|
|
45
|
-
} catch (e) {
|
|
46
|
-
console.error('Uncaught Error', e);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
})();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Load the generated ignored paths file from caller and remove the file after loading
|
|
54
|
-
*/
|
|
55
|
-
async function loadIgnoredPathsAsync(ignoredFile: string | null) {
|
|
56
|
-
if (!ignoredFile) {
|
|
57
|
-
return DEFAULT_IGNORE_PATHS;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const ignorePaths = [];
|
|
61
|
-
try {
|
|
62
|
-
const fingerprintIgnore = await fs.readFile(ignoredFile, 'utf8');
|
|
63
|
-
const fingerprintIgnoreLines = fingerprintIgnore.split('\n');
|
|
64
|
-
for (const line of fingerprintIgnoreLines) {
|
|
65
|
-
const trimmedLine = line.trim();
|
|
66
|
-
if (trimmedLine) {
|
|
67
|
-
ignorePaths.push(trimmedLine);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
} catch {}
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
await fs.rm(ignoredFile);
|
|
74
|
-
} catch {}
|
|
75
|
-
|
|
76
|
-
return ignorePaths;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Get the path to the ExpoConfigLoader file.
|
|
81
|
-
*/
|
|
82
|
-
export function getExpoConfigLoaderPath() {
|
|
83
|
-
return path.join(__dirname, 'ExpoConfigLoader.js');
|
|
84
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
import { getFileBasedHashSourceAsync } from './Utils';
|
|
4
|
-
import type { HashSource, NormalizedOptions } from '../Fingerprint.types';
|
|
5
|
-
|
|
6
|
-
const debug = require('debug')('expo:fingerprint:sourcer:PatchPackage');
|
|
7
|
-
|
|
8
|
-
export async function getPatchPackageSourcesAsync(
|
|
9
|
-
projectRoot: string,
|
|
10
|
-
options: NormalizedOptions
|
|
11
|
-
): Promise<HashSource[]> {
|
|
12
|
-
const result = await getFileBasedHashSourceAsync(projectRoot, 'patches', 'patchPackage');
|
|
13
|
-
if (result != null) {
|
|
14
|
-
debug(`Adding dir - ${chalk.dim('patches')}`);
|
|
15
|
-
return [result];
|
|
16
|
-
}
|
|
17
|
-
return [];
|
|
18
|
-
}
|