@expo/fingerprint 0.4.1 → 0.6.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.
Files changed (53) hide show
  1. package/README.md +60 -6
  2. package/bin/cli.js +26 -5
  3. package/build/sourcer/Bare.js +13 -7
  4. package/build/sourcer/Bare.js.map +1 -1
  5. package/package.json +16 -9
  6. package/.eslintignore +0 -1
  7. package/CHANGELOG.md +0 -55
  8. package/__mocks__/@expo/spawn-async.ts +0 -30
  9. package/__mocks__/fs/promises.ts +0 -17
  10. package/__mocks__/fs.ts +0 -2
  11. package/__mocks__/resolve-from.ts +0 -24
  12. package/babel.config.js +0 -6
  13. package/e2e/__tests__/__snapshots__/managed-test.ts.snap +0 -242
  14. package/e2e/__tests__/bare-test.ts +0 -72
  15. package/e2e/__tests__/managed-test.ts +0 -165
  16. package/e2e/jest.config.js +0 -11
  17. package/jest.config.js +0 -10
  18. package/scripts/createFixture.ts +0 -81
  19. package/src/Dedup.ts +0 -97
  20. package/src/Fingerprint.ts +0 -60
  21. package/src/Fingerprint.types.ts +0 -110
  22. package/src/Options.ts +0 -81
  23. package/src/Sort.ts +0 -22
  24. package/src/__tests__/Dedup-test.ts +0 -177
  25. package/src/__tests__/Fingerprint-test.ts +0 -143
  26. package/src/__tests__/Sort-test.ts +0 -56
  27. package/src/hash/Hash.ts +0 -203
  28. package/src/hash/__tests__/Hash-test.ts +0 -238
  29. package/src/index.ts +0 -2
  30. package/src/sourcer/Bare.ts +0 -115
  31. package/src/sourcer/Expo.ts +0 -223
  32. package/src/sourcer/ExpoConfigLoader.ts +0 -84
  33. package/src/sourcer/PatchPackage.ts +0 -18
  34. package/src/sourcer/Sourcer.ts +0 -58
  35. package/src/sourcer/Utils.ts +0 -62
  36. package/src/sourcer/__tests__/Bare-test.ts +0 -88
  37. package/src/sourcer/__tests__/Expo-test.ts +0 -305
  38. package/src/sourcer/__tests__/PatchPackage-test.ts +0 -57
  39. package/src/sourcer/__tests__/Sourcer-test.ts +0 -21
  40. package/src/sourcer/__tests__/Utils-test.ts +0 -41
  41. package/src/sourcer/__tests__/__snapshots__/Bare-test.ts.snap +0 -21
  42. package/src/sourcer/__tests__/__snapshots__/Expo-test.ts.snap +0 -139
  43. package/src/sourcer/__tests__/fixtures/BareReactNative70Project.json +0 -47
  44. package/src/sourcer/__tests__/fixtures/ExpoAutolinkingAndroid.json +0 -82
  45. package/src/sourcer/__tests__/fixtures/ExpoAutolinkingIos.json +0 -114
  46. package/src/sourcer/__tests__/fixtures/ExpoManaged47Project.json +0 -6
  47. package/src/sourcer/__tests__/fixtures/PatchPackage.json +0 -4
  48. package/src/sourcer/__tests__/fixtures/RncliAutoLinking.json +0 -165
  49. package/src/utils/Path.ts +0 -26
  50. package/src/utils/Profile.ts +0 -47
  51. package/src/utils/__tests__/Path-test.ts +0 -36
  52. package/src/utils/__tests__/Profile-test.ts +0 -11
  53. package/tsconfig.json +0 -9
package/src/Sort.ts DELETED
@@ -1,22 +0,0 @@
1
- import type { HashSource } from './Fingerprint.types';
2
-
3
- export function sortSources<T extends HashSource>(sources: T[]): T[] {
4
- const typeOrder = {
5
- file: 0,
6
- dir: 1,
7
- contents: 2,
8
- };
9
- return sources.sort((a, b) => {
10
- const typeResult = typeOrder[a.type] - typeOrder[b.type];
11
- if (typeResult === 0) {
12
- if (a.type === 'file' && b.type === 'file') {
13
- return a.filePath.localeCompare(b.filePath);
14
- } else if (a.type === 'dir' && b.type === 'dir') {
15
- return a.filePath.localeCompare(b.filePath);
16
- } else if (a.type === 'contents' && b.type === 'contents') {
17
- return a.id.localeCompare(b.id);
18
- }
19
- }
20
- return typeResult;
21
- });
22
- }
@@ -1,177 +0,0 @@
1
- import { dedupSources } from '../Dedup';
2
- import type { HashSource } from '../Fingerprint.types';
3
-
4
- describe(dedupSources, () => {
5
- it('should dedup descendant dir - ancestor coming first', () => {
6
- const sources: HashSource[] = [
7
- {
8
- type: 'dir',
9
- filePath: 'android',
10
- reasons: ['root'],
11
- },
12
- {
13
- type: 'dir',
14
- filePath: 'android/app',
15
- reasons: ['subfolder'],
16
- },
17
- ];
18
-
19
- expect(dedupSources(sources, '/app')).toEqual([
20
- {
21
- type: 'dir',
22
- filePath: 'android',
23
- reasons: ['root', 'subfolder'],
24
- },
25
- ]);
26
- });
27
-
28
- it('should dedup descendant dir - ancestor coming later', () => {
29
- const sources: HashSource[] = [
30
- {
31
- type: 'dir',
32
- filePath: 'android/app',
33
- reasons: ['subfolder'],
34
- },
35
- {
36
- type: 'dir',
37
- filePath: 'android',
38
- reasons: ['root'],
39
- },
40
- ];
41
-
42
- expect(dedupSources(sources, '/app')).toEqual([
43
- {
44
- type: 'dir',
45
- filePath: 'android',
46
- reasons: ['root', 'subfolder'],
47
- },
48
- ]);
49
- });
50
-
51
- it('should dedup descendant file - file coming first', () => {
52
- const sources: HashSource[] = [
53
- {
54
- type: 'file',
55
- filePath: 'android/app/build.gradle',
56
- reasons: ['file'],
57
- },
58
- {
59
- type: 'dir',
60
- filePath: 'android',
61
- reasons: ['root'],
62
- },
63
- ];
64
-
65
- expect(dedupSources(sources, '/app')).toEqual([
66
- {
67
- type: 'dir',
68
- filePath: 'android',
69
- reasons: ['root', 'file'],
70
- },
71
- ]);
72
- });
73
-
74
- it('should dedup descendant file - file coming later', () => {
75
- const sources: HashSource[] = [
76
- {
77
- type: 'dir',
78
- filePath: 'android',
79
- reasons: ['root'],
80
- },
81
- {
82
- type: 'file',
83
- filePath: 'android/app/build.gradle',
84
- reasons: ['file'],
85
- },
86
- ];
87
-
88
- expect(dedupSources(sources, '/app')).toEqual([
89
- {
90
- type: 'dir',
91
- filePath: 'android',
92
- reasons: ['root', 'file'],
93
- },
94
- ]);
95
- });
96
-
97
- it('should dedup slibings dir', () => {
98
- const sources: HashSource[] = [
99
- {
100
- type: 'dir',
101
- filePath: 'node_modules/expo',
102
- reasons: ['expo'],
103
- },
104
- {
105
- type: 'dir',
106
- filePath: 'node_modules/expo-modules-core',
107
- reasons: ['expo-modules-core'],
108
- },
109
- ];
110
-
111
- expect(dedupSources(sources, '/app')).toEqual([
112
- {
113
- type: 'dir',
114
- filePath: 'node_modules/expo',
115
- reasons: ['expo'],
116
- },
117
- {
118
- type: 'dir',
119
- filePath: 'node_modules/expo-modules-core',
120
- reasons: ['expo-modules-core'],
121
- },
122
- ]);
123
- });
124
-
125
- it('should keep unique contents by its id', () => {
126
- const sources: HashSource[] = [
127
- {
128
- type: 'contents',
129
- id: 'foo',
130
- contents: 'foo:1',
131
- reasons: ['foo:1'],
132
- },
133
- {
134
- type: 'dir',
135
- filePath: 'android',
136
- reasons: ['root'],
137
- },
138
- {
139
- type: 'contents',
140
- id: 'foo',
141
- contents: 'foo:2',
142
- reasons: ['foo:2'],
143
- },
144
- ];
145
-
146
- expect(dedupSources(sources, '/app')).toEqual([
147
- {
148
- type: 'contents',
149
- id: 'foo',
150
- contents: 'foo:1',
151
- reasons: ['foo:1', 'foo:2'],
152
- },
153
- {
154
- type: 'dir',
155
- filePath: 'android',
156
- reasons: ['root'],
157
- },
158
- ]);
159
- });
160
-
161
- it('should throw error when a dir is descendant of a file', () => {
162
- const sources: HashSource[] = [
163
- {
164
- type: 'file',
165
- filePath: 'android',
166
- reasons: ['file'],
167
- },
168
- {
169
- type: 'dir',
170
- filePath: 'android/app',
171
- reasons: ['dir'],
172
- },
173
- ];
174
-
175
- expect(() => dedupSources(sources, '/app')).toThrow();
176
- });
177
- });
@@ -1,143 +0,0 @@
1
- import { vol } from 'memfs';
2
-
3
- import { createFingerprintAsync, diffFingerprintChangesAsync } from '../Fingerprint';
4
- import type { Fingerprint } from '../Fingerprint.types';
5
- import { normalizeOptionsAsync } from '../Options';
6
-
7
- jest.mock('fs');
8
- jest.mock('fs/promises');
9
- jest.mock('resolve-from');
10
-
11
- describe(diffFingerprintChangesAsync, () => {
12
- afterEach(() => {
13
- vol.reset();
14
- });
15
-
16
- it('should return empty array when fingerprint matched', async () => {
17
- vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
18
- const fingerprint = await createFingerprintAsync('/app', await normalizeOptionsAsync('/app'));
19
- const diff = await diffFingerprintChangesAsync(
20
- fingerprint,
21
- '/app',
22
- await normalizeOptionsAsync('/app')
23
- );
24
- expect(diff.length).toBe(0);
25
- });
26
-
27
- it('should return diff from new item', async () => {
28
- vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
29
- const fingerprint: Fingerprint = {
30
- sources: [],
31
- hash: '',
32
- };
33
-
34
- const diff = await diffFingerprintChangesAsync(
35
- fingerprint,
36
- '/app',
37
- await normalizeOptionsAsync('/app')
38
- );
39
- expect(diff).toMatchInlineSnapshot(`
40
- [
41
- {
42
- "contents": "{"android":{"adaptiveIcon":{"backgroundColor":"#FFFFFF","foregroundImage":"./assets/adaptive-icon.png"}},"assetBundlePatterns":["**/*"],"icon":"./assets/icon.png","ios":{"supportsTablet":true},"name":"sdk47","orientation":"portrait","platforms":["android","ios","web"],"slug":"sdk47","splash":{"backgroundColor":"#ffffff","image":"./assets/splash.png","resizeMode":"contain"},"updates":{"fallbackToCacheTimeout":0},"userInterfaceStyle":"light","version":"1.0.0","web":{"favicon":"./assets/favicon.png"}}",
43
- "hash": "33b2b95de3b0b474810630e51527a2c0a6e5de9c",
44
- "id": "expoConfig",
45
- "reasons": [
46
- "expoConfig",
47
- ],
48
- "type": "contents",
49
- },
50
- ]
51
- `);
52
- });
53
-
54
- it('should return diff from contents changes', async () => {
55
- vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
56
- const packageJson = JSON.parse(vol.readFileSync('/app/package.json', 'utf8').toString());
57
- jest.doMock('/app/package.json', () => packageJson, { virtual: true });
58
- const fingerprint = await createFingerprintAsync('/app', await normalizeOptionsAsync('/app'));
59
-
60
- // first round for bumping package version which should not cause changes
61
- packageJson.version = '111.111.111';
62
- jest.doMock('/app/package.json', () => packageJson, { virtual: true });
63
- let diff = await diffFingerprintChangesAsync(
64
- fingerprint,
65
- '/app',
66
- await normalizeOptionsAsync('/app')
67
- );
68
- expect(diff.length).toBe(0);
69
-
70
- // second round to update scripts section and it should cause changes
71
- packageJson.scripts ||= {};
72
- packageJson.scripts.postinstall = 'echo "hello"';
73
- jest.doMock('/app/package.json', () => packageJson, { virtual: true });
74
- diff = await diffFingerprintChangesAsync(
75
- fingerprint,
76
- '/app',
77
- await normalizeOptionsAsync('/app')
78
- );
79
- jest.dontMock('/app/package.json');
80
- expect(diff).toMatchInlineSnapshot(`
81
- [
82
- {
83
- "contents": "{"start":"expo start","android":"expo start --android","ios":"expo start --ios","web":"expo start --web","postinstall":"echo \\"hello\\""}",
84
- "hash": "47f4d7bae018eb17440153f09977113501eace30",
85
- "id": "packageJson:scripts",
86
- "reasons": [
87
- "packageJson:scripts",
88
- ],
89
- "type": "contents",
90
- },
91
- ]
92
- `);
93
- });
94
-
95
- it('should return diff from file changes', async () => {
96
- vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
97
- const fingerprint = await createFingerprintAsync('/app', await normalizeOptionsAsync('/app'));
98
- const config = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
99
- config.expo.jsEngine = 'jsc';
100
- vol.writeFileSync('/app/app.json', JSON.stringify(config, null, 2));
101
- const diff = await diffFingerprintChangesAsync(
102
- fingerprint,
103
- '/app',
104
- await normalizeOptionsAsync('/app')
105
- );
106
- expect(diff).toMatchInlineSnapshot(`
107
- [
108
- {
109
- "contents": "{"android":{"adaptiveIcon":{"backgroundColor":"#FFFFFF","foregroundImage":"./assets/adaptive-icon.png"}},"assetBundlePatterns":["**/*"],"icon":"./assets/icon.png","ios":{"supportsTablet":true},"jsEngine":"jsc","name":"sdk47","orientation":"portrait","platforms":["android","ios","web"],"slug":"sdk47","splash":{"backgroundColor":"#ffffff","image":"./assets/splash.png","resizeMode":"contain"},"updates":{"fallbackToCacheTimeout":0},"userInterfaceStyle":"light","version":"1.0.0","web":{"favicon":"./assets/favicon.png"}}",
110
- "hash": "7068a4234e7312c6ac54b776ea4dfad0ac789b2a",
111
- "id": "expoConfig",
112
- "reasons": [
113
- "expoConfig",
114
- ],
115
- "type": "contents",
116
- },
117
- ]
118
- `);
119
- });
120
-
121
- it('should return diff from dir changes', async () => {
122
- vol.fromJSON(require('../sourcer/__tests__/fixtures/BareReactNative70Project.json'));
123
- const fingerprint = await createFingerprintAsync('/app', await normalizeOptionsAsync('/app'));
124
- vol.writeFileSync('/app/ios/README.md', '# Adding new file in ios dir');
125
- const diff = await diffFingerprintChangesAsync(
126
- fingerprint,
127
- '/app',
128
- await normalizeOptionsAsync('/app')
129
- );
130
- expect(diff).toMatchInlineSnapshot(`
131
- [
132
- {
133
- "filePath": "ios",
134
- "hash": "a1d299fe057e87bb79dfd0eb6e33cee98d626aa1",
135
- "reasons": [
136
- "bareNativeDir",
137
- ],
138
- "type": "dir",
139
- },
140
- ]
141
- `);
142
- });
143
- });
@@ -1,56 +0,0 @@
1
- import type { FingerprintSource, HashSource } from '../Fingerprint.types';
2
- import { sortSources } from '../Sort';
3
-
4
- describe(sortSources, () => {
5
- it(`should sort sources by type in 'file > dir > contents' order`, () => {
6
- const sources: HashSource[] = [
7
- { type: 'contents', id: 'foo', contents: 'HelloWorld', reasons: ['foo'] },
8
- { type: 'file', filePath: '/app/app.json', reasons: ['expoConfig'] },
9
- { type: 'dir', filePath: '/app/ios', reasons: ['bareNativeDir'] },
10
- ];
11
-
12
- expect(sortSources(sources)).toEqual([
13
- { type: 'file', filePath: '/app/app.json', reasons: ['expoConfig'] },
14
- { type: 'dir', filePath: '/app/ios', reasons: ['bareNativeDir'] },
15
- { type: 'contents', id: 'foo', contents: 'HelloWorld', reasons: ['foo'] },
16
- ]);
17
- });
18
-
19
- it(`should sort id or filePath when item types are the same`, () => {
20
- const sources: HashSource[] = [
21
- { type: 'file', filePath: '/app/eas.json', reasons: ['easBuild'] },
22
- { type: 'contents', id: 'foo', contents: 'HelloWorld', reasons: ['foo'] },
23
- { type: 'dir', filePath: '/app/ios', reasons: ['bareNativeDir'] },
24
- { type: 'file', filePath: '/app/app.json', reasons: ['expoConfig'] },
25
- { type: 'contents', id: 'bar', contents: 'bartender', reasons: ['bar'] },
26
- { type: 'dir', filePath: '/app/android', reasons: ['bareNativeDir'] },
27
- ];
28
-
29
- expect(sortSources(sources)).toEqual([
30
- { type: 'file', filePath: '/app/app.json', reasons: ['expoConfig'] },
31
- { type: 'file', filePath: '/app/eas.json', reasons: ['easBuild'] },
32
- { type: 'dir', filePath: '/app/android', reasons: ['bareNativeDir'] },
33
- { type: 'dir', filePath: '/app/ios', reasons: ['bareNativeDir'] },
34
- { type: 'contents', id: 'bar', contents: 'bartender', reasons: ['bar'] },
35
- { type: 'contents', id: 'foo', contents: 'HelloWorld', reasons: ['foo'] },
36
- ]);
37
- });
38
-
39
- it(`should support both HashSource and FingerprintSource types`, () => {
40
- const sources: HashSource[] = [
41
- { type: 'contents', id: 'foo', contents: 'HelloWorld', reasons: ['foo'] },
42
- ];
43
- const fingerprintSources: FingerprintSource[] = [
44
- {
45
- type: 'contents',
46
- id: 'foo',
47
- contents: 'HelloWorld',
48
- reasons: ['foo'],
49
- hash: 'bc9faaae1e35d52f3dea9651da12cd36627b8403',
50
- },
51
- ];
52
-
53
- expect(sortSources(sources)).toEqual(sources);
54
- expect(sortSources(fingerprintSources)).toEqual(fingerprintSources);
55
- });
56
- });
package/src/hash/Hash.ts DELETED
@@ -1,203 +0,0 @@
1
- import { createHash } from 'crypto';
2
- import { createReadStream } from 'fs';
3
- import fs from 'fs/promises';
4
- import pLimit from 'p-limit';
5
- import path from 'path';
6
-
7
- import type {
8
- Fingerprint,
9
- FingerprintSource,
10
- HashResult,
11
- HashSource,
12
- HashSourceContents,
13
- NormalizedOptions,
14
- } from '../Fingerprint.types';
15
- import { isIgnoredPath } from '../utils/Path';
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 | null> {
86
- // Backup code for faster hashing
87
- /*
88
- return limiter(async () => {
89
- if (isIgnoredPath(filePath, options.ignorePaths)) {
90
- return null;
91
- }
92
-
93
- const hasher = createHash(options.hashAlgorithm);
94
-
95
- const stat = await fs.stat(filePath);
96
- hasher.update(`${stat.size}`);
97
-
98
- const buffer = Buffer.alloc(4096);
99
- const fd = await fs.open(filePath, 'r');
100
- await fd.read(buffer, 0, buffer.length, 0);
101
- await fd.close();
102
- hasher.update(buffer);
103
- console.log('stat', filePath, stat.size);
104
- return { id: path.relative(projectRoot, filePath), hex: hasher.digest('hex') };
105
- });
106
- */
107
-
108
- return limiter(() => {
109
- return new Promise<HashResult | null>((resolve, reject) => {
110
- if (isIgnoredPath(filePath, options.ignorePaths)) {
111
- return resolve(null);
112
- }
113
-
114
- let resolved = false;
115
- const hasher = createHash(options.hashAlgorithm);
116
- const stream = createReadStream(path.join(projectRoot, filePath));
117
- stream.on('close', () => {
118
- if (!resolved) {
119
- const hex = hasher.digest('hex');
120
- resolve({ id: filePath, hex });
121
- resolved = true;
122
- }
123
- });
124
- stream.on('error', (e) => {
125
- reject(e);
126
- });
127
- stream.on('data', (chunk) => {
128
- hasher.update(chunk);
129
- });
130
- });
131
- });
132
- }
133
-
134
- /**
135
- * Create `HashResult` for a dir.
136
- * If the dir is excluded, returns null rather than a HashResult
137
- */
138
- export async function createDirHashResultsAsync(
139
- dirPath: string,
140
- limiter: pLimit.Limit,
141
- projectRoot: string,
142
- options: NormalizedOptions,
143
- depth: number = 0
144
- ): Promise<HashResult | null> {
145
- if (isIgnoredPath(dirPath, options.ignorePaths)) {
146
- return null;
147
- }
148
- const dirents = (await fs.readdir(path.join(projectRoot, dirPath), { withFileTypes: true })).sort(
149
- (a, b) => a.name.localeCompare(b.name)
150
- );
151
- const promises: Promise<HashResult | null>[] = [];
152
- for (const dirent of dirents) {
153
- if (dirent.isDirectory()) {
154
- const filePath = path.join(dirPath, dirent.name);
155
- promises.push(createDirHashResultsAsync(filePath, limiter, projectRoot, options, depth + 1));
156
- } else if (dirent.isFile()) {
157
- const filePath = path.join(dirPath, dirent.name);
158
- promises.push(createFileHashResultsAsync(filePath, limiter, projectRoot, options));
159
- }
160
- }
161
-
162
- const hasher = createHash(options.hashAlgorithm);
163
- const results = (await Promise.all(promises)).filter(
164
- (result): result is HashResult => result != null
165
- );
166
- if (results.length === 0) {
167
- return null;
168
- }
169
- for (const result of results) {
170
- hasher.update(result.id);
171
- hasher.update(result.hex);
172
- }
173
- const hex = hasher.digest('hex');
174
-
175
- return { id: dirPath, hex };
176
- }
177
-
178
- /**
179
- * Create `HashResult` for a `HashSourceContents`
180
- */
181
- export async function createContentsHashResultsAsync(
182
- source: HashSourceContents,
183
- options: NormalizedOptions
184
- ): Promise<HashResult> {
185
- const hex = createHash(options.hashAlgorithm).update(source.contents).digest('hex');
186
- return { id: source.id, hex };
187
- }
188
-
189
- /**
190
- * Create id from given source
191
- */
192
- export function createSourceId(source: HashSource): string {
193
- switch (source.type) {
194
- case 'contents':
195
- return source.id;
196
- case 'file':
197
- return source.filePath;
198
- case 'dir':
199
- return source.filePath;
200
- default:
201
- throw new Error('Unsupported source type');
202
- }
203
- }