@expo/fingerprint 0.0.1
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/.eslintignore +1 -0
- package/LICENSE +22 -0
- package/README.md +118 -0
- package/__mocks__/fs/promises.ts +2 -0
- package/__mocks__/fs.ts +2 -0
- package/babel.config.js +6 -0
- package/bin/cli.js +27 -0
- package/build/Dedup.d.ts +9 -0
- package/build/Dedup.js +83 -0
- package/build/Dedup.js.map +1 -0
- package/build/Fingerprint.d.ts +13 -0
- package/build/Fingerprint.js +42 -0
- package/build/Fingerprint.js.map +1 -0
- package/build/Fingerprint.types.d.ts +78 -0
- package/build/Fingerprint.types.js +4 -0
- package/build/Fingerprint.types.js.map +1 -0
- package/build/Options.d.ts +2 -0
- package/build/Options.js +23 -0
- package/build/Options.js.map +1 -0
- package/build/Sort.d.ts +2 -0
- package/build/Sort.js +27 -0
- package/build/Sort.js.map +1 -0
- package/build/hash/Hash.d.ts +28 -0
- package/build/hash/Hash.js +166 -0
- package/build/hash/Hash.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +22 -0
- package/build/index.js.map +1 -0
- package/build/sourcer/Bare.d.ts +6 -0
- package/build/sourcer/Bare.js +107 -0
- package/build/sourcer/Bare.js.map +1 -0
- package/build/sourcer/Expo.d.ts +5 -0
- package/build/sourcer/Expo.js +175 -0
- package/build/sourcer/Expo.js.map +1 -0
- package/build/sourcer/PatchPackage.d.ts +2 -0
- package/build/sourcer/PatchPackage.js +19 -0
- package/build/sourcer/PatchPackage.js.map +1 -0
- package/build/sourcer/Sourcer.d.ts +2 -0
- package/build/sourcer/Sourcer.js +42 -0
- package/build/sourcer/Sourcer.js.map +1 -0
- package/build/sourcer/Utils.d.ts +2 -0
- package/build/sourcer/Utils.js +25 -0
- package/build/sourcer/Utils.js.map +1 -0
- package/build/utils/Profile.d.ts +8 -0
- package/build/utils/Profile.js +43 -0
- package/build/utils/Profile.js.map +1 -0
- package/e2e/__tests__/__snapshots__/managed-test.ts.snap +200 -0
- package/e2e/__tests__/bare-test.ts +66 -0
- package/e2e/__tests__/managed-test.ts +162 -0
- package/e2e/jest.config.js +10 -0
- package/jest.config.js +10 -0
- package/package.json +70 -0
- package/scripts/createFixture.ts +81 -0
- package/src/Dedup.ts +97 -0
- package/src/Fingerprint.ts +51 -0
- package/src/Fingerprint.types.ts +98 -0
- package/src/Options.ts +18 -0
- package/src/Sort.ts +22 -0
- package/src/__tests__/Dedup-test.ts +177 -0
- package/src/__tests__/Fingerprint-test.ts +116 -0
- package/src/__tests__/Sort-test.ts +56 -0
- package/src/hash/Hash.ts +204 -0
- package/src/hash/__tests__/Hash-test.ts +192 -0
- package/src/index.ts +3 -0
- package/src/sourcer/Bare.ts +114 -0
- package/src/sourcer/Expo.ts +216 -0
- package/src/sourcer/PatchPackage.ts +18 -0
- package/src/sourcer/Sourcer.ts +58 -0
- package/src/sourcer/Utils.ts +23 -0
- package/src/sourcer/__tests__/Bare-test.ts +59 -0
- package/src/sourcer/__tests__/Expo-test.ts +265 -0
- package/src/sourcer/__tests__/PatchPackage-test.ts +54 -0
- package/src/sourcer/__tests__/Sourcer-test.ts +14 -0
- package/src/sourcer/__tests__/__snapshots__/Bare-test.ts.snap +29 -0
- package/src/sourcer/__tests__/__snapshots__/Expo-test.ts.snap +139 -0
- package/src/sourcer/__tests__/fixtures/BareReactNative70Project.json +47 -0
- package/src/sourcer/__tests__/fixtures/ExpoAutolinkingAndroid.json +82 -0
- package/src/sourcer/__tests__/fixtures/ExpoAutolinkingIos.json +114 -0
- package/src/sourcer/__tests__/fixtures/ExpoManaged47Project.json +6 -0
- package/src/sourcer/__tests__/fixtures/PatchPackage.json +4 -0
- package/src/sourcer/__tests__/fixtures/RncliAutoLinking.json +165 -0
- package/src/utils/Profile.ts +47 -0
- package/src/utils/__tests__/Profile-test.ts +11 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import spawnAsync from '@expo/spawn-async';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { getConfig, type ExpoConfig } from 'expo/config';
|
|
5
|
+
import findUp from 'find-up';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import resolveFrom from 'resolve-from';
|
|
8
|
+
|
|
9
|
+
import type { HashSource, NormalizedOptions } from '../Fingerprint.types';
|
|
10
|
+
import { getFileBasedHashSourceAsync } from './Utils';
|
|
11
|
+
|
|
12
|
+
const debug = require('debug')('expo:fingerprint:sourcer:Expo');
|
|
13
|
+
|
|
14
|
+
export async function getExpoConfigSourcesAsync(
|
|
15
|
+
projectRoot: string,
|
|
16
|
+
options: NormalizedOptions
|
|
17
|
+
): Promise<HashSource[]> {
|
|
18
|
+
let config;
|
|
19
|
+
try {
|
|
20
|
+
config = await getConfig(projectRoot, { skipSDKVersionRequirement: true });
|
|
21
|
+
} catch (e: unknown) {
|
|
22
|
+
debug('Cannot get Expo config: ' + e);
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const results: HashSource[] = [];
|
|
27
|
+
|
|
28
|
+
// app config files
|
|
29
|
+
const configFiles = ['app.config.ts', 'app.config.js', 'app.config.json', 'app.json'];
|
|
30
|
+
const configFileSources = (
|
|
31
|
+
await Promise.all(
|
|
32
|
+
configFiles.map(async (file) => {
|
|
33
|
+
const result = await getFileBasedHashSourceAsync(projectRoot, file, 'expoConfig');
|
|
34
|
+
if (result != null) {
|
|
35
|
+
debug(`Adding config file - ${chalk.dim(file)}`);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
).filter(Boolean) as HashSource[];
|
|
41
|
+
results.push(...configFileSources);
|
|
42
|
+
|
|
43
|
+
// external files in config
|
|
44
|
+
const isAndroid = options.platforms.includes('android');
|
|
45
|
+
const isIos = options.platforms.includes('ios');
|
|
46
|
+
const externalFiles = [
|
|
47
|
+
// icons
|
|
48
|
+
config.exp.icon,
|
|
49
|
+
isAndroid ? config.exp.android?.icon : undefined,
|
|
50
|
+
isIos ? config.exp.ios?.icon : undefined,
|
|
51
|
+
isAndroid ? config.exp.android?.adaptiveIcon?.foregroundImage : undefined,
|
|
52
|
+
isAndroid ? config.exp.android?.adaptiveIcon?.backgroundImage : undefined,
|
|
53
|
+
config.exp.notification?.icon,
|
|
54
|
+
|
|
55
|
+
// splash images
|
|
56
|
+
config.exp.splash?.image,
|
|
57
|
+
isAndroid ? config.exp.android?.splash?.image : undefined,
|
|
58
|
+
isAndroid ? config.exp.android?.splash?.mdpi : undefined,
|
|
59
|
+
isAndroid ? config.exp.android?.splash?.hdpi : undefined,
|
|
60
|
+
isAndroid ? config.exp.android?.splash?.xhdpi : undefined,
|
|
61
|
+
isAndroid ? config.exp.android?.splash?.xxhdpi : undefined,
|
|
62
|
+
isAndroid ? config.exp.android?.splash?.xxxhdpi : undefined,
|
|
63
|
+
isIos ? config.exp.ios?.splash?.image : undefined,
|
|
64
|
+
isIos ? config.exp.ios?.splash?.tabletImage : undefined,
|
|
65
|
+
|
|
66
|
+
// google service files
|
|
67
|
+
isAndroid ? config.exp.android?.googleServicesFile : undefined,
|
|
68
|
+
isIos ? config.exp.ios?.googleServicesFile : undefined,
|
|
69
|
+
].filter(Boolean) as string[];
|
|
70
|
+
const externalFileSources = (
|
|
71
|
+
await Promise.all(
|
|
72
|
+
externalFiles.map(async (file) => {
|
|
73
|
+
const result = await getFileBasedHashSourceAsync(
|
|
74
|
+
projectRoot,
|
|
75
|
+
file,
|
|
76
|
+
'expoConfigExternalFile'
|
|
77
|
+
);
|
|
78
|
+
if (result != null) {
|
|
79
|
+
debug(`Adding config external file - ${chalk.dim(file)}`);
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
})
|
|
83
|
+
)
|
|
84
|
+
).filter(Boolean) as HashSource[];
|
|
85
|
+
results.push(...externalFileSources);
|
|
86
|
+
|
|
87
|
+
// config plugins
|
|
88
|
+
const configPluginSources = getConfigPluginSourcesAsync(projectRoot, config.exp.plugins);
|
|
89
|
+
results.push(...configPluginSources);
|
|
90
|
+
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findUpPluginRoot(entryFile: string): string {
|
|
95
|
+
const entryRoot = path.dirname(entryFile);
|
|
96
|
+
const packageJson = findUp.sync('package.json', { cwd: path.dirname(entryFile) });
|
|
97
|
+
assert(packageJson, `No package.json found for module "${entryRoot}"`);
|
|
98
|
+
return path.dirname(packageJson);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getConfigPluginSourcesAsync(
|
|
102
|
+
projectRoot: string,
|
|
103
|
+
plugins: ExpoConfig['plugins']
|
|
104
|
+
): HashSource[] {
|
|
105
|
+
if (plugins == null) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const reasons = ['expoConfigPlugins'];
|
|
110
|
+
const nullableResults: (HashSource | null)[] = plugins.map((plugin) => {
|
|
111
|
+
const pluginPackageName = Array.isArray(plugin) ? plugin[0] : plugin;
|
|
112
|
+
if (typeof pluginPackageName === 'string') {
|
|
113
|
+
const pluginPackageEntryFile = resolveFrom.silent(projectRoot, pluginPackageName);
|
|
114
|
+
const pluginPackageRoot = pluginPackageEntryFile
|
|
115
|
+
? findUpPluginRoot(pluginPackageEntryFile)
|
|
116
|
+
: null;
|
|
117
|
+
if (pluginPackageRoot) {
|
|
118
|
+
debug(`Adding config-plugin root - ${chalk.dim(pluginPackageRoot)}`);
|
|
119
|
+
return { type: 'dir', filePath: path.relative(projectRoot, pluginPackageRoot), reasons };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
});
|
|
124
|
+
const results = nullableResults.filter(Boolean) as HashSource[];
|
|
125
|
+
return results;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function getEasBuildSourcesAsync(projectRoot: string, options: NormalizedOptions) {
|
|
129
|
+
const files = ['eas.json', '.easignore'];
|
|
130
|
+
const results = (
|
|
131
|
+
await Promise.all(
|
|
132
|
+
files.map(async (file) => {
|
|
133
|
+
const result = await getFileBasedHashSourceAsync(projectRoot, file, 'easBuild');
|
|
134
|
+
if (result != null) {
|
|
135
|
+
debug(`Adding eas file - ${chalk.dim(file)}`);
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
})
|
|
139
|
+
)
|
|
140
|
+
).filter(Boolean) as HashSource[];
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function getExpoAutolinkingAndroidSourcesAsync(
|
|
145
|
+
projectRoot: string,
|
|
146
|
+
options: NormalizedOptions
|
|
147
|
+
): Promise<HashSource[]> {
|
|
148
|
+
if (!options.platforms.includes('android')) {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const reasons = ['expoAutolinkingAndroid'];
|
|
154
|
+
const results: HashSource[] = [];
|
|
155
|
+
const { stdout } = await spawnAsync(
|
|
156
|
+
'npx',
|
|
157
|
+
['expo-modules-autolinking', 'resolve', '-p', 'android', '--json'],
|
|
158
|
+
{ cwd: projectRoot }
|
|
159
|
+
);
|
|
160
|
+
const config = JSON.parse(stdout);
|
|
161
|
+
for (const module of config.modules) {
|
|
162
|
+
for (const project of module.projects) {
|
|
163
|
+
const filePath = path.relative(projectRoot, project.sourceDir);
|
|
164
|
+
project.sourceDir = filePath; // use relative path for the dir
|
|
165
|
+
debug(`Adding expo-modules-autolinking android dir - ${chalk.dim(filePath)}`);
|
|
166
|
+
results.push({ type: 'dir', filePath, reasons });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
results.push({
|
|
170
|
+
type: 'contents',
|
|
171
|
+
id: 'expoAutolinkingConfig:android',
|
|
172
|
+
contents: JSON.stringify(config),
|
|
173
|
+
reasons,
|
|
174
|
+
});
|
|
175
|
+
return results;
|
|
176
|
+
} catch {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function getExpoAutolinkingIosSourcesAsync(
|
|
182
|
+
projectRoot: string,
|
|
183
|
+
options: NormalizedOptions
|
|
184
|
+
): Promise<HashSource[]> {
|
|
185
|
+
if (!options.platforms.includes('ios')) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const reasons = ['expoAutolinkingIos'];
|
|
191
|
+
const results: HashSource[] = [];
|
|
192
|
+
const { stdout } = await spawnAsync(
|
|
193
|
+
'npx',
|
|
194
|
+
['expo-modules-autolinking', 'resolve', '-p', 'ios', '--json'],
|
|
195
|
+
{ cwd: projectRoot }
|
|
196
|
+
);
|
|
197
|
+
const config = JSON.parse(stdout);
|
|
198
|
+
for (const module of config.modules) {
|
|
199
|
+
for (const pod of module.pods) {
|
|
200
|
+
const filePath = path.relative(projectRoot, pod.podspecDir);
|
|
201
|
+
pod.podspecDir = filePath; // use relative path for the dir
|
|
202
|
+
debug(`Adding expo-modules-autolinking ios dir - ${chalk.dim(filePath)}`);
|
|
203
|
+
results.push({ type: 'dir', filePath, reasons });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
results.push({
|
|
207
|
+
type: 'contents',
|
|
208
|
+
id: 'expoAutolinkingConfig:ios',
|
|
209
|
+
contents: JSON.stringify(config),
|
|
210
|
+
reasons,
|
|
211
|
+
});
|
|
212
|
+
return results;
|
|
213
|
+
} catch {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
import type { HashSource, NormalizedOptions } from '../Fingerprint.types';
|
|
4
|
+
import { getFileBasedHashSourceAsync } from './Utils';
|
|
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
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
import type { HashSource, NormalizedOptions } from '../Fingerprint.types';
|
|
4
|
+
import { profile } from '../utils/Profile';
|
|
5
|
+
import {
|
|
6
|
+
getBareAndroidSourcesAsync,
|
|
7
|
+
getBareIosSourcesAsync,
|
|
8
|
+
getPackageJsonScriptSourcesAsync,
|
|
9
|
+
getGitIgnoreSourcesAsync,
|
|
10
|
+
getRncliAutolinkingSourcesAsync,
|
|
11
|
+
} from './Bare';
|
|
12
|
+
import {
|
|
13
|
+
getEasBuildSourcesAsync,
|
|
14
|
+
getExpoAutolinkingAndroidSourcesAsync,
|
|
15
|
+
getExpoAutolinkingIosSourcesAsync,
|
|
16
|
+
getExpoConfigSourcesAsync,
|
|
17
|
+
} from './Expo';
|
|
18
|
+
import { getPatchPackageSourcesAsync } from './PatchPackage';
|
|
19
|
+
|
|
20
|
+
const debug = require('debug')('expo:fingerprint:sourcer:Sourcer');
|
|
21
|
+
|
|
22
|
+
export async function getHashSourcesAsync(
|
|
23
|
+
projectRoot: string,
|
|
24
|
+
options: NormalizedOptions
|
|
25
|
+
): Promise<HashSource[]> {
|
|
26
|
+
const results = await Promise.all([
|
|
27
|
+
// expo
|
|
28
|
+
profile(getExpoAutolinkingAndroidSourcesAsync)(projectRoot, options),
|
|
29
|
+
profile(getExpoAutolinkingIosSourcesAsync)(projectRoot, options),
|
|
30
|
+
profile(getExpoConfigSourcesAsync)(projectRoot, options),
|
|
31
|
+
profile(getEasBuildSourcesAsync)(projectRoot, options),
|
|
32
|
+
|
|
33
|
+
// bare managed files
|
|
34
|
+
profile(getGitIgnoreSourcesAsync)(projectRoot, options),
|
|
35
|
+
profile(getPackageJsonScriptSourcesAsync)(projectRoot, options),
|
|
36
|
+
|
|
37
|
+
// bare native files
|
|
38
|
+
profile(getBareAndroidSourcesAsync)(projectRoot, options),
|
|
39
|
+
profile(getBareIosSourcesAsync)(projectRoot, options),
|
|
40
|
+
|
|
41
|
+
// rn-cli autolinking
|
|
42
|
+
profile(getRncliAutolinkingSourcesAsync)(projectRoot, options),
|
|
43
|
+
|
|
44
|
+
// patch-package
|
|
45
|
+
profile(getPatchPackageSourcesAsync)(projectRoot, options),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
// extra sources
|
|
49
|
+
if (options.extraSources) {
|
|
50
|
+
for (const source of options.extraSources) {
|
|
51
|
+
debug(`Adding extra source - ${chalk.dim(JSON.stringify(source))}`);
|
|
52
|
+
}
|
|
53
|
+
results.push(options.extraSources);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// flatten results
|
|
57
|
+
return ([] as HashSource[]).concat(...results);
|
|
58
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import type { HashSource } from '../Fingerprint.types';
|
|
5
|
+
|
|
6
|
+
export async function getFileBasedHashSourceAsync(
|
|
7
|
+
projectRoot: string,
|
|
8
|
+
filePath: string,
|
|
9
|
+
reason: string
|
|
10
|
+
): Promise<HashSource | null> {
|
|
11
|
+
let result: HashSource | null = null;
|
|
12
|
+
try {
|
|
13
|
+
const stat = await fs.stat(path.join(projectRoot, filePath));
|
|
14
|
+
result = {
|
|
15
|
+
type: stat.isDirectory() ? 'dir' : 'file',
|
|
16
|
+
filePath,
|
|
17
|
+
reasons: [reason],
|
|
18
|
+
};
|
|
19
|
+
} catch {
|
|
20
|
+
result = null;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import spawnAsync from '@expo/spawn-async';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { vol } from 'memfs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import { normalizeOptions } from '../../Options';
|
|
7
|
+
import {
|
|
8
|
+
getBareAndroidSourcesAsync,
|
|
9
|
+
getBareIosSourcesAsync,
|
|
10
|
+
getRncliAutolinkingSourcesAsync,
|
|
11
|
+
} from '../Bare';
|
|
12
|
+
|
|
13
|
+
jest.mock('@expo/spawn-async');
|
|
14
|
+
jest.mock('fs/promises');
|
|
15
|
+
jest.mock('/app/package.json', () => ({}), { virtual: true });
|
|
16
|
+
|
|
17
|
+
describe('getBareSourcesAsync', () => {
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
vol.reset();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should contain android and ios folders in bare react-native project', async () => {
|
|
23
|
+
vol.fromJSON(require('./fixtures/BareReactNative70Project.json'));
|
|
24
|
+
let sources = await getBareAndroidSourcesAsync('/app', normalizeOptions());
|
|
25
|
+
expect(sources).toContainEqual(expect.objectContaining({ filePath: 'android', type: 'dir' }));
|
|
26
|
+
|
|
27
|
+
sources = await getBareIosSourcesAsync('/app', normalizeOptions());
|
|
28
|
+
expect(sources).toContainEqual(expect.objectContaining({ filePath: 'ios', type: 'dir' }));
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe(getRncliAutolinkingSourcesAsync, () => {
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vol.reset();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should contain rn-cli autolinking projects', async () => {
|
|
38
|
+
const mockSpawnAsync = spawnAsync as jest.MockedFunction<typeof spawnAsync>;
|
|
39
|
+
const fixture = fs.readFileSync(
|
|
40
|
+
path.join(__dirname, 'fixtures', 'RncliAutoLinking.json'),
|
|
41
|
+
'utf8'
|
|
42
|
+
);
|
|
43
|
+
mockSpawnAsync.mockResolvedValue({
|
|
44
|
+
stdout: fixture,
|
|
45
|
+
stderr: '',
|
|
46
|
+
status: 0,
|
|
47
|
+
signal: null,
|
|
48
|
+
output: [fixture, ''],
|
|
49
|
+
});
|
|
50
|
+
const sources = await getRncliAutolinkingSourcesAsync('/app', normalizeOptions());
|
|
51
|
+
expect(sources).toContainEqual(
|
|
52
|
+
expect.objectContaining({
|
|
53
|
+
type: 'dir',
|
|
54
|
+
filePath: 'node_modules/react-native-reanimated',
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
expect(sources).toMatchSnapshot();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import spawnAsync from '@expo/spawn-async';
|
|
2
|
+
import findUp from 'find-up';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { vol, fs as volFS } from 'memfs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import resolveFrom from 'resolve-from';
|
|
7
|
+
|
|
8
|
+
import { normalizeOptions } from '../../Options';
|
|
9
|
+
import {
|
|
10
|
+
getEasBuildSourcesAsync,
|
|
11
|
+
getExpoAutolinkingAndroidSourcesAsync,
|
|
12
|
+
getExpoAutolinkingIosSourcesAsync,
|
|
13
|
+
getExpoConfigSourcesAsync,
|
|
14
|
+
} from '../Expo';
|
|
15
|
+
|
|
16
|
+
jest.mock('@expo/spawn-async');
|
|
17
|
+
jest.mock('find-up');
|
|
18
|
+
jest.mock('fs/promises');
|
|
19
|
+
jest.mock('resolve-from');
|
|
20
|
+
jest.mock('/app/package.json', () => {}, { virtual: true });
|
|
21
|
+
|
|
22
|
+
describe(getEasBuildSourcesAsync, () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vol.reset();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should contains `eas.json` file', async () => {
|
|
28
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
29
|
+
vol.writeFileSync(
|
|
30
|
+
'/app/eas.json',
|
|
31
|
+
`
|
|
32
|
+
{
|
|
33
|
+
"cli": {
|
|
34
|
+
"version": ">= 2.6.0"
|
|
35
|
+
},
|
|
36
|
+
"build": {
|
|
37
|
+
"development": {
|
|
38
|
+
"distribution": "internal",
|
|
39
|
+
"android": {
|
|
40
|
+
"gradleCommand": ":app:assembleDebug"
|
|
41
|
+
},
|
|
42
|
+
"ios": {
|
|
43
|
+
"buildConfiguration": "Debug"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"preview": {
|
|
47
|
+
"distribution": "internal"
|
|
48
|
+
},
|
|
49
|
+
"production": {}
|
|
50
|
+
},
|
|
51
|
+
"submit": {
|
|
52
|
+
"production": {}
|
|
53
|
+
}
|
|
54
|
+
}`
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const sources = await getEasBuildSourcesAsync('/app', normalizeOptions());
|
|
58
|
+
expect(sources).toContainEqual(
|
|
59
|
+
expect.objectContaining({
|
|
60
|
+
type: 'file',
|
|
61
|
+
filePath: 'eas.json',
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('getExpoAutolinkingSourcesAsync', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
const mockSpawnAsync = spawnAsync as jest.MockedFunction<typeof spawnAsync>;
|
|
70
|
+
const fixtureAndroid = fs.readFileSync(
|
|
71
|
+
path.join(__dirname, 'fixtures', 'ExpoAutolinkingAndroid.json'),
|
|
72
|
+
'utf8'
|
|
73
|
+
);
|
|
74
|
+
const fixtureIos = fs.readFileSync(
|
|
75
|
+
path.join(__dirname, 'fixtures', 'ExpoAutolinkingIos.json'),
|
|
76
|
+
'utf8'
|
|
77
|
+
);
|
|
78
|
+
mockSpawnAsync.mockResolvedValueOnce({
|
|
79
|
+
stdout: fixtureAndroid,
|
|
80
|
+
stderr: '',
|
|
81
|
+
status: 0,
|
|
82
|
+
signal: null,
|
|
83
|
+
output: [fixtureAndroid, ''],
|
|
84
|
+
});
|
|
85
|
+
mockSpawnAsync.mockResolvedValueOnce({
|
|
86
|
+
stdout: fixtureIos,
|
|
87
|
+
stderr: '',
|
|
88
|
+
status: 0,
|
|
89
|
+
signal: null,
|
|
90
|
+
output: [fixtureIos, ''],
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
afterEach(() => {
|
|
95
|
+
vol.reset();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should contain expo autolinking projects', async () => {
|
|
99
|
+
let sources = await getExpoAutolinkingAndroidSourcesAsync('/app', normalizeOptions());
|
|
100
|
+
expect(sources).toContainEqual(
|
|
101
|
+
expect.objectContaining({
|
|
102
|
+
type: 'dir',
|
|
103
|
+
filePath: 'node_modules/expo-modules-core/android',
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
expect(sources).toMatchSnapshot();
|
|
107
|
+
|
|
108
|
+
sources = await getExpoAutolinkingIosSourcesAsync('/app', normalizeOptions());
|
|
109
|
+
expect(sources).toContainEqual(
|
|
110
|
+
expect.objectContaining({ type: 'dir', filePath: 'node_modules/expo-modules-core' })
|
|
111
|
+
);
|
|
112
|
+
expect(sources).toMatchSnapshot();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should not containt absolute path in contents', async () => {
|
|
116
|
+
let sources = await getExpoAutolinkingAndroidSourcesAsync('/app', normalizeOptions());
|
|
117
|
+
for (const source of sources) {
|
|
118
|
+
if (source.type === 'contents') {
|
|
119
|
+
expect(source.contents.indexOf('/app/')).toBe(-1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
sources = await getExpoAutolinkingIosSourcesAsync('/app', normalizeOptions());
|
|
124
|
+
for (const source of sources) {
|
|
125
|
+
if (source.type === 'contents') {
|
|
126
|
+
expect(source.contents.indexOf('/app/')).toBe(-1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe(getExpoConfigSourcesAsync, () => {
|
|
133
|
+
beforeAll(() => {
|
|
134
|
+
jest.doMock('fs', () => volFS);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
afterEach(() => {
|
|
138
|
+
vol.reset();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should contain app.json', async () => {
|
|
142
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
143
|
+
const sources = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
144
|
+
expect(sources).toContainEqual(
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
type: 'file',
|
|
147
|
+
filePath: 'app.json',
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should contain external icon file in app.json', async () => {
|
|
153
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
154
|
+
vol.mkdirSync('/app/assets');
|
|
155
|
+
vol.writeFileSync('/app/assets/icon.png', 'PNG data');
|
|
156
|
+
const sources = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
157
|
+
expect(sources).toContainEqual(
|
|
158
|
+
expect.objectContaining({
|
|
159
|
+
type: 'file',
|
|
160
|
+
filePath: './assets/icon.png',
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe(`getExpoConfigSourcesAsync - config-plugins`, () => {
|
|
167
|
+
let baseAppJson: { expo: any };
|
|
168
|
+
|
|
169
|
+
function setupThirdPartyPlugin() {
|
|
170
|
+
vol.mkdirSync('/app/node_modules/third-party', { recursive: true });
|
|
171
|
+
|
|
172
|
+
// package.json
|
|
173
|
+
vol.writeFileSync('/app/node_modules/third-party/package.json', '{}');
|
|
174
|
+
const mockFindUpSync = findUp.sync as jest.MockedFunction<typeof findUp.sync>;
|
|
175
|
+
mockFindUpSync.mockReturnValue('/app/node_modules/third-party/package.json');
|
|
176
|
+
|
|
177
|
+
// entry file
|
|
178
|
+
const withNoopPlugin = (config: any) => config;
|
|
179
|
+
jest.mock('/app/node_modules/third-party/index.js', () => ({ default: withNoopPlugin }), {
|
|
180
|
+
virtual: true,
|
|
181
|
+
});
|
|
182
|
+
const mockResolveFrom = resolveFrom.silent as jest.MockedFunction<typeof resolveFrom.silent>;
|
|
183
|
+
mockResolveFrom.mockReturnValue('/app/node_modules/third-party/index.js');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
beforeEach(() => {
|
|
187
|
+
jest.doMock('fs', () => volFS);
|
|
188
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
189
|
+
baseAppJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
afterEach(() => {
|
|
193
|
+
vol.reset();
|
|
194
|
+
jest.resetAllMocks();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should contain external config-plugin dir', async () => {
|
|
198
|
+
setupThirdPartyPlugin();
|
|
199
|
+
|
|
200
|
+
vol.writeFileSync(
|
|
201
|
+
'/app/app.json',
|
|
202
|
+
JSON.stringify({
|
|
203
|
+
...baseAppJson,
|
|
204
|
+
expo: {
|
|
205
|
+
...baseAppJson.expo,
|
|
206
|
+
plugins: ['third-party'],
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
const sources = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
211
|
+
expect(sources).toContainEqual(
|
|
212
|
+
expect.objectContaining({
|
|
213
|
+
type: 'dir',
|
|
214
|
+
filePath: 'node_modules/third-party',
|
|
215
|
+
})
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should contain external config-plugin dir from plugin with parameters', async () => {
|
|
220
|
+
setupThirdPartyPlugin();
|
|
221
|
+
|
|
222
|
+
vol.writeFileSync(
|
|
223
|
+
'/app/app.json',
|
|
224
|
+
JSON.stringify({
|
|
225
|
+
...baseAppJson,
|
|
226
|
+
expo: {
|
|
227
|
+
...baseAppJson.expo,
|
|
228
|
+
plugins: [['third-party', { parameter: 'foo' }]],
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
const sources = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
233
|
+
expect(sources).toContainEqual(
|
|
234
|
+
expect.objectContaining({
|
|
235
|
+
type: 'dir',
|
|
236
|
+
filePath: 'node_modules/third-party',
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should not contain external config-plugin dir from raw function plugins', async () => {
|
|
242
|
+
vol.writeFileSync(
|
|
243
|
+
'/app/app.config.js',
|
|
244
|
+
`\
|
|
245
|
+
export default ({ config }) => {
|
|
246
|
+
return config;
|
|
247
|
+
};`
|
|
248
|
+
);
|
|
249
|
+
const sources = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
250
|
+
|
|
251
|
+
vol.writeFileSync(
|
|
252
|
+
'/app/app.config.js',
|
|
253
|
+
`\
|
|
254
|
+
export default ({ config }) => {
|
|
255
|
+
config.plugins ||= [];
|
|
256
|
+
const withNoopPlugin = (config: any) => config;
|
|
257
|
+
config.plugins.push(withNoopPlugin);
|
|
258
|
+
return config;
|
|
259
|
+
};`
|
|
260
|
+
);
|
|
261
|
+
const sources2 = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
262
|
+
|
|
263
|
+
expect(sources).toEqual(sources2);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { vol } from 'memfs';
|
|
2
|
+
|
|
3
|
+
import { normalizeOptions } from '../../Options';
|
|
4
|
+
import { getPatchPackageSourcesAsync } from '../PatchPackage';
|
|
5
|
+
import { getHashSourcesAsync } from '../Sourcer';
|
|
6
|
+
|
|
7
|
+
jest.mock('fs/promises');
|
|
8
|
+
jest.mock('/app/package.json', () => ({}), { virtual: true });
|
|
9
|
+
|
|
10
|
+
describe(getPatchPackageSourcesAsync, () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vol.reset();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should contain patch-packages `patches` dir', async () => {
|
|
16
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
17
|
+
vol.fromJSON(require('./fixtures/PatchPackage.json'));
|
|
18
|
+
|
|
19
|
+
const sources = await getPatchPackageSourcesAsync('/app', normalizeOptions());
|
|
20
|
+
expect(sources).toContainEqual(
|
|
21
|
+
expect.objectContaining({
|
|
22
|
+
type: 'dir',
|
|
23
|
+
filePath: 'patches',
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('patch-package postinstall', () => {
|
|
30
|
+
it('should contain `package.json` scripts block for lifecycle patches', async () => {
|
|
31
|
+
const scriptsBlock = {
|
|
32
|
+
postinstall: 'npx patch-package',
|
|
33
|
+
};
|
|
34
|
+
jest.doMock(
|
|
35
|
+
'/app/package.json',
|
|
36
|
+
() => ({
|
|
37
|
+
name: 'app',
|
|
38
|
+
private: true,
|
|
39
|
+
scripts: scriptsBlock,
|
|
40
|
+
}),
|
|
41
|
+
{ virtual: true }
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const sources = await getHashSourcesAsync('/app', normalizeOptions());
|
|
45
|
+
expect(sources).toContainEqual(
|
|
46
|
+
expect.objectContaining({
|
|
47
|
+
type: 'contents',
|
|
48
|
+
id: 'packageJson:scripts',
|
|
49
|
+
contents: JSON.stringify(scriptsBlock),
|
|
50
|
+
reasons: ['packageJson:scripts'],
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
});
|