expo-updates 0.24.9 → 0.24.10
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 +10 -0
- package/android/build.gradle +2 -2
- package/build-cli/assetsVerify.d.ts +3 -0
- package/build-cli/assetsVerify.js +88 -0
- package/build-cli/assetsVerifyAsync.d.ts +59 -0
- package/build-cli/assetsVerifyAsync.js +127 -0
- package/build-cli/assetsVerifyTypes.d.ts +45 -0
- package/build-cli/assetsVerifyTypes.js +7 -0
- package/build-cli/cli.js +10 -0
- package/cli/assetsVerify.ts +125 -0
- package/cli/assetsVerifyAsync.ts +153 -0
- package/cli/assetsVerifyTypes.ts +64 -0
- package/cli/cli.ts +10 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 0.24.10 — 2024-02-06
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- Add assets:verify command to CLI. ([#26756](https://github.com/expo/expo/pull/26756) by [@douglowder](https://github.com/douglowder))
|
|
18
|
+
|
|
19
|
+
### 🐛 Bug fixes
|
|
20
|
+
|
|
21
|
+
- Fix assets:verify command for images with multiple scales. ([#26940](https://github.com/expo/expo/pull/26940) by [@douglowder](https://github.com/douglowder))
|
|
22
|
+
|
|
13
23
|
## 0.24.9 — 2024-01-26
|
|
14
24
|
|
|
15
25
|
### 🐛 Bug fixes
|
package/android/build.gradle
CHANGED
|
@@ -4,7 +4,7 @@ apply plugin: 'kotlin-kapt'
|
|
|
4
4
|
apply plugin: 'maven-publish'
|
|
5
5
|
|
|
6
6
|
group = 'host.exp.exponent'
|
|
7
|
-
version = '0.24.
|
|
7
|
+
version = '0.24.10'
|
|
8
8
|
|
|
9
9
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
10
10
|
if (expoModulesCorePlugin.exists()) {
|
|
@@ -122,7 +122,7 @@ android {
|
|
|
122
122
|
namespace "expo.modules.updates"
|
|
123
123
|
defaultConfig {
|
|
124
124
|
versionCode 31
|
|
125
|
-
versionName '0.24.
|
|
125
|
+
versionName '0.24.10'
|
|
126
126
|
consumerProguardFiles("proguard-rules.pro")
|
|
127
127
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
128
128
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.expoAssetsVerify = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
8
|
+
const assetsVerifyAsync_1 = require("./assetsVerifyAsync");
|
|
9
|
+
const assetsVerifyTypes_1 = require("./assetsVerifyTypes");
|
|
10
|
+
const args_1 = require("./utils/args");
|
|
11
|
+
const Log = tslib_1.__importStar(require("./utils/log"));
|
|
12
|
+
const debug = require('debug')('expo-updates:assets:verify');
|
|
13
|
+
const expoAssetsVerify = async (argv) => {
|
|
14
|
+
const args = (0, args_1.assertArgs)({
|
|
15
|
+
// Types
|
|
16
|
+
'--asset-map-path': String,
|
|
17
|
+
'--exported-manifest-path': String,
|
|
18
|
+
'--build-manifest-path': String,
|
|
19
|
+
'--platform': String,
|
|
20
|
+
'--help': Boolean,
|
|
21
|
+
// Aliases
|
|
22
|
+
'-a': '--asset-map-path',
|
|
23
|
+
'-e': '--exported-manifest-path',
|
|
24
|
+
'-b': '--build-manifest-path',
|
|
25
|
+
'-p': '--platform',
|
|
26
|
+
'-h': '--help',
|
|
27
|
+
}, argv !== null && argv !== void 0 ? argv : []);
|
|
28
|
+
if (args['--help']) {
|
|
29
|
+
Log.exit((0, chalk_1.default) `
|
|
30
|
+
{bold Description}
|
|
31
|
+
Verify that all static files in an exported bundle are in either the export or an embedded bundle
|
|
32
|
+
|
|
33
|
+
{bold Usage}
|
|
34
|
+
{dim $} npx expo-updates assets:verify {dim <dir>}
|
|
35
|
+
|
|
36
|
+
Options
|
|
37
|
+
<dir> Directory of the Expo project. Default: Current working directory
|
|
38
|
+
-a, --asset-map-path <path> Path to the \`assetmap.json\` in an export produced by the command \`npx expo export --dump-assetmap\`
|
|
39
|
+
-e, --exported-manifest-path <path> Path to the \`metadata.json\` in an export produced by the command \`npx expo export --dump-assetmap\`
|
|
40
|
+
-b, --build-manifest-path <path> Path to the \`app.manifest\` file created by expo-updates in an Expo application build (either ios or android)
|
|
41
|
+
-p, --platform <platform> Options: ${JSON.stringify(assetsVerifyTypes_1.validPlatforms)}
|
|
42
|
+
-h, --help Usage info
|
|
43
|
+
`, 0);
|
|
44
|
+
}
|
|
45
|
+
return (async () => {
|
|
46
|
+
const projectRoot = (0, args_1.getProjectRoot)(args);
|
|
47
|
+
const validatedArgs = resolveOptions(projectRoot, args);
|
|
48
|
+
debug(`Validated params: ${JSON.stringify(validatedArgs, null, 2)}`);
|
|
49
|
+
const { buildManifestPath, exportedManifestPath, assetMapPath, platform } = validatedArgs;
|
|
50
|
+
const missingAssets = await (0, assetsVerifyAsync_1.getMissingAssetsAsync)(buildManifestPath, exportedManifestPath, assetMapPath, platform);
|
|
51
|
+
if (missingAssets.length > 0) {
|
|
52
|
+
throw new Error(`${missingAssets.length} assets not found in either embedded manifest or in exported bundle:${JSON.stringify(missingAssets, null, 2)}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
Log.log(`All resolved assets found in either embedded manifest or in exported bundle.`);
|
|
56
|
+
}
|
|
57
|
+
process.exit(0);
|
|
58
|
+
})().catch((e) => {
|
|
59
|
+
Log.log(`${e}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
exports.expoAssetsVerify = expoAssetsVerify;
|
|
64
|
+
function resolveOptions(projectRoot, args) {
|
|
65
|
+
const exportedManifestPath = validatedPathFromArgument(projectRoot, args, '--exported-manifest-path');
|
|
66
|
+
const buildManifestPath = validatedPathFromArgument(projectRoot, args, '--build-manifest-path');
|
|
67
|
+
const assetMapPath = validatedPathFromArgument(projectRoot, args, '--asset-map-path');
|
|
68
|
+
const platform = args['--platform'];
|
|
69
|
+
if (!(0, assetsVerifyTypes_1.isValidPlatform)(platform)) {
|
|
70
|
+
throw new Error(`Platform must be one of ${JSON.stringify(assetsVerifyTypes_1.validPlatforms)}`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
exportedManifestPath,
|
|
74
|
+
buildManifestPath,
|
|
75
|
+
assetMapPath,
|
|
76
|
+
platform,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function validatedPathFromArgument(projectRoot, args, key) {
|
|
80
|
+
const maybeRelativePath = args[key];
|
|
81
|
+
if (!maybeRelativePath) {
|
|
82
|
+
throw new Error(`No value for ${key}`);
|
|
83
|
+
}
|
|
84
|
+
if (maybeRelativePath.indexOf('/') === 0) {
|
|
85
|
+
return maybeRelativePath; // absolute path
|
|
86
|
+
}
|
|
87
|
+
return path_1.default.resolve(projectRoot, maybeRelativePath);
|
|
88
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BuildManifest, ExportedMetadata, FullAssetDump, FullAssetDumpEntry, MissingAsset, Platform } from './assetsVerifyTypes';
|
|
2
|
+
/**
|
|
3
|
+
* Finds any assets that will be missing from an app given a build and an exported update bundle.
|
|
4
|
+
*
|
|
5
|
+
* @param buildManifestPath Path to the `app.manifest` file created by expo-updates in an Expo application build (either ios or android)
|
|
6
|
+
* @param exportMetadataPath Path to the `metadata.json` in an export produced by the command `npx expo export --dump-assetmap`
|
|
7
|
+
* @param assetMapPath Path to the `assetmap.json` in an export produced by the command `npx expo export --dump-assetmap`
|
|
8
|
+
* @param projectRoot The project root path
|
|
9
|
+
* @returns An array containing any assets that are found in the Metro asset dump, but not included in either app.manifest or the exported bundle
|
|
10
|
+
*/
|
|
11
|
+
export declare function getMissingAssetsAsync(buildManifestPath: string, exportMetadataPath: string, assetMapPath: string, platform: Platform): Promise<MissingAsset[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Reads and returns the embedded manifest (app.manifest) for a build.
|
|
14
|
+
*
|
|
15
|
+
* @param buildManifestPath Path to the build folder
|
|
16
|
+
* @param platform Either 'android' or 'ios'
|
|
17
|
+
* @param projectRoot The project root path
|
|
18
|
+
* @returns the JSON structure of the manifest
|
|
19
|
+
*/
|
|
20
|
+
export declare function getBuildManifestAsync(buildManifestPath: string): Promise<BuildManifest>;
|
|
21
|
+
/**
|
|
22
|
+
* Extracts the set of asset hashes from a build manifest.
|
|
23
|
+
*
|
|
24
|
+
* @param buildManifest The build manifest
|
|
25
|
+
* @returns The set of asset hashes contained in the build manifest
|
|
26
|
+
*/
|
|
27
|
+
export declare function getBuildManifestHashSet(buildManifest: BuildManifest): Set<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Reads and extracts the asset dump for an exported bundle.
|
|
30
|
+
*
|
|
31
|
+
* @param assetMapPath The path to the exported assetmap.json.
|
|
32
|
+
* @returns The asset dump as an object.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getFullAssetDumpAsync(assetMapPath: string): Promise<FullAssetDump>;
|
|
35
|
+
/**
|
|
36
|
+
* Extracts the set of asset hashes from an asset dump.
|
|
37
|
+
*
|
|
38
|
+
* @param assetDump
|
|
39
|
+
* @returns The set of asset hashes in the asset dump, and a map of hash to asset
|
|
40
|
+
*/
|
|
41
|
+
export declare function getFullAssetDumpHashSet(assetDump: FullAssetDump): {
|
|
42
|
+
fullAssetHashSet: Set<string>;
|
|
43
|
+
fullAssetHashMap: Map<string, FullAssetDumpEntry>;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Reads and extracts the metadata.json from an exported bundle.
|
|
47
|
+
*
|
|
48
|
+
* @param exportedMetadataPath Path to the exported metadata.json.
|
|
49
|
+
* @returns The metadata of the bundle.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getExportedMetadataAsync(exportedMetadataPath: string): Promise<ExportedMetadata>;
|
|
52
|
+
/**
|
|
53
|
+
* Extracts the set of asset hashes from an exported bundle's metadata for a given platform.
|
|
54
|
+
*
|
|
55
|
+
* @param metadata The metadata from the exported bundle
|
|
56
|
+
* @param platform Either 'android' or 'ios'
|
|
57
|
+
* @returns the set of asset hashes
|
|
58
|
+
*/
|
|
59
|
+
export declare function getExportedMetadataHashSet(metadata: ExportedMetadata, platform: Platform): Set<string>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getExportedMetadataHashSet = exports.getExportedMetadataAsync = exports.getFullAssetDumpHashSet = exports.getFullAssetDumpAsync = exports.getBuildManifestHashSet = exports.getBuildManifestAsync = exports.getMissingAssetsAsync = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const debug = require('debug')('expo-updates:assets:verify');
|
|
6
|
+
/**
|
|
7
|
+
* Finds any assets that will be missing from an app given a build and an exported update bundle.
|
|
8
|
+
*
|
|
9
|
+
* @param buildManifestPath Path to the `app.manifest` file created by expo-updates in an Expo application build (either ios or android)
|
|
10
|
+
* @param exportMetadataPath Path to the `metadata.json` in an export produced by the command `npx expo export --dump-assetmap`
|
|
11
|
+
* @param assetMapPath Path to the `assetmap.json` in an export produced by the command `npx expo export --dump-assetmap`
|
|
12
|
+
* @param projectRoot The project root path
|
|
13
|
+
* @returns An array containing any assets that are found in the Metro asset dump, but not included in either app.manifest or the exported bundle
|
|
14
|
+
*/
|
|
15
|
+
async function getMissingAssetsAsync(buildManifestPath, exportMetadataPath, assetMapPath, platform) {
|
|
16
|
+
const buildManifestHashSet = getBuildManifestHashSet(await getBuildManifestAsync(buildManifestPath));
|
|
17
|
+
const fullAssetDump = await getFullAssetDumpAsync(assetMapPath);
|
|
18
|
+
const { fullAssetHashSet, fullAssetHashMap } = getFullAssetDumpHashSet(fullAssetDump);
|
|
19
|
+
const exportedAssetSet = getExportedMetadataHashSet(await getExportedMetadataAsync(exportMetadataPath), platform);
|
|
20
|
+
debug(`Assets in build: ${JSON.stringify([...buildManifestHashSet], null, 2)}`);
|
|
21
|
+
debug(`Assets in exported bundle: ${JSON.stringify([...exportedAssetSet], null, 2)}`);
|
|
22
|
+
debug(`All assets resolved by Metro: ${JSON.stringify([...fullAssetHashSet], null, 2)}`);
|
|
23
|
+
const buildAssetsPlusExportedAssets = new Set(buildManifestHashSet);
|
|
24
|
+
exportedAssetSet.forEach((hash) => buildAssetsPlusExportedAssets.add(hash));
|
|
25
|
+
const missingAssets = [];
|
|
26
|
+
fullAssetHashSet.forEach((hash) => {
|
|
27
|
+
if (!buildAssetsPlusExportedAssets.has(hash)) {
|
|
28
|
+
const asset = fullAssetHashMap.get(hash);
|
|
29
|
+
asset === null || asset === void 0 ? void 0 : asset.fileHashes.forEach((fileHash, index) => {
|
|
30
|
+
if ((asset === null || asset === void 0 ? void 0 : asset.fileHashes[index]) === hash) {
|
|
31
|
+
missingAssets.push({
|
|
32
|
+
hash,
|
|
33
|
+
path: asset === null || asset === void 0 ? void 0 : asset.files[index],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return missingAssets;
|
|
40
|
+
}
|
|
41
|
+
exports.getMissingAssetsAsync = getMissingAssetsAsync;
|
|
42
|
+
/**
|
|
43
|
+
* Reads and returns the embedded manifest (app.manifest) for a build.
|
|
44
|
+
*
|
|
45
|
+
* @param buildManifestPath Path to the build folder
|
|
46
|
+
* @param platform Either 'android' or 'ios'
|
|
47
|
+
* @param projectRoot The project root path
|
|
48
|
+
* @returns the JSON structure of the manifest
|
|
49
|
+
*/
|
|
50
|
+
async function getBuildManifestAsync(buildManifestPath) {
|
|
51
|
+
const buildManifestString = await fs_1.promises.readFile(buildManifestPath, { encoding: 'utf-8' });
|
|
52
|
+
const buildManifest = JSON.parse(buildManifestString);
|
|
53
|
+
return buildManifest;
|
|
54
|
+
}
|
|
55
|
+
exports.getBuildManifestAsync = getBuildManifestAsync;
|
|
56
|
+
/**
|
|
57
|
+
* Extracts the set of asset hashes from a build manifest.
|
|
58
|
+
*
|
|
59
|
+
* @param buildManifest The build manifest
|
|
60
|
+
* @returns The set of asset hashes contained in the build manifest
|
|
61
|
+
*/
|
|
62
|
+
function getBuildManifestHashSet(buildManifest) {
|
|
63
|
+
var _a;
|
|
64
|
+
return new Set(((_a = buildManifest.assets) !== null && _a !== void 0 ? _a : []).map((asset) => asset.packagerHash));
|
|
65
|
+
}
|
|
66
|
+
exports.getBuildManifestHashSet = getBuildManifestHashSet;
|
|
67
|
+
/**
|
|
68
|
+
* Reads and extracts the asset dump for an exported bundle.
|
|
69
|
+
*
|
|
70
|
+
* @param assetMapPath The path to the exported assetmap.json.
|
|
71
|
+
* @returns The asset dump as an object.
|
|
72
|
+
*/
|
|
73
|
+
async function getFullAssetDumpAsync(assetMapPath) {
|
|
74
|
+
const assetMapString = await fs_1.promises.readFile(assetMapPath, { encoding: 'utf-8' });
|
|
75
|
+
const assetMap = new Map(Object.entries(JSON.parse(assetMapString)));
|
|
76
|
+
return assetMap;
|
|
77
|
+
}
|
|
78
|
+
exports.getFullAssetDumpAsync = getFullAssetDumpAsync;
|
|
79
|
+
/**
|
|
80
|
+
* Extracts the set of asset hashes from an asset dump.
|
|
81
|
+
*
|
|
82
|
+
* @param assetDump
|
|
83
|
+
* @returns The set of asset hashes in the asset dump, and a map of hash to asset
|
|
84
|
+
*/
|
|
85
|
+
function getFullAssetDumpHashSet(assetDump) {
|
|
86
|
+
const fullAssetHashSet = new Set();
|
|
87
|
+
const fullAssetHashMap = new Map();
|
|
88
|
+
assetDump.forEach((asset) => asset.fileHashes.forEach((hash) => {
|
|
89
|
+
fullAssetHashSet.add(hash);
|
|
90
|
+
fullAssetHashMap.set(hash, asset);
|
|
91
|
+
}));
|
|
92
|
+
return {
|
|
93
|
+
fullAssetHashSet,
|
|
94
|
+
fullAssetHashMap,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
exports.getFullAssetDumpHashSet = getFullAssetDumpHashSet;
|
|
98
|
+
/**
|
|
99
|
+
* Reads and extracts the metadata.json from an exported bundle.
|
|
100
|
+
*
|
|
101
|
+
* @param exportedMetadataPath Path to the exported metadata.json.
|
|
102
|
+
* @returns The metadata of the bundle.
|
|
103
|
+
*/
|
|
104
|
+
async function getExportedMetadataAsync(exportedMetadataPath) {
|
|
105
|
+
const metadataString = await fs_1.promises.readFile(exportedMetadataPath, { encoding: 'utf-8' });
|
|
106
|
+
const metadata = JSON.parse(metadataString);
|
|
107
|
+
return metadata;
|
|
108
|
+
}
|
|
109
|
+
exports.getExportedMetadataAsync = getExportedMetadataAsync;
|
|
110
|
+
/**
|
|
111
|
+
* Extracts the set of asset hashes from an exported bundle's metadata for a given platform.
|
|
112
|
+
*
|
|
113
|
+
* @param metadata The metadata from the exported bundle
|
|
114
|
+
* @param platform Either 'android' or 'ios'
|
|
115
|
+
* @returns the set of asset hashes
|
|
116
|
+
*/
|
|
117
|
+
function getExportedMetadataHashSet(metadata, platform) {
|
|
118
|
+
var _a;
|
|
119
|
+
const fileMetadata = platform === 'android' ? metadata.fileMetadata.android : metadata.fileMetadata.ios;
|
|
120
|
+
if (!fileMetadata) {
|
|
121
|
+
throw new Error(`Exported bundle was not exported for platform ${platform}`);
|
|
122
|
+
}
|
|
123
|
+
const assets = (_a = fileMetadata === null || fileMetadata === void 0 ? void 0 : fileMetadata.assets) !== null && _a !== void 0 ? _a : [];
|
|
124
|
+
// Asset paths in the export metadata are of the form 'assets/<hash string>'
|
|
125
|
+
return new Set(assets.map((asset) => asset.path.substring(7, asset.path.length)));
|
|
126
|
+
}
|
|
127
|
+
exports.getExportedMetadataHashSet = getExportedMetadataHashSet;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const validPlatforms: string[];
|
|
2
|
+
export type Platform = (typeof validPlatforms)[number];
|
|
3
|
+
export declare const isValidPlatform: (p: any) => boolean;
|
|
4
|
+
export interface ValidatedOptions {
|
|
5
|
+
exportedManifestPath: string;
|
|
6
|
+
buildManifestPath: string;
|
|
7
|
+
assetMapPath: string;
|
|
8
|
+
platform: Platform;
|
|
9
|
+
}
|
|
10
|
+
export type FullAssetDumpEntry = {
|
|
11
|
+
files: string[];
|
|
12
|
+
hash: string;
|
|
13
|
+
name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
fileHashes: string[];
|
|
16
|
+
};
|
|
17
|
+
export type FullAssetDump = Map<string, FullAssetDumpEntry>;
|
|
18
|
+
export type BuildManifestAsset = {
|
|
19
|
+
name: string;
|
|
20
|
+
type: string;
|
|
21
|
+
packagerHash: string;
|
|
22
|
+
};
|
|
23
|
+
export type BuildManifest = {
|
|
24
|
+
assets: BuildManifestAsset[];
|
|
25
|
+
} & {
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
};
|
|
28
|
+
export type ExportedMetadataAsset = {
|
|
29
|
+
path: string;
|
|
30
|
+
ext: string;
|
|
31
|
+
};
|
|
32
|
+
export type FileMetadata = {
|
|
33
|
+
bundle: string;
|
|
34
|
+
assets: ExportedMetadataAsset[];
|
|
35
|
+
};
|
|
36
|
+
export type ExportedMetadata = {
|
|
37
|
+
fileMetadata: {
|
|
38
|
+
ios?: FileMetadata;
|
|
39
|
+
android?: FileMetadata;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export type MissingAsset = {
|
|
43
|
+
hash: string;
|
|
44
|
+
path: string;
|
|
45
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Types for the options passed into the command
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.isValidPlatform = exports.validPlatforms = void 0;
|
|
5
|
+
exports.validPlatforms = ['android', 'ios'];
|
|
6
|
+
const isValidPlatform = (p) => exports.validPlatforms.includes(p);
|
|
7
|
+
exports.isValidPlatform = isValidPlatform;
|
package/build-cli/cli.js
CHANGED
|
@@ -4,11 +4,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const arg_1 = tslib_1.__importDefault(require("arg"));
|
|
6
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
8
|
+
const getenv_1 = require("getenv");
|
|
7
9
|
const Log = tslib_1.__importStar(require("./utils/log"));
|
|
10
|
+
// Setup before requiring `debug`.
|
|
11
|
+
if ((0, getenv_1.boolish)('EXPO_DEBUG', false)) {
|
|
12
|
+
debug_1.default.enable('expo-updates:*');
|
|
13
|
+
}
|
|
14
|
+
else if (debug_1.default.enabled('expo-updates:')) {
|
|
15
|
+
process.env.EXPO_DEBUG = '1';
|
|
16
|
+
}
|
|
8
17
|
const commands = {
|
|
9
18
|
// Add a new command here
|
|
10
19
|
'codesigning:generate': () => import('./generateCodeSigning.js').then((i) => i.generateCodeSigning),
|
|
11
20
|
'codesigning:configure': () => import('./configureCodeSigning.js').then((i) => i.configureCodeSigning),
|
|
21
|
+
'assets:verify': () => import('./assetsVerify.js').then((i) => i.expoAssetsVerify),
|
|
12
22
|
};
|
|
13
23
|
const args = (0, arg_1.default)({
|
|
14
24
|
// Types
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import arg from 'arg';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import { getMissingAssetsAsync } from './assetsVerifyAsync';
|
|
7
|
+
import {
|
|
8
|
+
isValidPlatform,
|
|
9
|
+
validPlatforms,
|
|
10
|
+
type ValidatedOptions,
|
|
11
|
+
type Platform,
|
|
12
|
+
} from './assetsVerifyTypes';
|
|
13
|
+
import { Command } from './cli';
|
|
14
|
+
import { assertArgs, getProjectRoot } from './utils/args';
|
|
15
|
+
import * as Log from './utils/log';
|
|
16
|
+
|
|
17
|
+
const debug = require('debug')('expo-updates:assets:verify') as typeof console.log;
|
|
18
|
+
|
|
19
|
+
export const expoAssetsVerify: Command = async (argv) => {
|
|
20
|
+
const args = assertArgs(
|
|
21
|
+
{
|
|
22
|
+
// Types
|
|
23
|
+
'--asset-map-path': String,
|
|
24
|
+
'--exported-manifest-path': String,
|
|
25
|
+
'--build-manifest-path': String,
|
|
26
|
+
'--platform': String,
|
|
27
|
+
'--help': Boolean,
|
|
28
|
+
// Aliases
|
|
29
|
+
'-a': '--asset-map-path',
|
|
30
|
+
'-e': '--exported-manifest-path',
|
|
31
|
+
'-b': '--build-manifest-path',
|
|
32
|
+
'-p': '--platform',
|
|
33
|
+
'-h': '--help',
|
|
34
|
+
},
|
|
35
|
+
argv ?? []
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (args['--help']) {
|
|
39
|
+
Log.exit(
|
|
40
|
+
chalk`
|
|
41
|
+
{bold Description}
|
|
42
|
+
Verify that all static files in an exported bundle are in either the export or an embedded bundle
|
|
43
|
+
|
|
44
|
+
{bold Usage}
|
|
45
|
+
{dim $} npx expo-updates assets:verify {dim <dir>}
|
|
46
|
+
|
|
47
|
+
Options
|
|
48
|
+
<dir> Directory of the Expo project. Default: Current working directory
|
|
49
|
+
-a, --asset-map-path <path> Path to the \`assetmap.json\` in an export produced by the command \`npx expo export --dump-assetmap\`
|
|
50
|
+
-e, --exported-manifest-path <path> Path to the \`metadata.json\` in an export produced by the command \`npx expo export --dump-assetmap\`
|
|
51
|
+
-b, --build-manifest-path <path> Path to the \`app.manifest\` file created by expo-updates in an Expo application build (either ios or android)
|
|
52
|
+
-p, --platform <platform> Options: ${JSON.stringify(validPlatforms)}
|
|
53
|
+
-h, --help Usage info
|
|
54
|
+
`,
|
|
55
|
+
0
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (async () => {
|
|
60
|
+
const projectRoot = getProjectRoot(args);
|
|
61
|
+
|
|
62
|
+
const validatedArgs = resolveOptions(projectRoot, args);
|
|
63
|
+
debug(`Validated params: ${JSON.stringify(validatedArgs, null, 2)}`);
|
|
64
|
+
|
|
65
|
+
const { buildManifestPath, exportedManifestPath, assetMapPath, platform } = validatedArgs;
|
|
66
|
+
|
|
67
|
+
const missingAssets = await getMissingAssetsAsync(
|
|
68
|
+
buildManifestPath,
|
|
69
|
+
exportedManifestPath,
|
|
70
|
+
assetMapPath,
|
|
71
|
+
platform
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (missingAssets.length > 0) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`${
|
|
77
|
+
missingAssets.length
|
|
78
|
+
} assets not found in either embedded manifest or in exported bundle:${JSON.stringify(
|
|
79
|
+
missingAssets,
|
|
80
|
+
null,
|
|
81
|
+
2
|
|
82
|
+
)}`
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
Log.log(`All resolved assets found in either embedded manifest or in exported bundle.`);
|
|
86
|
+
}
|
|
87
|
+
process.exit(0);
|
|
88
|
+
})().catch((e) => {
|
|
89
|
+
Log.log(`${e}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function resolveOptions(projectRoot: string, args: arg.Result<arg.Spec>): ValidatedOptions {
|
|
95
|
+
const exportedManifestPath = validatedPathFromArgument(
|
|
96
|
+
projectRoot,
|
|
97
|
+
args,
|
|
98
|
+
'--exported-manifest-path'
|
|
99
|
+
);
|
|
100
|
+
const buildManifestPath = validatedPathFromArgument(projectRoot, args, '--build-manifest-path');
|
|
101
|
+
const assetMapPath = validatedPathFromArgument(projectRoot, args, '--asset-map-path');
|
|
102
|
+
|
|
103
|
+
const platform = args['--platform'] as unknown as Platform;
|
|
104
|
+
if (!isValidPlatform(platform)) {
|
|
105
|
+
throw new Error(`Platform must be one of ${JSON.stringify(validPlatforms)}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
exportedManifestPath,
|
|
110
|
+
buildManifestPath,
|
|
111
|
+
assetMapPath,
|
|
112
|
+
platform,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function validatedPathFromArgument(projectRoot: string, args: arg.Result<arg.Spec>, key: string) {
|
|
117
|
+
const maybeRelativePath = args[key] as unknown as string;
|
|
118
|
+
if (!maybeRelativePath) {
|
|
119
|
+
throw new Error(`No value for ${key}`);
|
|
120
|
+
}
|
|
121
|
+
if (maybeRelativePath.indexOf('/') === 0) {
|
|
122
|
+
return maybeRelativePath; // absolute path
|
|
123
|
+
}
|
|
124
|
+
return path.resolve(projectRoot, maybeRelativePath);
|
|
125
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BuildManifest,
|
|
5
|
+
ExportedMetadata,
|
|
6
|
+
ExportedMetadataAsset,
|
|
7
|
+
FullAssetDump,
|
|
8
|
+
FullAssetDumpEntry,
|
|
9
|
+
MissingAsset,
|
|
10
|
+
Platform,
|
|
11
|
+
} from './assetsVerifyTypes';
|
|
12
|
+
|
|
13
|
+
const debug = require('debug')('expo-updates:assets:verify') as typeof console.log;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Finds any assets that will be missing from an app given a build and an exported update bundle.
|
|
17
|
+
*
|
|
18
|
+
* @param buildManifestPath Path to the `app.manifest` file created by expo-updates in an Expo application build (either ios or android)
|
|
19
|
+
* @param exportMetadataPath Path to the `metadata.json` in an export produced by the command `npx expo export --dump-assetmap`
|
|
20
|
+
* @param assetMapPath Path to the `assetmap.json` in an export produced by the command `npx expo export --dump-assetmap`
|
|
21
|
+
* @param projectRoot The project root path
|
|
22
|
+
* @returns An array containing any assets that are found in the Metro asset dump, but not included in either app.manifest or the exported bundle
|
|
23
|
+
*/
|
|
24
|
+
export async function getMissingAssetsAsync(
|
|
25
|
+
buildManifestPath: string,
|
|
26
|
+
exportMetadataPath: string,
|
|
27
|
+
assetMapPath: string,
|
|
28
|
+
platform: Platform
|
|
29
|
+
) {
|
|
30
|
+
const buildManifestHashSet = getBuildManifestHashSet(
|
|
31
|
+
await getBuildManifestAsync(buildManifestPath)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const fullAssetDump = await getFullAssetDumpAsync(assetMapPath);
|
|
35
|
+
const { fullAssetHashSet, fullAssetHashMap } = getFullAssetDumpHashSet(fullAssetDump);
|
|
36
|
+
|
|
37
|
+
const exportedAssetSet = getExportedMetadataHashSet(
|
|
38
|
+
await getExportedMetadataAsync(exportMetadataPath),
|
|
39
|
+
platform
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
debug(`Assets in build: ${JSON.stringify([...buildManifestHashSet], null, 2)}`);
|
|
43
|
+
debug(`Assets in exported bundle: ${JSON.stringify([...exportedAssetSet], null, 2)}`);
|
|
44
|
+
debug(`All assets resolved by Metro: ${JSON.stringify([...fullAssetHashSet], null, 2)}`);
|
|
45
|
+
|
|
46
|
+
const buildAssetsPlusExportedAssets = new Set(buildManifestHashSet);
|
|
47
|
+
exportedAssetSet.forEach((hash) => buildAssetsPlusExportedAssets.add(hash));
|
|
48
|
+
|
|
49
|
+
const missingAssets: MissingAsset[] = [];
|
|
50
|
+
|
|
51
|
+
fullAssetHashSet.forEach((hash) => {
|
|
52
|
+
if (!buildAssetsPlusExportedAssets.has(hash)) {
|
|
53
|
+
const asset = fullAssetHashMap.get(hash);
|
|
54
|
+
asset?.fileHashes.forEach((fileHash, index) => {
|
|
55
|
+
if (asset?.fileHashes[index] === hash) {
|
|
56
|
+
missingAssets.push({
|
|
57
|
+
hash,
|
|
58
|
+
path: asset?.files[index],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return missingAssets;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Reads and returns the embedded manifest (app.manifest) for a build.
|
|
70
|
+
*
|
|
71
|
+
* @param buildManifestPath Path to the build folder
|
|
72
|
+
* @param platform Either 'android' or 'ios'
|
|
73
|
+
* @param projectRoot The project root path
|
|
74
|
+
* @returns the JSON structure of the manifest
|
|
75
|
+
*/
|
|
76
|
+
export async function getBuildManifestAsync(buildManifestPath: string) {
|
|
77
|
+
const buildManifestString = await fs.readFile(buildManifestPath, { encoding: 'utf-8' });
|
|
78
|
+
const buildManifest: BuildManifest = JSON.parse(buildManifestString);
|
|
79
|
+
return buildManifest;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extracts the set of asset hashes from a build manifest.
|
|
84
|
+
*
|
|
85
|
+
* @param buildManifest The build manifest
|
|
86
|
+
* @returns The set of asset hashes contained in the build manifest
|
|
87
|
+
*/
|
|
88
|
+
export function getBuildManifestHashSet(buildManifest: BuildManifest) {
|
|
89
|
+
return new Set((buildManifest.assets ?? []).map((asset) => asset.packagerHash));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Reads and extracts the asset dump for an exported bundle.
|
|
94
|
+
*
|
|
95
|
+
* @param assetMapPath The path to the exported assetmap.json.
|
|
96
|
+
* @returns The asset dump as an object.
|
|
97
|
+
*/
|
|
98
|
+
export async function getFullAssetDumpAsync(assetMapPath: string) {
|
|
99
|
+
const assetMapString = await fs.readFile(assetMapPath, { encoding: 'utf-8' });
|
|
100
|
+
const assetMap: FullAssetDump = new Map(Object.entries(JSON.parse(assetMapString)));
|
|
101
|
+
return assetMap;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extracts the set of asset hashes from an asset dump.
|
|
106
|
+
*
|
|
107
|
+
* @param assetDump
|
|
108
|
+
* @returns The set of asset hashes in the asset dump, and a map of hash to asset
|
|
109
|
+
*/
|
|
110
|
+
export function getFullAssetDumpHashSet(assetDump: FullAssetDump) {
|
|
111
|
+
const fullAssetHashSet = new Set<string>();
|
|
112
|
+
const fullAssetHashMap = new Map<string, FullAssetDumpEntry>();
|
|
113
|
+
assetDump.forEach((asset) =>
|
|
114
|
+
asset.fileHashes.forEach((hash) => {
|
|
115
|
+
fullAssetHashSet.add(hash);
|
|
116
|
+
fullAssetHashMap.set(hash, asset);
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
return {
|
|
120
|
+
fullAssetHashSet,
|
|
121
|
+
fullAssetHashMap,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reads and extracts the metadata.json from an exported bundle.
|
|
127
|
+
*
|
|
128
|
+
* @param exportedMetadataPath Path to the exported metadata.json.
|
|
129
|
+
* @returns The metadata of the bundle.
|
|
130
|
+
*/
|
|
131
|
+
export async function getExportedMetadataAsync(exportedMetadataPath: string) {
|
|
132
|
+
const metadataString = await fs.readFile(exportedMetadataPath, { encoding: 'utf-8' });
|
|
133
|
+
const metadata: ExportedMetadata = JSON.parse(metadataString);
|
|
134
|
+
return metadata;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Extracts the set of asset hashes from an exported bundle's metadata for a given platform.
|
|
139
|
+
*
|
|
140
|
+
* @param metadata The metadata from the exported bundle
|
|
141
|
+
* @param platform Either 'android' or 'ios'
|
|
142
|
+
* @returns the set of asset hashes
|
|
143
|
+
*/
|
|
144
|
+
export function getExportedMetadataHashSet(metadata: ExportedMetadata, platform: Platform) {
|
|
145
|
+
const fileMetadata =
|
|
146
|
+
platform === 'android' ? metadata.fileMetadata.android : metadata.fileMetadata.ios;
|
|
147
|
+
if (!fileMetadata) {
|
|
148
|
+
throw new Error(`Exported bundle was not exported for platform ${platform}`);
|
|
149
|
+
}
|
|
150
|
+
const assets: ExportedMetadataAsset[] = fileMetadata?.assets ?? [];
|
|
151
|
+
// Asset paths in the export metadata are of the form 'assets/<hash string>'
|
|
152
|
+
return new Set(assets.map((asset) => asset.path.substring(7, asset.path.length)));
|
|
153
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Types for the options passed into the command
|
|
2
|
+
|
|
3
|
+
export const validPlatforms = ['android', 'ios'];
|
|
4
|
+
|
|
5
|
+
export type Platform = (typeof validPlatforms)[number];
|
|
6
|
+
|
|
7
|
+
export const isValidPlatform = (p: any) => validPlatforms.includes(p);
|
|
8
|
+
|
|
9
|
+
export interface ValidatedOptions {
|
|
10
|
+
exportedManifestPath: string;
|
|
11
|
+
buildManifestPath: string;
|
|
12
|
+
assetMapPath: string;
|
|
13
|
+
platform: Platform;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Types for the full asset map (npx expo export --dump-assets)
|
|
17
|
+
|
|
18
|
+
export type FullAssetDumpEntry = {
|
|
19
|
+
files: string[];
|
|
20
|
+
hash: string;
|
|
21
|
+
name: string;
|
|
22
|
+
type: string;
|
|
23
|
+
fileHashes: string[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type FullAssetDump = Map<string, FullAssetDumpEntry>;
|
|
27
|
+
|
|
28
|
+
// Types for the embedded manifest created by expo-updates
|
|
29
|
+
|
|
30
|
+
export type BuildManifestAsset = {
|
|
31
|
+
name: string;
|
|
32
|
+
type: string;
|
|
33
|
+
packagerHash: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type BuildManifest = {
|
|
37
|
+
assets: BuildManifestAsset[];
|
|
38
|
+
} & { [key: string]: any };
|
|
39
|
+
|
|
40
|
+
// Types for the metadata exported by npx expo export
|
|
41
|
+
|
|
42
|
+
export type ExportedMetadataAsset = {
|
|
43
|
+
path: string;
|
|
44
|
+
ext: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type FileMetadata = {
|
|
48
|
+
bundle: string;
|
|
49
|
+
assets: ExportedMetadataAsset[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type ExportedMetadata = {
|
|
53
|
+
fileMetadata: {
|
|
54
|
+
ios?: FileMetadata;
|
|
55
|
+
android?: FileMetadata;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Type for the missing asset array returned by getMissingAssetsAsync
|
|
60
|
+
|
|
61
|
+
export type MissingAsset = {
|
|
62
|
+
hash: string;
|
|
63
|
+
path: string;
|
|
64
|
+
};
|
package/cli/cli.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import arg from 'arg';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
+
import Debug from 'debug';
|
|
5
|
+
import { boolish } from 'getenv';
|
|
4
6
|
|
|
5
7
|
import * as Log from './utils/log';
|
|
6
8
|
|
|
9
|
+
// Setup before requiring `debug`.
|
|
10
|
+
if (boolish('EXPO_DEBUG', false)) {
|
|
11
|
+
Debug.enable('expo-updates:*');
|
|
12
|
+
} else if (Debug.enabled('expo-updates:')) {
|
|
13
|
+
process.env.EXPO_DEBUG = '1';
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export type Command = (argv?: string[]) => void;
|
|
8
17
|
|
|
9
18
|
const commands: { [command: string]: () => Promise<Command> } = {
|
|
@@ -12,6 +21,7 @@ const commands: { [command: string]: () => Promise<Command> } = {
|
|
|
12
21
|
import('./generateCodeSigning.js').then((i) => i.generateCodeSigning),
|
|
13
22
|
'codesigning:configure': () =>
|
|
14
23
|
import('./configureCodeSigning.js').then((i) => i.configureCodeSigning),
|
|
24
|
+
'assets:verify': () => import('./assetsVerify.js').then((i) => i.expoAssetsVerify),
|
|
15
25
|
};
|
|
16
26
|
|
|
17
27
|
const args = arg(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-updates",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.10",
|
|
4
4
|
"description": "Fetches and manages remotely-hosted assets and updates to your app's JS bundle.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -58,11 +58,12 @@
|
|
|
58
58
|
"expo-module-scripts": "^3.0.0",
|
|
59
59
|
"express": "^4.17.2",
|
|
60
60
|
"form-data": "^4.0.0",
|
|
61
|
+
"fs-extra": "~8.1.0",
|
|
61
62
|
"memfs": "^3.2.0",
|
|
62
63
|
"xstate": "^4.37.2"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
66
|
"expo": "*"
|
|
66
67
|
},
|
|
67
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "c4956bb901ddaffc2ad04ed22fd85aa36b8f3435"
|
|
68
69
|
}
|