pulse-updates 1.2.0 → 1.2.2
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/README.md +4 -1
- package/package.json +1 -1
- package/scripts/publish.mjs +74 -5
package/README.md
CHANGED
|
@@ -392,7 +392,10 @@ npx pulse-updates register-capabilities --platform <ios|android> [--api-key <key
|
|
|
392
392
|
|
|
393
393
|
Run it at build time so the server can warn when a future JS-only update references native modules
|
|
394
394
|
the installed binary doesn't have. When the release pipeline already has the exact embedded bundle,
|
|
395
|
-
pass it with `--bundle <path
|
|
395
|
+
pass it with `--bundle <path>`. On Android the command automatically reuses the newest Gradle packager
|
|
396
|
+
source map (the exact pre-Hermes graph retained under `android/app/build/intermediates/sourcemaps`), so
|
|
397
|
+
a normal Gradle release build needs no extra path. If no embedded or generated source exists, it builds
|
|
398
|
+
an unminified, scan-only Metro graph; that fallback is never uploaded or used as the app's launch bundle.
|
|
396
399
|
|
|
397
400
|
### Configuration File
|
|
398
401
|
|
package/package.json
CHANGED
package/scripts/publish.mjs
CHANGED
|
@@ -36,7 +36,21 @@ export function extractNativeModulesFromSource(source) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export function extractNativeModules(bundlePath) {
|
|
39
|
-
|
|
39
|
+
if (isHermesBytecode(bundlePath)) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Capability source is Hermes bytecode: ${bundlePath}. ` +
|
|
42
|
+
'Pass the pre-Hermes bundle or its index.android.bundle.packager.map source map.'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const raw = fs.readFileSync(bundlePath, 'utf8');
|
|
46
|
+
if (bundlePath.endsWith('.map')) {
|
|
47
|
+
const sourceMap = JSON.parse(raw);
|
|
48
|
+
if (!Array.isArray(sourceMap.sourcesContent)) {
|
|
49
|
+
throw new Error(`Capability source map has no sourcesContent: ${bundlePath}`);
|
|
50
|
+
}
|
|
51
|
+
return extractNativeModulesFromSource(sourceMap.sourcesContent.filter(Boolean).join('\n'));
|
|
52
|
+
}
|
|
53
|
+
return extractNativeModulesFromSource(raw);
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
/**
|
|
@@ -235,9 +249,10 @@ function ensureHermesc(platform) {
|
|
|
235
249
|
/**
|
|
236
250
|
* Create JS bundle using Metro
|
|
237
251
|
*/
|
|
238
|
-
function createBundle(platform, bundleDir, entryFile = 'index.ts') {
|
|
252
|
+
function createBundle(platform, bundleDir, entryFile = 'index.ts', options = {}) {
|
|
239
253
|
const bundleOutput = path.join(bundleDir, platform, 'index.bundle');
|
|
240
254
|
const assetsDir = path.join(bundleDir, platform, 'assets');
|
|
255
|
+
const minify = options.minify ?? true;
|
|
241
256
|
|
|
242
257
|
// Clean and create directories
|
|
243
258
|
fs.rmSync(path.join(bundleDir, platform), { recursive: true, force: true });
|
|
@@ -250,11 +265,13 @@ function createBundle(platform, bundleDir, entryFile = 'index.ts') {
|
|
|
250
265
|
'npx react-native bundle',
|
|
251
266
|
`--platform ${platform}`,
|
|
252
267
|
'--dev false',
|
|
268
|
+
options.resetCache ? '--reset-cache' : null,
|
|
253
269
|
`--entry-file ${entryFile}`,
|
|
254
270
|
`--bundle-output ${bundleOutput}`,
|
|
255
271
|
`--assets-dest ${assetsDir}`,
|
|
256
|
-
|
|
257
|
-
|
|
272
|
+
options.sourceMap ? `--sourcemap-output ${bundleOutput}.map` : null,
|
|
273
|
+
`--minify ${minify}`,
|
|
274
|
+
].filter(Boolean).join(' ');
|
|
258
275
|
|
|
259
276
|
execSync(cmd, { stdio: 'inherit' });
|
|
260
277
|
|
|
@@ -1129,14 +1146,66 @@ export function resolveCapabilitiesBundle(options, config, bundleFactory = creat
|
|
|
1129
1146
|
return resolved;
|
|
1130
1147
|
}
|
|
1131
1148
|
|
|
1149
|
+
if (config.platform === 'android') {
|
|
1150
|
+
const generated = findLatestGeneratedAndroidCapabilitySource(config.projectRoot || process.cwd());
|
|
1151
|
+
if (generated) {
|
|
1152
|
+
log(` Using the latest Gradle-generated Android capability source: ${generated}`, colors.dim);
|
|
1153
|
+
return generated;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1132
1157
|
logStep('1/2', `Building the ${config.platform} production bundle for capability discovery...`);
|
|
1133
1158
|
return bundleFactory(
|
|
1134
1159
|
config.platform,
|
|
1135
1160
|
path.resolve(config.bundleDir || './dist'),
|
|
1136
|
-
config.entryFile || 'index.ts'
|
|
1161
|
+
config.entryFile || 'index.ts',
|
|
1162
|
+
// Capability discovery scans source text and does not ship this bundle. Keep
|
|
1163
|
+
// it unminified: worklet transformers may create short-lived virtual modules
|
|
1164
|
+
// that Metro's minifier tries to hash after they have already been removed.
|
|
1165
|
+
// The release pipeline should still pass --bundle whenever it has the exact
|
|
1166
|
+
// embedded artifact, which remains the authoritative path.
|
|
1167
|
+
{ minify: false, resetCache: true, sourceMap: true }
|
|
1137
1168
|
);
|
|
1138
1169
|
}
|
|
1139
1170
|
|
|
1171
|
+
/** Find the pre-Hermes graph most recently produced by a React Native Android Gradle task. */
|
|
1172
|
+
export function findLatestGeneratedAndroidCapabilitySource(projectRoot = process.cwd()) {
|
|
1173
|
+
const buildRoot = path.join(projectRoot, 'android', 'app', 'build');
|
|
1174
|
+
if (!fs.existsSync(buildRoot)) return null;
|
|
1175
|
+
|
|
1176
|
+
const candidates = [];
|
|
1177
|
+
const variantOf = (candidatePath) => {
|
|
1178
|
+
const normal = candidatePath.replaceAll('\\', '/');
|
|
1179
|
+
return normal.match(/\/react\/([^/]+)\/index\.android\.bundle/)?.[1]
|
|
1180
|
+
|| normal.match(/\/intermediates\/assets\/([^/]+)\//)?.[1]
|
|
1181
|
+
|| null;
|
|
1182
|
+
};
|
|
1183
|
+
const visit = (directory) => {
|
|
1184
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
1185
|
+
const fullPath = path.join(directory, entry.name);
|
|
1186
|
+
if (entry.isDirectory()) visit(fullPath);
|
|
1187
|
+
else if (entry.isFile() && entry.name === 'index.android.bundle.packager.map') {
|
|
1188
|
+
candidates.push({ path: fullPath, modifiedAt: fs.statSync(fullPath).mtimeMs, map: true, variant: variantOf(fullPath) });
|
|
1189
|
+
} else if (entry.isFile() && entry.name === 'index.android.bundle' && !isHermesBytecode(fullPath)) {
|
|
1190
|
+
candidates.push({ path: fullPath, modifiedAt: fs.statSync(fullPath).mtimeMs, map: false, variant: variantOf(fullPath) });
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
visit(buildRoot);
|
|
1195
|
+
candidates.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
1196
|
+
const latest = candidates[0];
|
|
1197
|
+
if (!latest) return null;
|
|
1198
|
+
|
|
1199
|
+
// Gradle writes the minified bundle after its packager map. Choosing only by mtime therefore
|
|
1200
|
+
// selects the bundle, where R8/Metro have erased the canonical native binding call sites, and
|
|
1201
|
+
// reports a dangerous empty capability set. For the newest build variant, the packager map is
|
|
1202
|
+
// authoritative because it retains every original module in sourcesContent.
|
|
1203
|
+
const matchingMap = latest.variant
|
|
1204
|
+
? candidates.find((candidate) => candidate.map && candidate.variant === latest.variant)
|
|
1205
|
+
: null;
|
|
1206
|
+
return matchingMap?.path || latest.path;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1140
1209
|
|
|
1141
1210
|
/**
|
|
1142
1211
|
* The experiments an app is running, and how their split is holding.
|