@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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A helper script to load the Expo config and loaded plugins from a project
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getExpoConfigLoaderPath = void 0;
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const module_1 = __importDefault(require("module"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const resolve_from_1 = __importDefault(require("resolve-from"));
|
|
14
|
+
const Options_1 = require("../Options");
|
|
15
|
+
const Path_1 = require("../utils/Path");
|
|
16
|
+
async function runAsync(programName, args = []) {
|
|
17
|
+
if (args.length < 1) {
|
|
18
|
+
console.log(`Usage: ${programName} <projectRoot> [ignoredFile]`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const projectRoot = path_1.default.resolve(args[0]);
|
|
22
|
+
const ignoredFile = args[1] ? path_1.default.resolve(args[1]) : null;
|
|
23
|
+
// @ts-expect-error: module internal _cache
|
|
24
|
+
const loadedModulesBefore = new Set(Object.keys(module_1.default._cache));
|
|
25
|
+
const { getConfig } = require((0, resolve_from_1.default)(path_1.default.resolve(projectRoot), 'expo/config'));
|
|
26
|
+
const config = await getConfig(projectRoot, { skipSDKVersionRequirement: true });
|
|
27
|
+
// @ts-expect-error: module internal _cache
|
|
28
|
+
const loadedModules = Object.keys(module_1.default._cache)
|
|
29
|
+
.filter((modulePath) => !loadedModulesBefore.has(modulePath))
|
|
30
|
+
.map((modulePath) => path_1.default.relative(projectRoot, modulePath));
|
|
31
|
+
const ignoredPaths = await loadIgnoredPathsAsync(ignoredFile);
|
|
32
|
+
const filteredLoadedModules = loadedModules.filter((modulePath) => !(0, Path_1.isIgnoredPath)(modulePath, ignoredPaths));
|
|
33
|
+
console.log(JSON.stringify({ config, loadedModules: filteredLoadedModules }));
|
|
34
|
+
}
|
|
35
|
+
// If running from the command line
|
|
36
|
+
if (require.main?.filename === __filename) {
|
|
37
|
+
(async () => {
|
|
38
|
+
const programIndex = process.argv.findIndex((arg) => arg === __filename);
|
|
39
|
+
try {
|
|
40
|
+
await runAsync(process.argv[programIndex], process.argv.slice(programIndex + 1));
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
console.error('Uncaught Error', e);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
})();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Load the generated ignored paths file from caller and remove the file after loading
|
|
50
|
+
*/
|
|
51
|
+
async function loadIgnoredPathsAsync(ignoredFile) {
|
|
52
|
+
if (!ignoredFile) {
|
|
53
|
+
return Options_1.DEFAULT_IGNORE_PATHS;
|
|
54
|
+
}
|
|
55
|
+
const ignorePaths = [];
|
|
56
|
+
try {
|
|
57
|
+
const fingerprintIgnore = await promises_1.default.readFile(ignoredFile, 'utf8');
|
|
58
|
+
const fingerprintIgnoreLines = fingerprintIgnore.split('\n');
|
|
59
|
+
for (const line of fingerprintIgnoreLines) {
|
|
60
|
+
const trimmedLine = line.trim();
|
|
61
|
+
if (trimmedLine) {
|
|
62
|
+
ignorePaths.push(trimmedLine);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
try {
|
|
68
|
+
await promises_1.default.rm(ignoredFile);
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
return ignorePaths;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the path to the ExpoConfigLoader file.
|
|
75
|
+
*/
|
|
76
|
+
function getExpoConfigLoaderPath() {
|
|
77
|
+
return path_1.default.join(__dirname, 'ExpoConfigLoader.js');
|
|
78
|
+
}
|
|
79
|
+
exports.getExpoConfigLoaderPath = getExpoConfigLoaderPath;
|
|
80
|
+
//# sourceMappingURL=ExpoConfigLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoConfigLoader.js","sourceRoot":"","sources":["../../src/sourcer/ExpoConfigLoader.ts"],"names":[],"mappings":"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"}
|
package/build/sourcer/Utils.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { HashSource } from '../Fingerprint.types';
|
|
2
2
|
export declare function getFileBasedHashSourceAsync(projectRoot: string, filePath: string, reason: string): Promise<HashSource | null>;
|
|
3
|
+
/**
|
|
4
|
+
* A version of `JSON.stringify` that keeps the keys sorted
|
|
5
|
+
*/
|
|
6
|
+
export declare function stringifyJsonSorted(target: any, space?: string | number | undefined): string;
|
package/build/sourcer/Utils.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getFileBasedHashSourceAsync = void 0;
|
|
6
|
+
exports.stringifyJsonSorted = exports.getFileBasedHashSourceAsync = void 0;
|
|
7
7
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
async function getFileBasedHashSourceAsync(projectRoot, filePath, reason) {
|
|
@@ -22,4 +22,41 @@ async function getFileBasedHashSourceAsync(projectRoot, filePath, reason) {
|
|
|
22
22
|
return result;
|
|
23
23
|
}
|
|
24
24
|
exports.getFileBasedHashSourceAsync = getFileBasedHashSourceAsync;
|
|
25
|
+
/**
|
|
26
|
+
* A version of `JSON.stringify` that keeps the keys sorted
|
|
27
|
+
*/
|
|
28
|
+
function stringifyJsonSorted(target, space) {
|
|
29
|
+
return JSON.stringify(target, (_, value) => sortJson(value), space);
|
|
30
|
+
}
|
|
31
|
+
exports.stringifyJsonSorted = stringifyJsonSorted;
|
|
32
|
+
function sortJson(json) {
|
|
33
|
+
if (Array.isArray(json)) {
|
|
34
|
+
return json.sort((a, b) => {
|
|
35
|
+
// Sort array items by their stringified value.
|
|
36
|
+
// We don't need the array to be sorted in meaningful way, just to be sorted in deterministic.
|
|
37
|
+
// E.g. `[{ b: '2' }, {}, { a: '3' }, null]` -> `[null, { a : '3' }, { b: '2' }, {}]`
|
|
38
|
+
// This result is not a perfect solution, but it's good enough for our use case.
|
|
39
|
+
const stringifiedA = stringifyJsonSorted(a);
|
|
40
|
+
const stringifiedB = stringifyJsonSorted(b);
|
|
41
|
+
if (stringifiedA < stringifiedB) {
|
|
42
|
+
return -1;
|
|
43
|
+
}
|
|
44
|
+
else if (stringifiedA > stringifiedB) {
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
return 0;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (json != null && typeof json === 'object') {
|
|
51
|
+
// Sort object items by keys
|
|
52
|
+
return Object.keys(json)
|
|
53
|
+
.sort()
|
|
54
|
+
.reduce((acc, key) => {
|
|
55
|
+
acc[key] = json[key];
|
|
56
|
+
return acc;
|
|
57
|
+
}, {});
|
|
58
|
+
}
|
|
59
|
+
// Return primitives
|
|
60
|
+
return json;
|
|
61
|
+
}
|
|
25
62
|
//# sourceMappingURL=Utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Utils.js","sourceRoot":"","sources":["../../src/sourcer/Utils.ts"],"names":[],"mappings":";;;;;;AAAA,2DAA6B;AAC7B,gDAAwB;AAIjB,KAAK,UAAU,2BAA2B,CAC/C,WAAmB,EACnB,QAAgB,EAChB,MAAc;IAEd,IAAI,MAAM,GAAsB,IAAI,CAAC;IACrC,IAAI;QACF,MAAM,IAAI,GAAG,MAAM,kBAAE,CAAC,IAAI,CAAC,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC7D,MAAM,GAAG;YACP,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YACzC,QAAQ;YACR,OAAO,EAAE,CAAC,MAAM,CAAC;SAClB,CAAC;KACH;IAAC,MAAM;QACN,MAAM,GAAG,IAAI,CAAC;KACf;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAjBD,kEAiBC"}
|
|
1
|
+
{"version":3,"file":"Utils.js","sourceRoot":"","sources":["../../src/sourcer/Utils.ts"],"names":[],"mappings":";;;;;;AAAA,2DAA6B;AAC7B,gDAAwB;AAIjB,KAAK,UAAU,2BAA2B,CAC/C,WAAmB,EACnB,QAAgB,EAChB,MAAc;IAEd,IAAI,MAAM,GAAsB,IAAI,CAAC;IACrC,IAAI;QACF,MAAM,IAAI,GAAG,MAAM,kBAAE,CAAC,IAAI,CAAC,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC7D,MAAM,GAAG;YACP,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YACzC,QAAQ;YACR,OAAO,EAAE,CAAC,MAAM,CAAC;SAClB,CAAC;KACH;IAAC,MAAM;QACN,MAAM,GAAG,IAAI,CAAC;KACf;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAjBD,kEAiBC;AAED;;GAEG;AACH,SAAgB,mBAAmB,CAAC,MAAW,EAAE,KAAmC;IAClF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;AACtE,CAAC;AAFD,kDAEC;AAED,SAAS,QAAQ,CAAC,IAAS;IACzB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACvB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACxB,+CAA+C;YAC/C,8FAA8F;YAC9F,qFAAqF;YACrF,gFAAgF;YAChF,MAAM,YAAY,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,YAAY,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;YAC5C,IAAI,YAAY,GAAG,YAAY,EAAE;gBAC/B,OAAO,CAAC,CAAC,CAAC;aACX;iBAAM,IAAI,YAAY,GAAG,YAAY,EAAE;gBACtC,OAAO,CAAC,CAAC;aACV;YACD,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;KACJ;IAED,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;QAC5C,4BAA4B;QAC5B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;aACrB,IAAI,EAAE;aACN,MAAM,CAAC,CAAC,GAAQ,EAAE,GAAW,EAAE,EAAE;YAChC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YACrB,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAAE,CAAC,CAAC;KACV;IAED,oBAAoB;IACpB,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isIgnoredPath = void 0;
|
|
7
|
+
const minimatch_1 = __importDefault(require("minimatch"));
|
|
8
|
+
/**
|
|
9
|
+
* Indicate the given `filePath` should be excluded by `ignorePaths`
|
|
10
|
+
*/
|
|
11
|
+
function isIgnoredPath(filePath, ignorePaths, minimatchOptions = { dot: true }) {
|
|
12
|
+
const minimatchObjs = ignorePaths.map((ignorePath) => new minimatch_1.default.Minimatch(ignorePath, minimatchOptions));
|
|
13
|
+
let result = false;
|
|
14
|
+
for (const minimatchObj of minimatchObjs) {
|
|
15
|
+
const currMatch = minimatchObj.match(filePath);
|
|
16
|
+
if (minimatchObj.negate && result && !currMatch) {
|
|
17
|
+
// Special handler for negate (!pattern).
|
|
18
|
+
// As long as previous match result is true and not matched from the current negate pattern, we should early return.
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
result || (result = currMatch);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
exports.isIgnoredPath = isIgnoredPath;
|
|
26
|
+
//# sourceMappingURL=Path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Path.js","sourceRoot":"","sources":["../../src/utils/Path.ts"],"names":[],"mappings":";;;;;;AAAA,0DAAkC;AAElC;;GAEG;AACH,SAAgB,aAAa,CAC3B,QAAgB,EAChB,WAAqB,EACrB,mBAAuC,EAAE,GAAG,EAAE,IAAI,EAAE;IAEpD,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,CACnC,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,mBAAS,CAAC,SAAS,CAAC,UAAU,EAAE,gBAAgB,CAAC,CACtE,CAAC;IAEF,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE;QACxC,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,YAAY,CAAC,MAAM,IAAI,MAAM,IAAI,CAAC,SAAS,EAAE;YAC/C,yCAAyC;YACzC,oHAAoH;YACpH,OAAO,KAAK,CAAC;SACd;QACD,MAAM,KAAN,MAAM,GAAK,SAAS,EAAC;KACtB;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AApBD,sCAoBC"}
|
|
@@ -131,11 +131,12 @@ exports[`getHashSourcesAsync - managed project should match snapshot 1`] = `
|
|
|
131
131
|
"type": "contents",
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
|
-
"
|
|
134
|
+
"contents": "{"android":{"adaptiveIcon":{"backgroundColor":"#FFFFFF","foregroundImage":"./assets/adaptive-icon.png"}},"assetBundlePatterns":["**/*"],"icon":"./assets/icon.png","ios":{"supportsTablet":true},"name":"fingerprint-e2e-managed","orientation":"portrait","platforms":["android","ios"],"sdkVersion":"47.0.0","slug":"fingerprint-e2e-managed","splash":{"backgroundColor":"#ffffff","image":"./assets/splash.png","resizeMode":"contain"},"updates":{"fallbackToCacheTimeout":0},"userInterfaceStyle":"light","version":"1.0.0","web":{"favicon":"./assets/favicon.png"}}",
|
|
135
|
+
"id": "expoConfig",
|
|
135
136
|
"reasons": [
|
|
136
137
|
"expoConfig",
|
|
137
138
|
],
|
|
138
|
-
"type": "
|
|
139
|
+
"type": "contents",
|
|
139
140
|
},
|
|
140
141
|
{
|
|
141
142
|
"filePath": "./assets/icon.png",
|
|
@@ -6,6 +6,13 @@ import rimraf from 'rimraf';
|
|
|
6
6
|
|
|
7
7
|
import { createProjectHashAsync } from '../../src/Fingerprint';
|
|
8
8
|
|
|
9
|
+
jest.mock('../../src/sourcer/ExpoConfigLoader', () => ({
|
|
10
|
+
// Mock the getExpoConfigLoaderPath to use the built version rather than the typescript version from src
|
|
11
|
+
getExpoConfigLoaderPath: jest.fn(() =>
|
|
12
|
+
path.resolve(__dirname, '..', '..', 'build', 'sourcer', 'ExpoConfigLoader.js')
|
|
13
|
+
),
|
|
14
|
+
}));
|
|
15
|
+
|
|
9
16
|
describe('bare project test', () => {
|
|
10
17
|
jest.setTimeout(600000);
|
|
11
18
|
const tmpDir = os.tmpdir();
|
|
@@ -8,9 +8,16 @@ import {
|
|
|
8
8
|
createProjectHashAsync,
|
|
9
9
|
diffFingerprintChangesAsync,
|
|
10
10
|
} from '../../src/Fingerprint';
|
|
11
|
-
import {
|
|
11
|
+
import { normalizeOptionsAsync } from '../../src/Options';
|
|
12
12
|
import { getHashSourcesAsync } from '../../src/sourcer/Sourcer';
|
|
13
13
|
|
|
14
|
+
jest.mock('../../src/sourcer/ExpoConfigLoader', () => ({
|
|
15
|
+
// Mock the getExpoConfigLoaderPath to use the built version rather than the typescript version from src
|
|
16
|
+
getExpoConfigLoaderPath: jest.fn(() =>
|
|
17
|
+
path.resolve(__dirname, '..', '..', 'build', 'sourcer', 'ExpoConfigLoader.js')
|
|
18
|
+
),
|
|
19
|
+
}));
|
|
20
|
+
|
|
14
21
|
describe('managed project test', () => {
|
|
15
22
|
jest.setTimeout(600000);
|
|
16
23
|
const tmpDir = require('temp-dir');
|
|
@@ -65,7 +72,7 @@ describe('managed project test', () => {
|
|
|
65
72
|
|
|
66
73
|
const configPath = path.join(projectRoot, 'app.json');
|
|
67
74
|
const config = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
|
68
|
-
config.jsEngine = 'hermes';
|
|
75
|
+
config.expo.jsEngine = 'hermes';
|
|
69
76
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
70
77
|
|
|
71
78
|
const hash2 = await createProjectHashAsync(projectRoot);
|
|
@@ -147,7 +154,10 @@ describe(`getHashSourcesAsync - managed project`, () => {
|
|
|
147
154
|
});
|
|
148
155
|
|
|
149
156
|
it('should match snapshot', async () => {
|
|
150
|
-
const sources = await getHashSourcesAsync(
|
|
157
|
+
const sources = await getHashSourcesAsync(
|
|
158
|
+
projectRoot,
|
|
159
|
+
await normalizeOptionsAsync(projectRoot)
|
|
160
|
+
);
|
|
151
161
|
expect(sources).toMatchSnapshot();
|
|
152
162
|
});
|
|
153
163
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/fingerprint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A library to generate a fingerprint from a React Native project",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"clean": "rimraf build ./tsconfig.tsbuildinfo",
|
|
12
12
|
"lint": "eslint .",
|
|
13
13
|
"test": "jest",
|
|
14
|
-
"test:e2e": "jest --config e2e/jest.config.js"
|
|
14
|
+
"test:e2e": "yarn prepare && jest --config e2e/jest.config.js"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"fingerprint": "bin/cli.js"
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"glob": "^7.1.7",
|
|
49
49
|
"temp-dir": "^2.0.0"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "039366f050814a220cf31f87e0fbdf27416bb478"
|
|
52
52
|
}
|
package/src/Fingerprint.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dedupSources } from './Dedup';
|
|
2
2
|
import type { Fingerprint, FingerprintSource, Options } from './Fingerprint.types';
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeOptionsAsync } from './Options';
|
|
4
4
|
import { sortSources } from './Sort';
|
|
5
5
|
import { createFingerprintFromSourcesAsync } from './hash/Hash';
|
|
6
6
|
import { getHashSourcesAsync } from './sourcer/Sourcer';
|
|
@@ -12,7 +12,7 @@ export async function createFingerprintAsync(
|
|
|
12
12
|
projectRoot: string,
|
|
13
13
|
options?: Options
|
|
14
14
|
): Promise<Fingerprint> {
|
|
15
|
-
const opts =
|
|
15
|
+
const opts = await normalizeOptionsAsync(projectRoot, options);
|
|
16
16
|
const sources = await getHashSourcesAsync(projectRoot, opts);
|
|
17
17
|
const normalizedSources = sortSources(dedupSources(sources, projectRoot));
|
|
18
18
|
const fingerprint = await createFingerprintFromSourcesAsync(normalizedSources, projectRoot, opts);
|
package/src/Fingerprint.types.ts
CHANGED
|
@@ -39,9 +39,21 @@ export interface Options {
|
|
|
39
39
|
/**
|
|
40
40
|
* Excludes directories from hashing. This supported pattern is as `glob()`.
|
|
41
41
|
* Default is `['android/build', 'android/app/build', 'android/app/.cxx', 'ios/Pods']`.
|
|
42
|
+
* @deprecated Use `ignorePaths` instead.
|
|
42
43
|
*/
|
|
43
44
|
dirExcludes?: string[];
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Ignore files and directories from hashing. This supported pattern is as `glob()`.
|
|
48
|
+
*
|
|
49
|
+
* Please note that the pattern matching is slightly different from gitignore. For example, we don't support partial matching where `build` does not match `android/build`. You should use `'**' + '/build'` instead.
|
|
50
|
+
* @see [minimatch implementations](https://github.com/isaacs/minimatch#comparisons-to-other-fnmatchglob-implementations) for more reference.
|
|
51
|
+
*
|
|
52
|
+
* Besides this `ignorePaths`, fingerprint comes with implicit default ignorePaths defined in `Options.DEFAULT_IGNORE_PATHS`.
|
|
53
|
+
* If you want to override the default ignorePaths, use `!` prefix.
|
|
54
|
+
*/
|
|
55
|
+
ignorePaths?: string[];
|
|
56
|
+
|
|
45
57
|
/**
|
|
46
58
|
* Additional sources for hashing.
|
|
47
59
|
*/
|
|
@@ -54,7 +66,7 @@ export interface NormalizedOptions extends Options {
|
|
|
54
66
|
platforms: NonNullable<Options['platforms']>;
|
|
55
67
|
concurrentIoLimit: NonNullable<Options['concurrentIoLimit']>;
|
|
56
68
|
hashAlgorithm: NonNullable<Options['hashAlgorithm']>;
|
|
57
|
-
|
|
69
|
+
ignorePaths: NonNullable<Options['ignorePaths']>;
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
export interface HashSourceFile {
|
package/src/Options.ts
CHANGED
|
@@ -1,18 +1,81 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
1
2
|
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
|
|
3
5
|
import type { NormalizedOptions, Options } from './Fingerprint.types';
|
|
4
6
|
|
|
5
|
-
export
|
|
7
|
+
export const FINGERPRINT_IGNORE_FILENAME = '.fingerprintignore';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_IGNORE_PATHS = [
|
|
10
|
+
FINGERPRINT_IGNORE_FILENAME,
|
|
11
|
+
'**/android/build/**/*',
|
|
12
|
+
'**/android/app/build/**/*',
|
|
13
|
+
'**/android/app/.cxx/**/*',
|
|
14
|
+
'**/ios/Pods/**/*',
|
|
15
|
+
|
|
16
|
+
// Ignore all expo configs because we will read expo config in a HashSourceContents already
|
|
17
|
+
'app.config.ts',
|
|
18
|
+
'app.config.js',
|
|
19
|
+
'app.config.json',
|
|
20
|
+
'app.json',
|
|
21
|
+
|
|
22
|
+
// Ignore default javascript files when calling `getConfig()`
|
|
23
|
+
'**/node_modules/@babel/**/*',
|
|
24
|
+
'**/node_modules/@expo/**/*',
|
|
25
|
+
'**/node_modules/@jridgewell/**/*',
|
|
26
|
+
'**/node_modules/expo/config.js',
|
|
27
|
+
'**/node_modules/expo/config-plugins.js',
|
|
28
|
+
`**/node_modules/{${[
|
|
29
|
+
'debug',
|
|
30
|
+
'escape-string-regexp',
|
|
31
|
+
'getenv',
|
|
32
|
+
'graceful-fs',
|
|
33
|
+
'has-flag',
|
|
34
|
+
'imurmurhash',
|
|
35
|
+
'js-tokens',
|
|
36
|
+
'json5',
|
|
37
|
+
'lines-and-columns',
|
|
38
|
+
'require-from-string',
|
|
39
|
+
'resolve-from',
|
|
40
|
+
'signal-exit',
|
|
41
|
+
'sucrase',
|
|
42
|
+
'supports-color',
|
|
43
|
+
'ts-interface-checker',
|
|
44
|
+
'write-file-atomic',
|
|
45
|
+
].join(',')}}/**/*`,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export async function normalizeOptionsAsync(
|
|
49
|
+
projectRoot: string,
|
|
50
|
+
options?: Options
|
|
51
|
+
): Promise<NormalizedOptions> {
|
|
6
52
|
return {
|
|
7
53
|
...options,
|
|
8
54
|
platforms: options?.platforms ?? ['android', 'ios'],
|
|
9
55
|
concurrentIoLimit: options?.concurrentIoLimit ?? os.cpus().length,
|
|
10
56
|
hashAlgorithm: options?.hashAlgorithm ?? 'sha1',
|
|
11
|
-
|
|
12
|
-
'**/android/build',
|
|
13
|
-
'**/android/app/build',
|
|
14
|
-
'**/android/app/.cxx',
|
|
15
|
-
'ios/Pods',
|
|
16
|
-
],
|
|
57
|
+
ignorePaths: await collectIgnorePathsAsync(projectRoot, options),
|
|
17
58
|
};
|
|
18
59
|
}
|
|
60
|
+
|
|
61
|
+
async function collectIgnorePathsAsync(projectRoot: string, options?: Options): Promise<string[]> {
|
|
62
|
+
const ignorePaths = [
|
|
63
|
+
...DEFAULT_IGNORE_PATHS,
|
|
64
|
+
...(options?.ignorePaths ?? []),
|
|
65
|
+
...(options?.dirExcludes?.map((dirExclude) => `${dirExclude}/**/*`) ?? []),
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const fingerprintIgnorePath = path.join(projectRoot, FINGERPRINT_IGNORE_FILENAME);
|
|
69
|
+
try {
|
|
70
|
+
const fingerprintIgnore = await fs.readFile(fingerprintIgnorePath, 'utf8');
|
|
71
|
+
const fingerprintIgnoreLines = fingerprintIgnore.split('\n');
|
|
72
|
+
for (const line of fingerprintIgnoreLines) {
|
|
73
|
+
const trimmedLine = line.trim();
|
|
74
|
+
if (trimmedLine) {
|
|
75
|
+
ignorePaths.push(trimmedLine);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
|
|
80
|
+
return ignorePaths;
|
|
81
|
+
}
|
|
@@ -2,7 +2,7 @@ import { vol } from 'memfs';
|
|
|
2
2
|
|
|
3
3
|
import { createFingerprintAsync, diffFingerprintChangesAsync } from '../Fingerprint';
|
|
4
4
|
import type { Fingerprint } from '../Fingerprint.types';
|
|
5
|
-
import {
|
|
5
|
+
import { normalizeOptionsAsync } from '../Options';
|
|
6
6
|
|
|
7
7
|
jest.mock('fs');
|
|
8
8
|
jest.mock('fs/promises');
|
|
@@ -15,8 +15,12 @@ describe(diffFingerprintChangesAsync, () => {
|
|
|
15
15
|
|
|
16
16
|
it('should return empty array when fingerprint matched', async () => {
|
|
17
17
|
vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
|
|
18
|
-
const fingerprint = await createFingerprintAsync('/app',
|
|
19
|
-
const diff = await diffFingerprintChangesAsync(
|
|
18
|
+
const fingerprint = await createFingerprintAsync('/app', await normalizeOptionsAsync('/app'));
|
|
19
|
+
const diff = await diffFingerprintChangesAsync(
|
|
20
|
+
fingerprint,
|
|
21
|
+
'/app',
|
|
22
|
+
await normalizeOptionsAsync('/app')
|
|
23
|
+
);
|
|
20
24
|
expect(diff.length).toBe(0);
|
|
21
25
|
});
|
|
22
26
|
|
|
@@ -27,16 +31,21 @@ describe(diffFingerprintChangesAsync, () => {
|
|
|
27
31
|
hash: '',
|
|
28
32
|
};
|
|
29
33
|
|
|
30
|
-
const diff = await diffFingerprintChangesAsync(
|
|
34
|
+
const diff = await diffFingerprintChangesAsync(
|
|
35
|
+
fingerprint,
|
|
36
|
+
'/app',
|
|
37
|
+
await normalizeOptionsAsync('/app')
|
|
38
|
+
);
|
|
31
39
|
expect(diff).toMatchInlineSnapshot(`
|
|
32
40
|
[
|
|
33
41
|
{
|
|
34
|
-
"
|
|
35
|
-
"hash": "
|
|
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",
|
|
36
45
|
"reasons": [
|
|
37
46
|
"expoConfig",
|
|
38
47
|
],
|
|
39
|
-
"type": "
|
|
48
|
+
"type": "contents",
|
|
40
49
|
},
|
|
41
50
|
]
|
|
42
51
|
`);
|
|
@@ -46,19 +55,27 @@ describe(diffFingerprintChangesAsync, () => {
|
|
|
46
55
|
vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
|
|
47
56
|
const packageJson = JSON.parse(vol.readFileSync('/app/package.json', 'utf8').toString());
|
|
48
57
|
jest.doMock('/app/package.json', () => packageJson, { virtual: true });
|
|
49
|
-
const fingerprint = await createFingerprintAsync('/app',
|
|
58
|
+
const fingerprint = await createFingerprintAsync('/app', await normalizeOptionsAsync('/app'));
|
|
50
59
|
|
|
51
60
|
// first round for bumping package version which should not cause changes
|
|
52
61
|
packageJson.version = '111.111.111';
|
|
53
62
|
jest.doMock('/app/package.json', () => packageJson, { virtual: true });
|
|
54
|
-
let diff = await diffFingerprintChangesAsync(
|
|
63
|
+
let diff = await diffFingerprintChangesAsync(
|
|
64
|
+
fingerprint,
|
|
65
|
+
'/app',
|
|
66
|
+
await normalizeOptionsAsync('/app')
|
|
67
|
+
);
|
|
55
68
|
expect(diff.length).toBe(0);
|
|
56
69
|
|
|
57
70
|
// second round to update scripts section and it should cause changes
|
|
58
71
|
packageJson.scripts ||= {};
|
|
59
72
|
packageJson.scripts.postinstall = 'echo "hello"';
|
|
60
73
|
jest.doMock('/app/package.json', () => packageJson, { virtual: true });
|
|
61
|
-
diff = await diffFingerprintChangesAsync(
|
|
74
|
+
diff = await diffFingerprintChangesAsync(
|
|
75
|
+
fingerprint,
|
|
76
|
+
'/app',
|
|
77
|
+
await normalizeOptionsAsync('/app')
|
|
78
|
+
);
|
|
62
79
|
jest.dontMock('/app/package.json');
|
|
63
80
|
expect(diff).toMatchInlineSnapshot(`
|
|
64
81
|
[
|
|
@@ -77,20 +94,25 @@ describe(diffFingerprintChangesAsync, () => {
|
|
|
77
94
|
|
|
78
95
|
it('should return diff from file changes', async () => {
|
|
79
96
|
vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
|
|
80
|
-
const fingerprint = await createFingerprintAsync('/app',
|
|
97
|
+
const fingerprint = await createFingerprintAsync('/app', await normalizeOptionsAsync('/app'));
|
|
81
98
|
const config = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
|
|
82
99
|
config.expo.jsEngine = 'jsc';
|
|
83
100
|
vol.writeFileSync('/app/app.json', JSON.stringify(config, null, 2));
|
|
84
|
-
const diff = await diffFingerprintChangesAsync(
|
|
101
|
+
const diff = await diffFingerprintChangesAsync(
|
|
102
|
+
fingerprint,
|
|
103
|
+
'/app',
|
|
104
|
+
await normalizeOptionsAsync('/app')
|
|
105
|
+
);
|
|
85
106
|
expect(diff).toMatchInlineSnapshot(`
|
|
86
107
|
[
|
|
87
108
|
{
|
|
88
|
-
"
|
|
89
|
-
"hash": "
|
|
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",
|
|
90
112
|
"reasons": [
|
|
91
113
|
"expoConfig",
|
|
92
114
|
],
|
|
93
|
-
"type": "
|
|
115
|
+
"type": "contents",
|
|
94
116
|
},
|
|
95
117
|
]
|
|
96
118
|
`);
|
|
@@ -98,9 +120,13 @@ describe(diffFingerprintChangesAsync, () => {
|
|
|
98
120
|
|
|
99
121
|
it('should return diff from dir changes', async () => {
|
|
100
122
|
vol.fromJSON(require('../sourcer/__tests__/fixtures/BareReactNative70Project.json'));
|
|
101
|
-
const fingerprint = await createFingerprintAsync('/app',
|
|
123
|
+
const fingerprint = await createFingerprintAsync('/app', await normalizeOptionsAsync('/app'));
|
|
102
124
|
vol.writeFileSync('/app/ios/README.md', '# Adding new file in ios dir');
|
|
103
|
-
const diff = await diffFingerprintChangesAsync(
|
|
125
|
+
const diff = await diffFingerprintChangesAsync(
|
|
126
|
+
fingerprint,
|
|
127
|
+
'/app',
|
|
128
|
+
await normalizeOptionsAsync('/app')
|
|
129
|
+
);
|
|
104
130
|
expect(diff).toMatchInlineSnapshot(`
|
|
105
131
|
[
|
|
106
132
|
{
|
package/src/hash/Hash.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
import { createReadStream } from 'fs';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
|
-
import minimatch from 'minimatch';
|
|
5
4
|
import pLimit from 'p-limit';
|
|
6
5
|
import path from 'path';
|
|
7
6
|
|
|
@@ -13,6 +12,7 @@ import type {
|
|
|
13
12
|
HashSourceContents,
|
|
14
13
|
NormalizedOptions,
|
|
15
14
|
} from '../Fingerprint.types';
|
|
15
|
+
import { isIgnoredPath } from '../utils/Path';
|
|
16
16
|
import { profile } from '../utils/Profile';
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -82,10 +82,14 @@ export async function createFileHashResultsAsync(
|
|
|
82
82
|
limiter: pLimit.Limit,
|
|
83
83
|
projectRoot: string,
|
|
84
84
|
options: NormalizedOptions
|
|
85
|
-
): Promise<HashResult> {
|
|
85
|
+
): Promise<HashResult | null> {
|
|
86
86
|
// Backup code for faster hashing
|
|
87
87
|
/*
|
|
88
88
|
return limiter(async () => {
|
|
89
|
+
if (isIgnoredPath(filePath, options.ignorePaths)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
89
93
|
const hasher = createHash(options.hashAlgorithm);
|
|
90
94
|
|
|
91
95
|
const stat = await fs.stat(filePath);
|
|
@@ -102,7 +106,11 @@ export async function createFileHashResultsAsync(
|
|
|
102
106
|
*/
|
|
103
107
|
|
|
104
108
|
return limiter(() => {
|
|
105
|
-
return new Promise<HashResult>((resolve, reject) => {
|
|
109
|
+
return new Promise<HashResult | null>((resolve, reject) => {
|
|
110
|
+
if (isIgnoredPath(filePath, options.ignorePaths)) {
|
|
111
|
+
return resolve(null);
|
|
112
|
+
}
|
|
113
|
+
|
|
106
114
|
let resolved = false;
|
|
107
115
|
const hasher = createHash(options.hashAlgorithm);
|
|
108
116
|
const stream = createReadStream(path.join(projectRoot, filePath));
|
|
@@ -123,18 +131,6 @@ export async function createFileHashResultsAsync(
|
|
|
123
131
|
});
|
|
124
132
|
}
|
|
125
133
|
|
|
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
134
|
/**
|
|
139
135
|
* Create `HashResult` for a dir.
|
|
140
136
|
* If the dir is excluded, returns null rather than a HashResult
|
|
@@ -146,7 +142,7 @@ export async function createDirHashResultsAsync(
|
|
|
146
142
|
options: NormalizedOptions,
|
|
147
143
|
depth: number = 0
|
|
148
144
|
): Promise<HashResult | null> {
|
|
149
|
-
if (
|
|
145
|
+
if (isIgnoredPath(dirPath, options.ignorePaths)) {
|
|
150
146
|
return null;
|
|
151
147
|
}
|
|
152
148
|
const dirents = (await fs.readdir(path.join(projectRoot, dirPath), { withFileTypes: true })).sort(
|
|
@@ -164,12 +160,15 @@ export async function createDirHashResultsAsync(
|
|
|
164
160
|
}
|
|
165
161
|
|
|
166
162
|
const hasher = createHash(options.hashAlgorithm);
|
|
167
|
-
const results = await Promise.all(promises)
|
|
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
|
+
}
|
|
168
169
|
for (const result of results) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
hasher.update(result.hex);
|
|
172
|
-
}
|
|
170
|
+
hasher.update(result.id);
|
|
171
|
+
hasher.update(result.hex);
|
|
173
172
|
}
|
|
174
173
|
const hex = hasher.digest('hex');
|
|
175
174
|
|