@expo/fingerprint 0.1.0 → 0.3.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/CHANGELOG.md +20 -0
- package/README.md +70 -0
- package/__mocks__/@expo/spawn-async.ts +30 -0
- package/__mocks__/fs/promises.ts +15 -0
- package/build/Fingerprint.js +1 -1
- package/build/Fingerprint.js.map +1 -1
- package/build/Fingerprint.types.d.ts +12 -1
- package/build/Fingerprint.types.js.map +1 -1
- package/build/Options.d.ts +3 -1
- package/build/Options.js +63 -9
- package/build/Options.js.map +1 -1
- package/build/hash/Hash.d.ts +1 -1
- package/build/hash/Hash.js +15 -18
- package/build/hash/Hash.js.map +1 -1
- package/build/sourcer/Expo.js +42 -44
- package/build/sourcer/Expo.js.map +1 -1
- package/build/sourcer/ExpoConfigLoader.d.ts +7 -0
- package/build/sourcer/ExpoConfigLoader.js +80 -0
- package/build/sourcer/ExpoConfigLoader.js.map +1 -0
- package/build/sourcer/Utils.d.ts +4 -0
- package/build/sourcer/Utils.js +38 -1
- package/build/sourcer/Utils.js.map +1 -1
- package/build/utils/Path.d.ts +5 -0
- package/build/utils/Path.js +26 -0
- package/build/utils/Path.js.map +1 -0
- package/e2e/__tests__/__snapshots__/managed-test.ts.snap +3 -2
- package/e2e/__tests__/bare-test.ts +7 -0
- package/e2e/__tests__/managed-test.ts +13 -3
- package/package.json +3 -3
- package/src/Fingerprint.ts +2 -2
- package/src/Fingerprint.types.ts +13 -1
- package/src/Options.ts +70 -7
- package/src/__tests__/Fingerprint-test.ts +43 -17
- package/src/hash/Hash.ts +20 -21
- package/src/hash/__tests__/Hash-test.ts +62 -16
- package/src/sourcer/Expo.ts +48 -55
- package/src/sourcer/ExpoConfigLoader.ts +84 -0
- package/src/sourcer/Utils.ts +39 -0
- package/src/sourcer/__tests__/Bare-test.ts +11 -5
- package/src/sourcer/__tests__/Expo-test.ts +88 -109
- package/src/sourcer/__tests__/PatchPackage-test.ts +6 -3
- package/src/sourcer/__tests__/Sourcer-test.ts +9 -2
- package/src/sourcer/__tests__/Utils-test.ts +41 -0
- package/src/utils/Path.ts +26 -0
- package/src/utils/__tests__/Path-test.ts +36 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import spawnAsync from '@expo/spawn-async';
|
|
2
|
-
import
|
|
2
|
+
import { getConfig } from 'expo/config';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { vol, fs as volFS } from 'memfs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import resolveFrom from 'resolve-from';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { HashSourceContents } from '../../Fingerprint.types';
|
|
9
|
+
import { normalizeOptionsAsync } from '../../Options';
|
|
9
10
|
import {
|
|
10
11
|
getEasBuildSourcesAsync,
|
|
11
12
|
getExpoAutolinkingAndroidSourcesAsync,
|
|
@@ -55,7 +56,7 @@ describe(getEasBuildSourcesAsync, () => {
|
|
|
55
56
|
}`
|
|
56
57
|
);
|
|
57
58
|
|
|
58
|
-
const sources = await getEasBuildSourcesAsync('/app',
|
|
59
|
+
const sources = await getEasBuildSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
59
60
|
expect(sources).toContainEqual(
|
|
60
61
|
expect.objectContaining({
|
|
61
62
|
type: 'file',
|
|
@@ -97,7 +98,10 @@ describe('getExpoAutolinkingSourcesAsync', () => {
|
|
|
97
98
|
});
|
|
98
99
|
|
|
99
100
|
it('should contain expo autolinking projects', async () => {
|
|
100
|
-
let sources = await getExpoAutolinkingAndroidSourcesAsync(
|
|
101
|
+
let sources = await getExpoAutolinkingAndroidSourcesAsync(
|
|
102
|
+
'/app',
|
|
103
|
+
await normalizeOptionsAsync('/app')
|
|
104
|
+
);
|
|
101
105
|
expect(sources).toContainEqual(
|
|
102
106
|
expect.objectContaining({
|
|
103
107
|
type: 'dir',
|
|
@@ -106,7 +110,7 @@ describe('getExpoAutolinkingSourcesAsync', () => {
|
|
|
106
110
|
);
|
|
107
111
|
expect(sources).toMatchSnapshot();
|
|
108
112
|
|
|
109
|
-
sources = await getExpoAutolinkingIosSourcesAsync('/app',
|
|
113
|
+
sources = await getExpoAutolinkingIosSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
110
114
|
expect(sources).toContainEqual(
|
|
111
115
|
expect.objectContaining({ type: 'dir', filePath: 'node_modules/expo-modules-core' })
|
|
112
116
|
);
|
|
@@ -114,14 +118,17 @@ describe('getExpoAutolinkingSourcesAsync', () => {
|
|
|
114
118
|
});
|
|
115
119
|
|
|
116
120
|
it('should not containt absolute path in contents', async () => {
|
|
117
|
-
let sources = await getExpoAutolinkingAndroidSourcesAsync(
|
|
121
|
+
let sources = await getExpoAutolinkingAndroidSourcesAsync(
|
|
122
|
+
'/app',
|
|
123
|
+
await normalizeOptionsAsync('/app')
|
|
124
|
+
);
|
|
118
125
|
for (const source of sources) {
|
|
119
126
|
if (source.type === 'contents') {
|
|
120
127
|
expect(source.contents.indexOf('/app/')).toBe(-1);
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
sources = await getExpoAutolinkingIosSourcesAsync('/app',
|
|
131
|
+
sources = await getExpoAutolinkingIosSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
125
132
|
for (const source of sources) {
|
|
126
133
|
if (source.type === 'contents') {
|
|
127
134
|
expect(source.contents.indexOf('/app/')).toBe(-1);
|
|
@@ -141,32 +148,73 @@ describe(getExpoConfigSourcesAsync, () => {
|
|
|
141
148
|
|
|
142
149
|
it('should return empty array when expo package is not installed', async () => {
|
|
143
150
|
vol.fromJSON(require('./fixtures/BareReactNative70Project.json'));
|
|
144
|
-
const mockedResolveFrom = resolveFrom as jest.MockedFunction<typeof resolveFrom>;
|
|
151
|
+
const mockedResolveFrom = resolveFrom.silent as jest.MockedFunction<typeof resolveFrom.silent>;
|
|
145
152
|
mockedResolveFrom.mockImplementationOnce((fromDirectory: string, moduleId: string) => {
|
|
146
|
-
const actualResolver = jest.requireActual('resolve-from');
|
|
153
|
+
const actualResolver = jest.requireActual('resolve-from').silent;
|
|
147
154
|
// To fake the case as no expo installed, trying to resolve as **nonexist/expo/config** module
|
|
148
155
|
return actualResolver(fromDirectory, 'nonexist/expo/config');
|
|
149
156
|
});
|
|
150
|
-
const sources = await getExpoConfigSourcesAsync('/app',
|
|
157
|
+
const sources = await getExpoConfigSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
151
158
|
expect(sources.length).toBe(0);
|
|
152
159
|
});
|
|
153
160
|
|
|
154
|
-
it('should contain
|
|
161
|
+
it('should contain expo config', async () => {
|
|
155
162
|
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
})
|
|
163
|
+
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
|
|
164
|
+
const sources = await getExpoConfigSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
165
|
+
const expoConfigSource = sources.find<HashSourceContents>(
|
|
166
|
+
(source): source is HashSourceContents =>
|
|
167
|
+
source.type === 'contents' && source.id === 'expoConfig'
|
|
162
168
|
);
|
|
169
|
+
const expoConfig = JSON.parse(expoConfigSource?.contents?.toString() ?? 'null');
|
|
170
|
+
expect(expoConfig).not.toBeNull();
|
|
171
|
+
expect(expoConfig.name).toEqual(appJson.expo.name);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should not contain runtimeVersion in expo config', async () => {
|
|
175
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
176
|
+
vol.writeFileSync(
|
|
177
|
+
'/app/app.config.js',
|
|
178
|
+
`\
|
|
179
|
+
export default ({ config }) => {
|
|
180
|
+
config.runtimeVersion = '1.0.0';
|
|
181
|
+
return config;
|
|
182
|
+
};`
|
|
183
|
+
);
|
|
184
|
+
const sources = await getExpoConfigSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
185
|
+
const expoConfigSource = sources.find<HashSourceContents>(
|
|
186
|
+
(source): source is HashSourceContents =>
|
|
187
|
+
source.type === 'contents' && source.id === 'expoConfig'
|
|
188
|
+
);
|
|
189
|
+
const expoConfig = JSON.parse(expoConfigSource?.contents?.toString() ?? 'null');
|
|
190
|
+
expect(expoConfig).not.toBeNull();
|
|
191
|
+
expect(expoConfig.runtimeVersion).toBeUndefined();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should keep expo config contents in deterministic order', async () => {
|
|
195
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
196
|
+
const sources = await getExpoConfigSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
197
|
+
|
|
198
|
+
const appJsonContents = vol.readFileSync('/app/app.json', 'utf8').toString();
|
|
199
|
+
const appJson = JSON.parse(appJsonContents);
|
|
200
|
+
const { name } = appJson.expo;
|
|
201
|
+
// Re-insert name to change the object order
|
|
202
|
+
delete appJson.expo.name;
|
|
203
|
+
appJson.expo.name = name;
|
|
204
|
+
const newAppJsonContents = JSON.stringify(appJson);
|
|
205
|
+
expect(newAppJsonContents).not.toEqual(appJsonContents);
|
|
206
|
+
vol.writeFileSync('/app/app.json', newAppJsonContents);
|
|
207
|
+
|
|
208
|
+
// Even new app.json contents changed its order, the source contents should be the same.
|
|
209
|
+
const sources2 = await getExpoConfigSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
210
|
+
expect(sources).toEqual(sources2);
|
|
163
211
|
});
|
|
164
212
|
|
|
165
213
|
it('should contain external icon file in app.json', async () => {
|
|
166
214
|
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
167
215
|
vol.mkdirSync('/app/assets');
|
|
168
216
|
vol.writeFileSync('/app/assets/icon.png', 'PNG data');
|
|
169
|
-
const sources = await getExpoConfigSourcesAsync('/app',
|
|
217
|
+
const sources = await getExpoConfigSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
170
218
|
expect(sources).toContainEqual(
|
|
171
219
|
expect.objectContaining({
|
|
172
220
|
type: 'file',
|
|
@@ -174,108 +222,39 @@ describe(getExpoConfigSourcesAsync, () => {
|
|
|
174
222
|
})
|
|
175
223
|
);
|
|
176
224
|
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe(`getExpoConfigSourcesAsync - config-plugins`, () => {
|
|
180
|
-
let baseAppJson: { expo: any };
|
|
181
|
-
|
|
182
|
-
function setupThirdPartyPlugin() {
|
|
183
|
-
vol.mkdirSync('/app/node_modules/third-party', { recursive: true });
|
|
184
225
|
|
|
185
|
-
|
|
186
|
-
vol.writeFileSync('/app/node_modules/third-party/package.json', '{}');
|
|
187
|
-
const mockFindUpSync = findUp.sync as jest.MockedFunction<typeof findUp.sync>;
|
|
188
|
-
mockFindUpSync.mockReturnValue('/app/node_modules/third-party/package.json');
|
|
189
|
-
|
|
190
|
-
// entry file
|
|
191
|
-
const withNoopPlugin = (config: any) => config;
|
|
192
|
-
jest.mock('/app/node_modules/third-party/index.js', () => ({ default: withNoopPlugin }), {
|
|
193
|
-
virtual: true,
|
|
194
|
-
});
|
|
195
|
-
const mockResolveFrom = resolveFrom.silent as jest.MockedFunction<typeof resolveFrom.silent>;
|
|
196
|
-
mockResolveFrom.mockReturnValue('/app/node_modules/third-party/index.js');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
beforeEach(() => {
|
|
200
|
-
jest.doMock('fs', () => volFS);
|
|
226
|
+
it('should contain extra files from config plugins', async () => {
|
|
201
227
|
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
...baseAppJson.expo,
|
|
220
|
-
plugins: ['third-party'],
|
|
221
|
-
},
|
|
222
|
-
})
|
|
223
|
-
);
|
|
224
|
-
const sources = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
228
|
+
const config = await getConfig('/app', { skipSDKVersionRequirement: true });
|
|
229
|
+
const mockSpawnAsync = spawnAsync as jest.MockedFunction<typeof spawnAsync>;
|
|
230
|
+
const stdout = JSON.stringify({
|
|
231
|
+
config,
|
|
232
|
+
loadedModules: [
|
|
233
|
+
'node_modules/third-party/index.js',
|
|
234
|
+
'node_modules/third-party/node_modules/transitive-third-party/index.js',
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
mockSpawnAsync.mockResolvedValueOnce({
|
|
238
|
+
output: [],
|
|
239
|
+
stdout,
|
|
240
|
+
stderr: '',
|
|
241
|
+
signal: null,
|
|
242
|
+
status: 0,
|
|
243
|
+
});
|
|
244
|
+
const sources = await getExpoConfigSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
225
245
|
expect(sources).toContainEqual(
|
|
226
246
|
expect.objectContaining({
|
|
227
|
-
type: '
|
|
228
|
-
filePath: 'node_modules/third-party',
|
|
229
|
-
})
|
|
230
|
-
);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should contain external config-plugin dir from plugin with parameters', async () => {
|
|
234
|
-
setupThirdPartyPlugin();
|
|
235
|
-
|
|
236
|
-
vol.writeFileSync(
|
|
237
|
-
'/app/app.json',
|
|
238
|
-
JSON.stringify({
|
|
239
|
-
...baseAppJson,
|
|
240
|
-
expo: {
|
|
241
|
-
...baseAppJson.expo,
|
|
242
|
-
plugins: [['third-party', { parameter: 'foo' }]],
|
|
243
|
-
},
|
|
247
|
+
type: 'file',
|
|
248
|
+
filePath: 'node_modules/third-party/index.js',
|
|
244
249
|
})
|
|
245
250
|
);
|
|
246
|
-
const sources = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
247
251
|
expect(sources).toContainEqual(
|
|
248
252
|
expect.objectContaining({
|
|
249
|
-
type: '
|
|
250
|
-
filePath: 'node_modules/third-party',
|
|
253
|
+
type: 'file',
|
|
254
|
+
filePath: 'node_modules/third-party/node_modules/transitive-third-party/index.js',
|
|
251
255
|
})
|
|
252
256
|
);
|
|
253
257
|
});
|
|
254
|
-
|
|
255
|
-
it('should not contain external config-plugin dir from raw function plugins', async () => {
|
|
256
|
-
vol.writeFileSync(
|
|
257
|
-
'/app/app.config.js',
|
|
258
|
-
`\
|
|
259
|
-
export default ({ config }) => {
|
|
260
|
-
return config;
|
|
261
|
-
};`
|
|
262
|
-
);
|
|
263
|
-
const sources = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
264
|
-
|
|
265
|
-
vol.writeFileSync(
|
|
266
|
-
'/app/app.config.js',
|
|
267
|
-
`\
|
|
268
|
-
export default ({ config }) => {
|
|
269
|
-
config.plugins ||= [];
|
|
270
|
-
const withNoopPlugin = (config: any) => config;
|
|
271
|
-
config.plugins.push(withNoopPlugin);
|
|
272
|
-
return config;
|
|
273
|
-
};`
|
|
274
|
-
);
|
|
275
|
-
const sources2 = await getExpoConfigSourcesAsync('/app', normalizeOptions());
|
|
276
|
-
|
|
277
|
-
expect(sources).toEqual(sources2);
|
|
278
|
-
});
|
|
279
258
|
});
|
|
280
259
|
|
|
281
260
|
describe('sortExpoAutolinkingConfig', () => {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { vol } from 'memfs';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeOptionsAsync } from '../../Options';
|
|
4
4
|
import { getPatchPackageSourcesAsync } from '../PatchPackage';
|
|
5
5
|
import { getHashSourcesAsync } from '../Sourcer';
|
|
6
6
|
|
|
7
|
+
jest.mock('@expo/spawn-async');
|
|
8
|
+
jest.mock('fs');
|
|
7
9
|
jest.mock('fs/promises');
|
|
8
10
|
jest.mock('/app/package.json', () => ({}), { virtual: true });
|
|
9
11
|
|
|
@@ -16,7 +18,7 @@ describe(getPatchPackageSourcesAsync, () => {
|
|
|
16
18
|
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
17
19
|
vol.fromJSON(require('./fixtures/PatchPackage.json'));
|
|
18
20
|
|
|
19
|
-
const sources = await getPatchPackageSourcesAsync('/app',
|
|
21
|
+
const sources = await getPatchPackageSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
20
22
|
expect(sources).toContainEqual(
|
|
21
23
|
expect.objectContaining({
|
|
22
24
|
type: 'dir',
|
|
@@ -28,6 +30,7 @@ describe(getPatchPackageSourcesAsync, () => {
|
|
|
28
30
|
|
|
29
31
|
describe('patch-package postinstall', () => {
|
|
30
32
|
it('should contain `package.json` scripts block for lifecycle patches', async () => {
|
|
33
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
31
34
|
const scriptsBlock = {
|
|
32
35
|
postinstall: 'npx patch-package',
|
|
33
36
|
};
|
|
@@ -41,7 +44,7 @@ describe('patch-package postinstall', () => {
|
|
|
41
44
|
{ virtual: true }
|
|
42
45
|
);
|
|
43
46
|
|
|
44
|
-
const sources = await getHashSourcesAsync('/app',
|
|
47
|
+
const sources = await getHashSourcesAsync('/app', await normalizeOptionsAsync('/app'));
|
|
45
48
|
expect(sources).toContainEqual(
|
|
46
49
|
expect.objectContaining({
|
|
47
50
|
type: 'contents',
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vol } from 'memfs';
|
|
2
|
+
|
|
3
|
+
import { normalizeOptionsAsync } from '../../Options';
|
|
2
4
|
import { getHashSourcesAsync } from '../Sourcer';
|
|
3
5
|
|
|
6
|
+
jest.mock('@expo/spawn-async');
|
|
7
|
+
jest.mock('fs');
|
|
8
|
+
jest.mock('fs/promises');
|
|
9
|
+
|
|
4
10
|
describe(getHashSourcesAsync, () => {
|
|
5
11
|
it('should include `extraSources` from input parameter', async () => {
|
|
12
|
+
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
|
|
6
13
|
const sources = await getHashSourcesAsync(
|
|
7
14
|
'/app',
|
|
8
|
-
|
|
15
|
+
await normalizeOptionsAsync('/app', {
|
|
9
16
|
extraSources: [{ type: 'dir', filePath: '/app/scripts', reasons: ['extra'] }],
|
|
10
17
|
})
|
|
11
18
|
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { stringifyJsonSorted } from '../Utils';
|
|
2
|
+
|
|
3
|
+
describe(stringifyJsonSorted, () => {
|
|
4
|
+
it('should support primitive types', () => {
|
|
5
|
+
expect(stringifyJsonSorted(1)).toEqual('1');
|
|
6
|
+
expect(stringifyJsonSorted('s')).toEqual('"s"');
|
|
7
|
+
expect(stringifyJsonSorted(true)).toEqual('true');
|
|
8
|
+
expect(stringifyJsonSorted(false)).toEqual('false');
|
|
9
|
+
expect(stringifyJsonSorted(null)).toEqual('null');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should sort array', () => {
|
|
13
|
+
expect(stringifyJsonSorted([])).toEqual('[]');
|
|
14
|
+
expect(stringifyJsonSorted([2, 'a', 1, false, 6, 4, 5, 's', 3, 0, true, 1, 1])).toEqual(
|
|
15
|
+
'["a","s",0,1,1,1,2,3,4,5,6,false,true]'
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should sort object by keys', () => {
|
|
20
|
+
expect(stringifyJsonSorted({})).toEqual('{}');
|
|
21
|
+
expect(stringifyJsonSorted({ c: '1', a: '3', b: '2' })).toEqual('{"a":"3","b":"2","c":"1"}');
|
|
22
|
+
expect(
|
|
23
|
+
stringifyJsonSorted({ c: '1', a: '3', b: '2', nested: { c: '1', a: '3', b: '2' } })
|
|
24
|
+
).toEqual('{"a":"3","b":"2","c":"1","nested":{"a":"3","b":"2","c":"1"}}');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should support mixed array/object', () => {
|
|
28
|
+
expect(stringifyJsonSorted([{ b: '2' }, { c: '1' }, { a: '3' }])).toEqual(
|
|
29
|
+
'[{"a":"3"},{"b":"2"},{"c":"1"}]'
|
|
30
|
+
);
|
|
31
|
+
expect(stringifyJsonSorted([{ b: '2' }, {}, { a: '3' }, null])).toEqual(
|
|
32
|
+
'[null,{"a":"3"},{"b":"2"},{}]'
|
|
33
|
+
);
|
|
34
|
+
expect(
|
|
35
|
+
stringifyJsonSorted({
|
|
36
|
+
array: [3, 2, 1],
|
|
37
|
+
nestedData: { nestedArray: [{ b: '2' }, { c: '1' }, { a: '3' }] },
|
|
38
|
+
})
|
|
39
|
+
).toEqual('{"array":[1,2,3],"nestedData":{"nestedArray":[{"a":"3"},{"b":"2"},{"c":"1"}]}}');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import minimatch from 'minimatch';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Indicate the given `filePath` should be excluded by `ignorePaths`
|
|
5
|
+
*/
|
|
6
|
+
export function isIgnoredPath(
|
|
7
|
+
filePath: string,
|
|
8
|
+
ignorePaths: string[],
|
|
9
|
+
minimatchOptions: minimatch.IOptions = { dot: true }
|
|
10
|
+
): boolean {
|
|
11
|
+
const minimatchObjs = ignorePaths.map(
|
|
12
|
+
(ignorePath) => new minimatch.Minimatch(ignorePath, minimatchOptions)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
let result = false;
|
|
16
|
+
for (const minimatchObj of minimatchObjs) {
|
|
17
|
+
const currMatch = minimatchObj.match(filePath);
|
|
18
|
+
if (minimatchObj.negate && result && !currMatch) {
|
|
19
|
+
// Special handler for negate (!pattern).
|
|
20
|
+
// As long as previous match result is true and not matched from the current negate pattern, we should early return.
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
result ||= currMatch;
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isIgnoredPath } from '../Path';
|
|
2
|
+
|
|
3
|
+
describe(isIgnoredPath, () => {
|
|
4
|
+
it('should support file pattern', () => {
|
|
5
|
+
expect(isIgnoredPath('app.json', ['app.json'])).toBe(true);
|
|
6
|
+
expect(isIgnoredPath('app.ts', ['*.{js,ts}'])).toBe(true);
|
|
7
|
+
expect(isIgnoredPath('/dir/app.json', ['/dir/*.json'])).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should support directory pattern', () => {
|
|
11
|
+
expect(isIgnoredPath('/app/ios/Podfile', ['**/ios/**/*'])).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('case sensitive by design', () => {
|
|
15
|
+
expect(isIgnoredPath('app.json', ['APP.JSON'])).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should include dot files from wildcard pattern', () => {
|
|
19
|
+
expect(isIgnoredPath('.bashrc', ['*'])).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('no `matchBase` and `partial` by design', () => {
|
|
23
|
+
expect(isIgnoredPath('/dir/app.json', ['app.json'])).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('match a file inside a dir should use a globstar', () => {
|
|
27
|
+
expect(isIgnoredPath('/dir/app.ts', ['*'])).toBe(false);
|
|
28
|
+
expect(isIgnoredPath('/dir/app.ts', ['**/*'])).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should use `!` to override default ignorePaths', () => {
|
|
32
|
+
const ignorePaths = ['**/ios/**/*', '!**/ios/Podfile', '**/android/**/*'];
|
|
33
|
+
expect(isIgnoredPath('/app/ios/Podfile', ignorePaths)).toBe(false);
|
|
34
|
+
expect(isIgnoredPath('/app/ios/Podfile.lock', ignorePaths)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
});
|