@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
package/src/hash/Hash.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { createReadStream } from 'fs';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import minimatch from 'minimatch';
|
|
5
|
+
import pLimit from 'p-limit';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Fingerprint,
|
|
10
|
+
FingerprintSource,
|
|
11
|
+
HashResult,
|
|
12
|
+
HashSource,
|
|
13
|
+
HashSourceContents,
|
|
14
|
+
NormalizedOptions,
|
|
15
|
+
} from '../Fingerprint.types';
|
|
16
|
+
import { profile } from '../utils/Profile';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a `Fingerprint` from `HashSources` array
|
|
20
|
+
*/
|
|
21
|
+
export async function createFingerprintFromSourcesAsync(
|
|
22
|
+
sources: HashSource[],
|
|
23
|
+
projectRoot: string,
|
|
24
|
+
options: NormalizedOptions
|
|
25
|
+
): Promise<Fingerprint> {
|
|
26
|
+
const limiter = pLimit(options.concurrentIoLimit);
|
|
27
|
+
const fingerprintSources = await Promise.all(
|
|
28
|
+
sources.map((source) => createFingerprintSourceAsync(source, limiter, projectRoot, options))
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const hasher = createHash(options.hashAlgorithm);
|
|
32
|
+
for (const source of fingerprintSources) {
|
|
33
|
+
if (source.hash != null) {
|
|
34
|
+
hasher.update(createSourceId(source));
|
|
35
|
+
hasher.update(source.hash);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const hash = hasher.digest('hex');
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
sources: fingerprintSources,
|
|
42
|
+
hash,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a `FingerprintSource` from a `HashSource`
|
|
48
|
+
* This function will get a hash value and merge back to original source
|
|
49
|
+
*/
|
|
50
|
+
export async function createFingerprintSourceAsync(
|
|
51
|
+
source: HashSource,
|
|
52
|
+
limiter: pLimit.Limit,
|
|
53
|
+
projectRoot: string,
|
|
54
|
+
options: NormalizedOptions
|
|
55
|
+
): Promise<FingerprintSource> {
|
|
56
|
+
let result: HashResult | null = null;
|
|
57
|
+
switch (source.type) {
|
|
58
|
+
case 'contents':
|
|
59
|
+
result = await createContentsHashResultsAsync(source, options);
|
|
60
|
+
break;
|
|
61
|
+
case 'file':
|
|
62
|
+
result = await createFileHashResultsAsync(source.filePath, limiter, projectRoot, options);
|
|
63
|
+
break;
|
|
64
|
+
case 'dir':
|
|
65
|
+
result = await profile(
|
|
66
|
+
createDirHashResultsAsync,
|
|
67
|
+
`createDirHashResultsAsync(${source.filePath})`
|
|
68
|
+
)(source.filePath, limiter, projectRoot, options);
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
throw new Error('Unsupported source type');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { ...source, hash: result?.hex ?? null };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a `HashResult` from a file
|
|
79
|
+
*/
|
|
80
|
+
export async function createFileHashResultsAsync(
|
|
81
|
+
filePath: string,
|
|
82
|
+
limiter: pLimit.Limit,
|
|
83
|
+
projectRoot: string,
|
|
84
|
+
options: NormalizedOptions
|
|
85
|
+
): Promise<HashResult> {
|
|
86
|
+
// Backup code for faster hashing
|
|
87
|
+
/*
|
|
88
|
+
return limiter(async () => {
|
|
89
|
+
const hasher = createHash(options.hashAlgorithm);
|
|
90
|
+
|
|
91
|
+
const stat = await fs.stat(filePath);
|
|
92
|
+
hasher.update(`${stat.size}`);
|
|
93
|
+
|
|
94
|
+
const buffer = Buffer.alloc(4096);
|
|
95
|
+
const fd = await fs.open(filePath, 'r');
|
|
96
|
+
await fd.read(buffer, 0, buffer.length, 0);
|
|
97
|
+
await fd.close();
|
|
98
|
+
hasher.update(buffer);
|
|
99
|
+
console.log('stat', filePath, stat.size);
|
|
100
|
+
return { id: path.relative(projectRoot, filePath), hex: hasher.digest('hex') };
|
|
101
|
+
});
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
return limiter(() => {
|
|
105
|
+
return new Promise<HashResult>((resolve, reject) => {
|
|
106
|
+
let resolved = false;
|
|
107
|
+
const hasher = createHash(options.hashAlgorithm);
|
|
108
|
+
const stream = createReadStream(path.join(projectRoot, filePath));
|
|
109
|
+
stream.on('close', () => {
|
|
110
|
+
if (!resolved) {
|
|
111
|
+
const hex = hasher.digest('hex');
|
|
112
|
+
resolve({ id: filePath, hex });
|
|
113
|
+
resolved = true;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
stream.on('error', (e) => {
|
|
117
|
+
reject(e);
|
|
118
|
+
});
|
|
119
|
+
stream.on('data', (chunk) => {
|
|
120
|
+
hasher.update(chunk);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Indicate the given `dirPath` should be excluded by `dirExcludes`
|
|
128
|
+
*/
|
|
129
|
+
function isExcludedDir(dirPath: string, dirExcludes: string[]): boolean {
|
|
130
|
+
for (const exclude of dirExcludes) {
|
|
131
|
+
if (minimatch(dirPath, exclude)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create `HashResult` for a dir.
|
|
140
|
+
* If the dir is excluded, returns null rather than a HashResult
|
|
141
|
+
*/
|
|
142
|
+
export async function createDirHashResultsAsync(
|
|
143
|
+
dirPath: string,
|
|
144
|
+
limiter: pLimit.Limit,
|
|
145
|
+
projectRoot: string,
|
|
146
|
+
options: NormalizedOptions,
|
|
147
|
+
depth: number = 0
|
|
148
|
+
): Promise<HashResult | null> {
|
|
149
|
+
if (isExcludedDir(dirPath, options.dirExcludes)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const dirents = (await fs.readdir(path.join(projectRoot, dirPath), { withFileTypes: true })).sort(
|
|
153
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
154
|
+
);
|
|
155
|
+
const promises: Promise<HashResult | null>[] = [];
|
|
156
|
+
for (const dirent of dirents) {
|
|
157
|
+
if (dirent.isDirectory()) {
|
|
158
|
+
const filePath = path.join(dirPath, dirent.name);
|
|
159
|
+
promises.push(createDirHashResultsAsync(filePath, limiter, projectRoot, options, depth + 1));
|
|
160
|
+
} else if (dirent.isFile()) {
|
|
161
|
+
const filePath = path.join(dirPath, dirent.name);
|
|
162
|
+
promises.push(createFileHashResultsAsync(filePath, limiter, projectRoot, options));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const hasher = createHash(options.hashAlgorithm);
|
|
167
|
+
const results = await Promise.all(promises);
|
|
168
|
+
for (const result of results) {
|
|
169
|
+
if (result != null) {
|
|
170
|
+
hasher.update(result.id);
|
|
171
|
+
hasher.update(result.hex);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const hex = hasher.digest('hex');
|
|
175
|
+
|
|
176
|
+
return { id: dirPath, hex };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create `HashResult` for a `HashSourceContents`
|
|
181
|
+
*/
|
|
182
|
+
export async function createContentsHashResultsAsync(
|
|
183
|
+
source: HashSourceContents,
|
|
184
|
+
options: NormalizedOptions
|
|
185
|
+
): Promise<HashResult> {
|
|
186
|
+
const hex = createHash(options.hashAlgorithm).update(source.contents).digest('hex');
|
|
187
|
+
return { id: source.id, hex };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create id from given source
|
|
192
|
+
*/
|
|
193
|
+
export function createSourceId(source: HashSource): string {
|
|
194
|
+
switch (source.type) {
|
|
195
|
+
case 'contents':
|
|
196
|
+
return source.id;
|
|
197
|
+
case 'file':
|
|
198
|
+
return source.filePath;
|
|
199
|
+
case 'dir':
|
|
200
|
+
return source.filePath;
|
|
201
|
+
default:
|
|
202
|
+
throw new Error('Unsupported source type');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
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 { normalizeOptions } 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
|
+
vol.mkdirSync('/app');
|
|
27
|
+
vol.writeFileSync(path.join('/app', 'app.json'), '{}');
|
|
28
|
+
|
|
29
|
+
const sources: HashSource[] = [
|
|
30
|
+
{ type: 'contents', id: 'foo', contents: 'HelloWorld', reasons: ['foo'] },
|
|
31
|
+
{ type: 'file', filePath: 'app.json', reasons: ['expoConfig'] },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
expect(await createFingerprintFromSourcesAsync(sources, '/app', normalizeOptions()))
|
|
35
|
+
.toMatchInlineSnapshot(`
|
|
36
|
+
Object {
|
|
37
|
+
"hash": "ec7d81780f735d5e289b27cdcc04a6c99d2621dc",
|
|
38
|
+
"sources": Array [
|
|
39
|
+
Object {
|
|
40
|
+
"contents": "HelloWorld",
|
|
41
|
+
"hash": "db8ac1c259eb89d4a131b253bacfca5f319d54f2",
|
|
42
|
+
"id": "foo",
|
|
43
|
+
"reasons": Array [
|
|
44
|
+
"foo",
|
|
45
|
+
],
|
|
46
|
+
"type": "contents",
|
|
47
|
+
},
|
|
48
|
+
Object {
|
|
49
|
+
"filePath": "app.json",
|
|
50
|
+
"hash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f",
|
|
51
|
+
"reasons": Array [
|
|
52
|
+
"expoConfig",
|
|
53
|
+
],
|
|
54
|
+
"type": "file",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
`);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe(createFingerprintSourceAsync, () => {
|
|
63
|
+
it('should merge hash value to original source', async () => {
|
|
64
|
+
const source: HashSource = {
|
|
65
|
+
type: 'contents',
|
|
66
|
+
id: 'foo',
|
|
67
|
+
contents: 'HelloWorld',
|
|
68
|
+
reasons: ['foo'],
|
|
69
|
+
};
|
|
70
|
+
const expectedResult = {
|
|
71
|
+
...source,
|
|
72
|
+
hash: 'db8ac1c259eb89d4a131b253bacfca5f319d54f2',
|
|
73
|
+
};
|
|
74
|
+
expect(
|
|
75
|
+
await createFingerprintSourceAsync(source, pLimit(1), '/app', normalizeOptions())
|
|
76
|
+
).toEqual(expectedResult);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe(createContentsHashResultsAsync, () => {
|
|
81
|
+
it('should return {id, hex} result', async () => {
|
|
82
|
+
const id = 'foo';
|
|
83
|
+
const contents = '{}';
|
|
84
|
+
const options = normalizeOptions();
|
|
85
|
+
const result = await createContentsHashResultsAsync(
|
|
86
|
+
{
|
|
87
|
+
type: 'contents',
|
|
88
|
+
id,
|
|
89
|
+
contents,
|
|
90
|
+
reasons: [id],
|
|
91
|
+
},
|
|
92
|
+
options
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const expectHex = createHash(options.hashAlgorithm).update(contents).digest('hex');
|
|
96
|
+
expect(result.id).toEqual(id);
|
|
97
|
+
expect(result.hex).toEqual(expectHex);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe(createFileHashResultsAsync, () => {
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
vol.reset();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should return {id, hex} result', async () => {
|
|
107
|
+
const filePath = 'app.json';
|
|
108
|
+
const contents = '{}';
|
|
109
|
+
const limiter = pLimit(1);
|
|
110
|
+
const options = normalizeOptions();
|
|
111
|
+
vol.mkdirSync('/app');
|
|
112
|
+
vol.writeFileSync(path.join('/app', filePath), contents);
|
|
113
|
+
|
|
114
|
+
const result = await createFileHashResultsAsync(filePath, limiter, '/app', options);
|
|
115
|
+
|
|
116
|
+
const expectHex = createHash(options.hashAlgorithm).update(contents).digest('hex');
|
|
117
|
+
expect(result.id).toEqual(filePath);
|
|
118
|
+
expect(result.hex).toEqual(expectHex);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe(createDirHashResultsAsync, () => {
|
|
123
|
+
afterEach(() => {
|
|
124
|
+
vol.reset();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should return {id, hex} result', async () => {
|
|
128
|
+
const limiter = pLimit(3);
|
|
129
|
+
const options = normalizeOptions();
|
|
130
|
+
const volJSON = {
|
|
131
|
+
'/app/ios/Podfile': '...',
|
|
132
|
+
'/app/eas.json': '{}',
|
|
133
|
+
'/app/app.json': '{}',
|
|
134
|
+
'/app/android/build.gradle': '...',
|
|
135
|
+
};
|
|
136
|
+
vol.fromJSON(volJSON);
|
|
137
|
+
const result = await createDirHashResultsAsync('.', limiter, '/app', options);
|
|
138
|
+
|
|
139
|
+
expect(result?.id).toEqual('.');
|
|
140
|
+
expect(result?.hex).not.toBe('');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return stable result from sorted files', async () => {
|
|
144
|
+
const limiter = pLimit(3);
|
|
145
|
+
const options = normalizeOptions();
|
|
146
|
+
const volJSON = {
|
|
147
|
+
'/app/ios/Podfile': '...',
|
|
148
|
+
'/app/eas.json': '{}',
|
|
149
|
+
'/app/app.json': '{}',
|
|
150
|
+
'/app/android/build.gradle': '...',
|
|
151
|
+
};
|
|
152
|
+
vol.fromJSON(volJSON);
|
|
153
|
+
const result = await createDirHashResultsAsync('.', limiter, '/app', options);
|
|
154
|
+
|
|
155
|
+
vol.reset();
|
|
156
|
+
const sortedVolJSON = {
|
|
157
|
+
'/app/app.json': '{}',
|
|
158
|
+
'/app/eas.json': '{}',
|
|
159
|
+
'/app/android/build.gradle': '...',
|
|
160
|
+
'/app/ios/Podfile': '...',
|
|
161
|
+
};
|
|
162
|
+
vol.fromJSON(sortedVolJSON);
|
|
163
|
+
const sortedResult = await createDirHashResultsAsync('.', limiter, '/app', options);
|
|
164
|
+
|
|
165
|
+
expect(result?.id).toEqual(sortedResult?.id);
|
|
166
|
+
expect(result?.hex).toEqual(sortedResult?.hex);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe(createSourceId, () => {
|
|
171
|
+
it(`should use filePath as id for file or dir`, () => {
|
|
172
|
+
const fileSource: HashSource = {
|
|
173
|
+
type: 'file',
|
|
174
|
+
filePath: '/app/app.json',
|
|
175
|
+
reasons: ['expoConfig'],
|
|
176
|
+
};
|
|
177
|
+
expect(createSourceId(fileSource)).toBe('/app/app.json');
|
|
178
|
+
|
|
179
|
+
const dirSource: HashSource = { type: 'dir', filePath: '/app/ios', reasons: ['bareNativeDir'] };
|
|
180
|
+
expect(createSourceId(dirSource)).toBe('/app/ios');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it(`should use given id for contents`, () => {
|
|
184
|
+
const source: HashSource = {
|
|
185
|
+
type: 'contents',
|
|
186
|
+
id: 'foo',
|
|
187
|
+
contents: 'HelloWorld',
|
|
188
|
+
reasons: ['foo'],
|
|
189
|
+
};
|
|
190
|
+
expect(createSourceId(source)).toBe('foo');
|
|
191
|
+
});
|
|
192
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import spawnAsync from '@expo/spawn-async';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
import type { HashSource, NormalizedOptions, Platform } from '../Fingerprint.types';
|
|
6
|
+
import { getFileBasedHashSourceAsync } from './Utils';
|
|
7
|
+
|
|
8
|
+
const debug = require('debug')('expo:fingerprint:sourcer:Bare');
|
|
9
|
+
|
|
10
|
+
export async function getBareAndroidSourcesAsync(
|
|
11
|
+
projectRoot: string,
|
|
12
|
+
options: NormalizedOptions
|
|
13
|
+
): Promise<HashSource[]> {
|
|
14
|
+
if (options.platforms.includes('android')) {
|
|
15
|
+
const result = await getFileBasedHashSourceAsync(projectRoot, 'android', 'bareNativeDir');
|
|
16
|
+
if (result != null) {
|
|
17
|
+
debug(`Adding bare native dir - ${chalk.dim('android')}`);
|
|
18
|
+
return [result];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getBareIosSourcesAsync(
|
|
25
|
+
projectRoot: string,
|
|
26
|
+
options: NormalizedOptions
|
|
27
|
+
): Promise<HashSource[]> {
|
|
28
|
+
if (options.platforms.includes('ios')) {
|
|
29
|
+
const result = await getFileBasedHashSourceAsync(projectRoot, 'ios', 'bareNativeDir');
|
|
30
|
+
if (result != null) {
|
|
31
|
+
debug(`Adding bare native dir - ${chalk.dim('ios')}`);
|
|
32
|
+
return [result];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function getPackageJsonScriptSourcesAsync(
|
|
39
|
+
projectRoot: string,
|
|
40
|
+
options: NormalizedOptions
|
|
41
|
+
) {
|
|
42
|
+
let packageJson;
|
|
43
|
+
try {
|
|
44
|
+
packageJson = require(`${projectRoot}/package.json`);
|
|
45
|
+
} catch (e: unknown) {
|
|
46
|
+
debug(`Unable to read package.json from ${projectRoot}/package.json: ` + e);
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const results: HashSource[] = [];
|
|
50
|
+
if (packageJson.scripts) {
|
|
51
|
+
debug(`Adding package.json contents - ${chalk.dim('scripts')}`);
|
|
52
|
+
const id = 'packageJson:scripts';
|
|
53
|
+
results.push({
|
|
54
|
+
type: 'contents',
|
|
55
|
+
id,
|
|
56
|
+
contents: JSON.stringify(packageJson.scripts),
|
|
57
|
+
reasons: [id],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return results;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function getGitIgnoreSourcesAsync(projectRoot: string, options: NormalizedOptions) {
|
|
64
|
+
const result = await getFileBasedHashSourceAsync(projectRoot, '.gitignore', 'bareGitIgnore');
|
|
65
|
+
if (result != null) {
|
|
66
|
+
debug(`Adding file - ${chalk.dim('.gitignore')}`);
|
|
67
|
+
return [result];
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function getRncliAutolinkingSourcesAsync(
|
|
73
|
+
projectRoot: string,
|
|
74
|
+
options: NormalizedOptions
|
|
75
|
+
): Promise<HashSource[]> {
|
|
76
|
+
try {
|
|
77
|
+
const results: HashSource[] = [];
|
|
78
|
+
const { stdout } = await spawnAsync('npx', ['react-native', 'config'], { cwd: projectRoot });
|
|
79
|
+
const config = JSON.parse(stdout);
|
|
80
|
+
const { root } = config;
|
|
81
|
+
const reasons = ['bareRncliAutolinking'];
|
|
82
|
+
for (const depData of Object.values<any>(config.dependencies)) {
|
|
83
|
+
const filePath = path.relative(root, depData.root);
|
|
84
|
+
results.push({ type: 'dir', filePath, reasons });
|
|
85
|
+
debug(`Adding react-native-cli autolinking dir - ${chalk.dim(filePath)}`);
|
|
86
|
+
for (const platform of options.platforms) {
|
|
87
|
+
const platformData = getRncliPlatformData(depData, root, platform);
|
|
88
|
+
if (platformData) {
|
|
89
|
+
results.push({
|
|
90
|
+
type: 'contents',
|
|
91
|
+
id: `rncliAutolinkingConfig:${depData.name}:${platform}`,
|
|
92
|
+
contents: platformData,
|
|
93
|
+
reasons,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return results;
|
|
99
|
+
} catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getRncliPlatformData(dependency: any, root: string, platform: Platform): string {
|
|
105
|
+
const platformData = dependency.platforms[platform];
|
|
106
|
+
if (!platformData) {
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
109
|
+
const json: Record<string, string> = {};
|
|
110
|
+
for (const [key, value] of Object.entries<any>(platformData)) {
|
|
111
|
+
json[key] = value?.startsWith?.(root) ? path.relative(root, value) : value;
|
|
112
|
+
}
|
|
113
|
+
return JSON.stringify(json);
|
|
114
|
+
}
|