@qvac/sdk 0.20.1 → 0.21.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/NOTICE +76 -13
- package/README.md +39 -5
- package/dist/examples/advisory-model-fit.d.ts +17 -16
- package/dist/examples/advisory-model-fit.js +18 -18
- package/dist/examples/asr/parakeet-indic-conformer-filesystem.js +3 -3
- package/dist/examples/asr/parakeet-unified-coreml.d.ts +2 -0
- package/dist/examples/asr/parakeet-unified-coreml.js +113 -0
- package/dist/examples/assess-model-fit.js +9 -4
- package/dist/examples/model-fit-ui/server.js +5 -2
- package/dist/examples/rpc-inference.d.ts +2 -0
- package/dist/examples/rpc-inference.js +51 -0
- package/dist/examples/rpc-server.d.ts +2 -0
- package/dist/examples/rpc-server.js +32 -0
- package/dist/examples/tools/llamacpp-deferred-tools.d.ts +2 -0
- package/dist/examples/tools/llamacpp-deferred-tools.js +77 -0
- package/dist/examples/tts/audio8.js +4 -0
- package/dist/examples/tts/moss-dialogue.d.ts +2 -0
- package/dist/examples/tts/moss-dialogue.js +63 -0
- package/dist/examples/tts/moss.d.ts +2 -0
- package/dist/examples/tts/moss.js +71 -0
- package/dist/scripts/contract/build-contract.d.ts +2 -1
- package/dist/scripts/contract/build-contract.js +4 -1
- package/dist/scripts/contract/build-numeric-constants.d.ts +15 -0
- package/dist/scripts/contract/build-numeric-constants.js +13 -0
- package/dist/scripts/contract/method-shapes.d.ts +3 -0
- package/dist/scripts/contract/method-shapes.js +3 -0
- package/dist/scripts/enforce-inference-versions.d.ts +18 -0
- package/dist/scripts/enforce-inference-versions.js +158 -0
- package/dist/scripts/generate-bare-kit-runtimes.d.ts +2 -0
- package/dist/scripts/generate-bare-kit-runtimes.js +106 -0
- package/dist/src/client/api/assess-model-fit.d.ts +5 -4
- package/dist/src/client/api/assess-model-fit.js +24 -7
- package/dist/src/client/api/bci-transcribe.d.ts +4 -1
- package/dist/src/client/api/get-system-resources.d.ts +8 -0
- package/dist/src/client/api/get-system-resources.js +8 -0
- package/dist/src/client/api/index.d.ts +1 -0
- package/dist/src/client/api/index.js +1 -0
- package/dist/src/client/api/rpc-server.d.ts +20 -0
- package/dist/src/client/api/rpc-server.js +45 -0
- package/dist/src/client/api/text-to-speech.d.ts +9 -0
- package/dist/src/client/api/text-to-speech.js +9 -0
- package/dist/src/client/api/transcribe.d.ts +21 -15
- package/dist/src/client/api/transcribe.js +65 -20
- package/dist/src/client/rpc/rpc-error.js +11 -7
- package/dist/src/commands/bundle/entry-gen.d.ts +2 -2
- package/dist/src/commands/bundle/entry-gen.js +8 -4
- package/dist/src/commands/bundle/index.d.ts +20 -0
- package/dist/src/commands/bundle/index.js +88 -12
- package/dist/src/commands/bundle/plugins.js +1 -1
- package/dist/src/commands/command-logger.d.ts +6 -0
- package/dist/src/commands/command-logger.js +12 -0
- package/dist/src/commands/host-prebuilds/index.d.ts +71 -0
- package/dist/src/commands/host-prebuilds/index.js +227 -0
- package/dist/src/commands/host-prebuilds/package-manager.d.ts +54 -0
- package/dist/src/commands/host-prebuilds/package-manager.js +278 -0
- package/dist/src/commands/index.d.ts +10 -1
- package/dist/src/commands/index.js +6 -0
- package/dist/src/commands/verify/abi.d.ts +22 -2
- package/dist/src/commands/verify/abi.js +49 -27
- package/dist/src/commands/verify/addon-source.d.ts +22 -0
- package/dist/src/commands/verify/addon-source.js +69 -3
- package/dist/src/commands/verify/bare-kit-runtime.d.ts +38 -0
- package/dist/src/commands/verify/bare-kit-runtime.js +201 -0
- package/dist/src/commands/verify/bare-kit-runtimes.d.ts +12 -0
- package/dist/src/commands/verify/bare-kit-runtimes.js +108 -0
- package/dist/src/commands/verify/bundle-source.js +16 -15
- package/dist/src/commands/verify/engines-advice.d.ts +73 -0
- package/dist/src/commands/verify/engines-advice.js +271 -0
- package/dist/src/commands/verify/index.d.ts +22 -0
- package/dist/src/commands/verify/index.js +151 -13
- package/dist/src/commands/verify/node-modules-source.js +43 -39
- package/dist/src/commands/verify/prebuilds.d.ts +19 -0
- package/dist/src/commands/verify/prebuilds.js +52 -13
- package/dist/src/expo/plugins/index.d.ts +2 -0
- package/dist/src/expo/plugins/withMobileBundle.d.ts +15 -5
- package/dist/src/expo/plugins/withMobileBundle.js +68 -42
- package/dist/src/expo/plugins/withQvacSDK.d.ts +7 -1
- package/dist/src/expo/plugins/withQvacSDK.js +6 -3
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.js +4 -2
- package/dist/src/schemas/sdk-errors-client.d.ts +2 -0
- package/dist/src/schemas/sdk-errors-client.js +16 -0
- package/dist/src/schemas/sdk-errors-server.d.ts +1 -0
- package/dist/src/schemas/sdk-errors-server.js +5 -0
- package/dist/src/utils/errors-client.d.ts +15 -0
- package/dist/src/utils/errors-client.js +26 -0
- package/dist/src/utils/errors-server.d.ts +1 -0
- package/dist/src/utils/errors-server.js +1 -0
- package/dist/src/utils/mcp-adapter.js +7 -1
- package/dist/src/utils/tool-helpers.d.ts +4 -0
- package/dist/src/utils/tool-helpers.js +2 -0
- package/dist/src/worker/ggml-rpc-server-provider.d.ts +2 -0
- package/dist/src/worker/ggml-rpc-server-provider.js +1 -0
- package/dist/src/worker/rpc-server-provider.d.ts +3 -0
- package/dist/src/worker/rpc-server-provider.js +1 -0
- package/package.json +30 -25
- package/dist/scripts/enforce-inference-peer-dependencies.d.ts +0 -2
- package/dist/scripts/enforce-inference-peer-dependencies.js +0 -39
- package/dist/src/deprecate.d.ts +0 -2
- package/dist/src/deprecate.js +0 -9
- package/dist/src/worker/legacy-worker-entry.d.ts +0 -2
- package/dist/src/worker/legacy-worker-entry.js +0 -3
- package/dist/src/worker/worker-core.d.ts +0 -7
- package/dist/src/worker/worker-core.js +0 -8
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
import { detectPackageManager } from '../host-prebuilds/package-manager.js';
|
|
3
|
+
import { formatRuntimeSource } from './abi.js';
|
|
4
|
+
import { findReactNativeBareKitUpgrade, NETWORK_TIMEOUT_MS, REACT_NATIVE_BARE_KIT } from './bare-kit-runtime.js';
|
|
5
|
+
import { REACT_NATIVE_BARE_KIT_RUNTIMES } from './bare-kit-runtimes.js';
|
|
6
|
+
const REGISTRY_URL = 'https://registry.npmjs.org';
|
|
7
|
+
/** The registry's abbreviated metadata lists `engines` per version at a fraction of the full size. */
|
|
8
|
+
const ABBREVIATED_METADATA_TYPE = 'application/vnd.npm.install-v1+json';
|
|
9
|
+
export async function fetchPackageMetadata(name) {
|
|
10
|
+
const url = `${REGISTRY_URL}/${encodeURIComponent(name)}`;
|
|
11
|
+
const response = await fetch(url, {
|
|
12
|
+
headers: { accept: ABBREVIATED_METADATA_TYPE },
|
|
13
|
+
signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS)
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok)
|
|
16
|
+
throw new Error(`GET ${url} returned HTTP ${response.status}`);
|
|
17
|
+
return (await response.json());
|
|
18
|
+
}
|
|
19
|
+
export function formatOverrideSnippet(manager, name, version, parents) {
|
|
20
|
+
let manifest;
|
|
21
|
+
switch (manager) {
|
|
22
|
+
case 'pnpm':
|
|
23
|
+
manifest = {
|
|
24
|
+
pnpm: {
|
|
25
|
+
overrides: Object.fromEntries(parents.length > 0 ? parents.map((p) => [`${p}>${name}`, version]) : [[name, version]])
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
break;
|
|
29
|
+
case 'yarn':
|
|
30
|
+
manifest = {
|
|
31
|
+
resolutions: Object.fromEntries(parents.length > 0 ? parents.map((p) => [`${p}/${name}`, version]) : [[name, version]])
|
|
32
|
+
};
|
|
33
|
+
break;
|
|
34
|
+
case 'bun':
|
|
35
|
+
// bun only supports top-level overrides.
|
|
36
|
+
manifest = { overrides: { [name]: version } };
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
manifest = {
|
|
40
|
+
overrides: Object.fromEntries(parents.length > 0 ? parents.map((p) => [p, { [name]: version }]) : [[name, version]])
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return JSON.stringify(manifest, null, 2).replace(/\{\n\s+("[^"\n]+": "[^"\n]+")\n\s+\}/g, '{ $1 }');
|
|
44
|
+
}
|
|
45
|
+
function groupFailures(failures) {
|
|
46
|
+
const byName = new Map();
|
|
47
|
+
for (const failure of failures) {
|
|
48
|
+
let entry = byName.get(failure.name);
|
|
49
|
+
if (entry === undefined) {
|
|
50
|
+
entry = { versions: new Set(), ranges: new Set() };
|
|
51
|
+
byName.set(failure.name, entry);
|
|
52
|
+
}
|
|
53
|
+
if (failure.version !== undefined)
|
|
54
|
+
entry.versions.add(failure.version);
|
|
55
|
+
entry.ranges.add(failure.enginesBare);
|
|
56
|
+
}
|
|
57
|
+
return byName;
|
|
58
|
+
}
|
|
59
|
+
function findParents(name, packages) {
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
const parents = [];
|
|
62
|
+
for (const record of packages) {
|
|
63
|
+
const range = record.dependencies[name];
|
|
64
|
+
if (range === undefined)
|
|
65
|
+
continue;
|
|
66
|
+
const key = `${record.name}\0${range}`;
|
|
67
|
+
if (seen.has(key))
|
|
68
|
+
continue;
|
|
69
|
+
seen.add(key);
|
|
70
|
+
parents.push({ name: record.name, range });
|
|
71
|
+
}
|
|
72
|
+
return parents.sort((a, b) => a.name.localeCompare(b.name));
|
|
73
|
+
}
|
|
74
|
+
function runsOn(runtimeVersion, engines) {
|
|
75
|
+
const range = engines?.bare;
|
|
76
|
+
if (typeof range !== 'string')
|
|
77
|
+
return true;
|
|
78
|
+
return semver.validRange(range) !== null && semver.satisfies(runtimeVersion, range);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Newest release that runs on the runtime and that every parent accepts,
|
|
82
|
+
* else one that at least one parent accepts. Without known parents, only the
|
|
83
|
+
* installed release line (`^installed`) is considered. A version no
|
|
84
|
+
* dependent accepts would only trade one failure for another, so none is
|
|
85
|
+
* suggested then.
|
|
86
|
+
*/
|
|
87
|
+
export function pickOverrideVersion(metadata, runtimeVersion, installedVersions, parentRanges) {
|
|
88
|
+
const compatible = Object.entries(metadata.versions)
|
|
89
|
+
.filter(([version, manifest]) => {
|
|
90
|
+
if (semver.valid(version) === null || semver.prerelease(version) !== null)
|
|
91
|
+
return false;
|
|
92
|
+
return runsOn(runtimeVersion, manifest.engines);
|
|
93
|
+
})
|
|
94
|
+
.map(([version]) => version)
|
|
95
|
+
.sort(semver.rcompare);
|
|
96
|
+
if (parentRanges.length > 0) {
|
|
97
|
+
const acceptedByAll = compatible.find((version) => parentRanges.every((range) => semver.satisfies(version, range)));
|
|
98
|
+
if (acceptedByAll !== undefined)
|
|
99
|
+
return acceptedByAll;
|
|
100
|
+
return (compatible.find((version) => parentRanges.some((range) => semver.satisfies(version, range))) ?? null);
|
|
101
|
+
}
|
|
102
|
+
const lines = installedVersions.filter((v) => semver.valid(v) !== null).map(releaseLine);
|
|
103
|
+
return (compatible.find((version) => lines.some((range) => semver.satisfies(version, range))) ?? null);
|
|
104
|
+
}
|
|
105
|
+
/** Versions `^version` would treat as compatible, including older ones: 1.4.0 -> 1.x, 0.20.3 -> 0.20.x. */
|
|
106
|
+
function releaseLine(version) {
|
|
107
|
+
const major = semver.major(version);
|
|
108
|
+
const minor = semver.minor(version);
|
|
109
|
+
if (major > 0)
|
|
110
|
+
return `${major}.x`;
|
|
111
|
+
if (minor > 0)
|
|
112
|
+
return `0.${minor}.x`;
|
|
113
|
+
return `0.0.${semver.patch(version)}`;
|
|
114
|
+
}
|
|
115
|
+
function requiredBareVersion(ranges) {
|
|
116
|
+
let required = null;
|
|
117
|
+
for (const range of ranges) {
|
|
118
|
+
const min = semver.validRange(range) === null ? null : semver.minVersion(range);
|
|
119
|
+
if (min !== null && (required === null || semver.gt(min.version, required))) {
|
|
120
|
+
required = min.version;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return required ?? 'unknown';
|
|
124
|
+
}
|
|
125
|
+
function newestTableEntry() {
|
|
126
|
+
const versions = Object.keys(REACT_NATIVE_BARE_KIT_RUNTIMES).sort(semver.rcompare);
|
|
127
|
+
const newest = versions[0];
|
|
128
|
+
return newest === undefined
|
|
129
|
+
? null
|
|
130
|
+
: { version: newest, ...REACT_NATIVE_BARE_KIT_RUNTIMES[newest] };
|
|
131
|
+
}
|
|
132
|
+
function buildUpgrade(runtime, ranges, requiredBare) {
|
|
133
|
+
if (runtime.source === 'react-native-bare-kit' && runtime.packageVersion !== undefined) {
|
|
134
|
+
const target = findReactNativeBareKitUpgrade(runtime.packageVersion, ranges);
|
|
135
|
+
if (target !== null) {
|
|
136
|
+
return {
|
|
137
|
+
upgrade: {
|
|
138
|
+
packageName: REACT_NATIVE_BARE_KIT,
|
|
139
|
+
from: runtime.packageVersion,
|
|
140
|
+
to: target.version,
|
|
141
|
+
bare: target.bare
|
|
142
|
+
},
|
|
143
|
+
upgradeHint: `Upgrade ${REACT_NATIVE_BARE_KIT} to ${target.version} or newer ` +
|
|
144
|
+
`(embeds bare-kit ${target.bareKit}, Bare ${target.bare}).`
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const newest = newestTableEntry();
|
|
148
|
+
return {
|
|
149
|
+
upgrade: null,
|
|
150
|
+
upgradeHint: `No ${REACT_NATIVE_BARE_KIT} release in the built-in table embeds Bare ${requiredBare} or newer` +
|
|
151
|
+
(newest === null
|
|
152
|
+
? '.'
|
|
153
|
+
: `; the newest known, ${newest.version}, embeds Bare ${newest.bare}. Check for a newer release.`)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (runtime.source === 'bare-runtime' || runtime.source === 'bare') {
|
|
157
|
+
return {
|
|
158
|
+
upgrade: null,
|
|
159
|
+
upgradeHint: `Upgrade ${runtime.source} to ${requiredBare} or newer.`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
upgrade: null,
|
|
164
|
+
upgradeHint: `The runtime version comes from ${runtime.source}; raise it to ${requiredBare} or newer if the target runtime allows it.`
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export async function buildEnginesAdvice(options) {
|
|
168
|
+
const { projectRoot, hosts, runtime, failures, packages, network = true, onProgress, fetchPackageMetadata: fetchOne = fetchPackageMetadata } = options;
|
|
169
|
+
const grouped = groupFailures(failures);
|
|
170
|
+
const allRanges = [...grouped.values()].flatMap((entry) => [...entry.ranges]);
|
|
171
|
+
const requiredBare = requiredBareVersion(allRanges);
|
|
172
|
+
const { manager: packageManager } = await detectPackageManager(projectRoot);
|
|
173
|
+
const names = [...grouped.keys()].sort();
|
|
174
|
+
if (network && names.length > 0) {
|
|
175
|
+
onProgress?.(`Looking up releases of ${names.join(', ')} on registry.npmjs.org that run on Bare ` +
|
|
176
|
+
`${runtime.version} (${names.length} request${names.length === 1 ? '' : 's'}, timeout ${NETWORK_TIMEOUT_MS / 1000}s)...`);
|
|
177
|
+
}
|
|
178
|
+
const overrides = await Promise.all(names.map(async (name) => {
|
|
179
|
+
const entry = grouped.get(name);
|
|
180
|
+
const installedVersions = [...entry.versions].sort();
|
|
181
|
+
const parents = findParents(name, packages);
|
|
182
|
+
const override = {
|
|
183
|
+
name,
|
|
184
|
+
installedVersions,
|
|
185
|
+
enginesBare: [...entry.ranges].sort(),
|
|
186
|
+
version: null,
|
|
187
|
+
parents: parents.map((parent) => ({ ...parent, satisfied: false })),
|
|
188
|
+
snippet: null
|
|
189
|
+
};
|
|
190
|
+
if (!network) {
|
|
191
|
+
override.lookupSkipped = true;
|
|
192
|
+
return override;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const metadata = await fetchOne(name);
|
|
196
|
+
override.version = pickOverrideVersion(metadata, runtime.version, installedVersions, parents.map((parent) => parent.range));
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
override.lookupError = error instanceof Error ? error.message : String(error);
|
|
200
|
+
return override;
|
|
201
|
+
}
|
|
202
|
+
const version = override.version;
|
|
203
|
+
if (version === null)
|
|
204
|
+
return override;
|
|
205
|
+
override.parents = parents.map((parent) => ({
|
|
206
|
+
...parent,
|
|
207
|
+
satisfied: semver.satisfies(version, parent.range)
|
|
208
|
+
}));
|
|
209
|
+
// Scoping only matters when some dependent must keep its current copy.
|
|
210
|
+
const accepting = override.parents.filter((parent) => parent.satisfied);
|
|
211
|
+
override.snippet = formatOverrideSnippet(packageManager, name, version, accepting.length === override.parents.length ? [] : accepting.map((parent) => parent.name));
|
|
212
|
+
return override;
|
|
213
|
+
}));
|
|
214
|
+
return {
|
|
215
|
+
hosts,
|
|
216
|
+
runtime,
|
|
217
|
+
requiredBare,
|
|
218
|
+
...buildUpgrade(runtime, allRanges, requiredBare),
|
|
219
|
+
overrides,
|
|
220
|
+
packageManager
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const MANIFEST_FIELD = {
|
|
224
|
+
npm: '"overrides"',
|
|
225
|
+
pnpm: '"pnpm.overrides"',
|
|
226
|
+
yarn: '"resolutions"',
|
|
227
|
+
bun: '"overrides"'
|
|
228
|
+
};
|
|
229
|
+
export function formatEnginesAdvice(advice) {
|
|
230
|
+
const lines = [
|
|
231
|
+
` Fix for ${advice.hosts.join(', ')}:`,
|
|
232
|
+
` Runtime: Bare ${advice.runtime.version} (from ${formatRuntimeSource(advice.runtime)})`,
|
|
233
|
+
...advice.overrides.map((override) => ` Requires: ${override.name}@${override.installedVersions.join(', ') || 'unknown'} needs Bare ${override.enginesBare.join(' and ')}`),
|
|
234
|
+
` Option 1: ${advice.upgradeHint}`
|
|
235
|
+
];
|
|
236
|
+
for (const override of advice.overrides) {
|
|
237
|
+
const installed = override.installedVersions.join(', ') || 'unknown';
|
|
238
|
+
if (override.lookupSkipped === true) {
|
|
239
|
+
lines.push(` Option 2 (${override.name}): pin an older release; run again with network lookups ` +
|
|
240
|
+
'enabled (without --offline) to find one that runs on this Bare.');
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (override.version === null) {
|
|
244
|
+
const acceptedBy = override.parents.length === 0
|
|
245
|
+
? ` on the ${installed} release line`
|
|
246
|
+
: ` that ${override.parents.map((p) => `${p.name} (${p.range})`).join(', ')} accept${override.parents.length === 1 ? 's' : ''}`;
|
|
247
|
+
lines.push(` Option 2 (${override.name}): no release that runs on Bare ${advice.runtime.version}${acceptedBy} was found` +
|
|
248
|
+
(override.lookupError === undefined
|
|
249
|
+
? '; upgrade the runtime.'
|
|
250
|
+
: ` (${override.lookupError}).`));
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const field = advice.packageManager === null
|
|
254
|
+
? '"overrides" (npm/bun), "pnpm.overrides" or "resolutions" (yarn)'
|
|
255
|
+
: MANIFEST_FIELD[advice.packageManager];
|
|
256
|
+
lines.push(` Option 2 (${override.name}): pin ${installed} -> ${override.version} with ${field} in your app's package.json:`);
|
|
257
|
+
for (const line of (override.snippet ?? '').split('\n'))
|
|
258
|
+
lines.push(` ${line}`);
|
|
259
|
+
const rejected = override.parents.filter((parent) => !parent.satisfied);
|
|
260
|
+
if (rejected.length > 0) {
|
|
261
|
+
lines.push(` ${rejected.map((p) => `${p.name} (${p.range})`).join(', ')} ` +
|
|
262
|
+
`${rejected.length === 1 ? 'does' : 'do'} not accept ${override.version}; ` +
|
|
263
|
+
'those copies are left as they are.');
|
|
264
|
+
}
|
|
265
|
+
if (advice.packageManager === 'bun' && override.parents.length > 0) {
|
|
266
|
+
lines.push(' bun applies overrides to every copy in the tree.');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
lines.push('');
|
|
270
|
+
return lines;
|
|
271
|
+
}
|
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import { type AddonSourceKind, type NativeAddon } from './addon-source.js';
|
|
2
2
|
import { type MissingPrebuildIssue } from './prebuilds.js';
|
|
3
3
|
import { type AbiIssue, type BareRuntimeResolution } from './abi.js';
|
|
4
|
+
import { type FetchText, type ProgressFn } from './bare-kit-runtime.js';
|
|
5
|
+
import { type EnginesAdvice, type FetchPackageMetadata } from './engines-advice.js';
|
|
4
6
|
export interface VerifyBundleOptions {
|
|
5
7
|
projectRoot: string;
|
|
6
8
|
addonsSource: string;
|
|
7
9
|
hosts: string[];
|
|
8
10
|
bareRuntimeVersion?: string;
|
|
9
11
|
configPath?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Allow GitHub and npm registry requests: GitHub for a react-native-bare-kit
|
|
14
|
+
* release newer than the built-in table, the registry only after a mismatch
|
|
15
|
+
* is found, to suggest an override. Defaults to true.
|
|
16
|
+
*/
|
|
17
|
+
network?: boolean;
|
|
18
|
+
/** Called before any step that waits on the network. */
|
|
19
|
+
onProgress?: ProgressFn;
|
|
20
|
+
/** Test seams for the network lookups. */
|
|
21
|
+
fetchText?: FetchText;
|
|
22
|
+
fetchPackageMetadata?: FetchPackageMetadata;
|
|
23
|
+
}
|
|
24
|
+
export interface RuntimeGroup {
|
|
25
|
+
hosts: string[];
|
|
26
|
+
resolution: BareRuntimeResolution;
|
|
10
27
|
}
|
|
11
28
|
export interface InvalidSourceIssue {
|
|
12
29
|
code: 'invalid-source';
|
|
@@ -48,9 +65,14 @@ export interface VerifyBundleResult {
|
|
|
48
65
|
resolvedAddonsSource: string;
|
|
49
66
|
sourceKind: AddonSourceKind | null;
|
|
50
67
|
hosts: string[];
|
|
68
|
+
/** Runtime of the first entry in `runtimes`: the mobile one when any host is mobile. */
|
|
51
69
|
runtime: BareRuntimeResolution | null;
|
|
70
|
+
/** Mobile and desktop hosts run different Bare builds, so each is resolved separately. */
|
|
71
|
+
runtimes?: RuntimeGroup[];
|
|
52
72
|
addons: NativeAddon[];
|
|
53
73
|
issues: VerifyBundleIssue[];
|
|
74
|
+
/** How to fix each runtime that fails an engines.bare range. */
|
|
75
|
+
advice?: EnginesAdvice[];
|
|
54
76
|
}
|
|
55
77
|
export declare function verifyBundle(options: VerifyBundleOptions): Promise<VerifyBundleResult>;
|
|
56
78
|
export declare function hasErrors(result: VerifyBundleResult): boolean;
|
|
@@ -4,8 +4,10 @@ import { resolveConfigFileInProject, loadConfigFromPath } from '../../client/con
|
|
|
4
4
|
import { createCollectDiagnostics, formatAddonId } from './addon-source.js';
|
|
5
5
|
import { collectAddonsFromBundle, InvalidBundleSourceError } from './bundle-source.js';
|
|
6
6
|
import { collectAddonsFromNodeModules, InvalidNodeModulesSourceError } from './node-modules-source.js';
|
|
7
|
-
import { checkPrebuilds } from './prebuilds.js';
|
|
7
|
+
import { checkPrebuilds, isMobileHost } from './prebuilds.js';
|
|
8
8
|
import { checkAbi, formatConfigLabel, normalizeVersion, resolveBareRuntime } from './abi.js';
|
|
9
|
+
import { isReactNativeBareKitInstalled, resolveMobileBareRuntime } from './bare-kit-runtime.js';
|
|
10
|
+
import { buildEnginesAdvice, formatEnginesAdvice } from './engines-advice.js';
|
|
9
11
|
export async function verifyBundle(options) {
|
|
10
12
|
const { projectRoot, addonsSource, hosts, bareRuntimeVersion, configPath } = options;
|
|
11
13
|
const resolvedAddonsSource = path.isAbsolute(addonsSource)
|
|
@@ -119,6 +121,9 @@ export async function verifyBundle(options) {
|
|
|
119
121
|
};
|
|
120
122
|
}
|
|
121
123
|
const diagnostics = createCollectDiagnostics();
|
|
124
|
+
if (sourceKind === 'node-modules') {
|
|
125
|
+
options.onProgress?.(`Scanning ${resolvedAddonsSource} for native addons and engines.bare...`);
|
|
126
|
+
}
|
|
122
127
|
let addons;
|
|
123
128
|
try {
|
|
124
129
|
addons =
|
|
@@ -178,20 +183,57 @@ export async function verifyBundle(options) {
|
|
|
178
183
|
issues.push(...prebuildIssues);
|
|
179
184
|
}
|
|
180
185
|
let runtime = null;
|
|
186
|
+
let runtimes;
|
|
187
|
+
const advice = [];
|
|
181
188
|
if (invalidRuntimeVersion === null) {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
const explicit = bareRuntimeVersion !== undefined
|
|
190
|
+
? { version: bareRuntimeVersion, source: 'flag' }
|
|
191
|
+
: configRuntimeVersion !== undefined
|
|
192
|
+
? { version: configRuntimeVersion, source: 'config' }
|
|
193
|
+
: null;
|
|
194
|
+
runtimes = await resolveRuntimeGroups({
|
|
195
|
+
projectRoot,
|
|
196
|
+
hosts,
|
|
197
|
+
explicit,
|
|
198
|
+
network: options.network,
|
|
199
|
+
onProgress: options.onProgress,
|
|
200
|
+
fetchText: options.fetchText
|
|
201
|
+
});
|
|
202
|
+
runtime = runtimes[0]?.resolution ?? null;
|
|
203
|
+
const packages = enginesPackages(diagnostics.packages);
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
for (const group of runtimes) {
|
|
206
|
+
const groupIssues = checkAbi({
|
|
207
|
+
addons: addonsLinkedOn(addons, group.hosts),
|
|
208
|
+
runtime: group.resolution,
|
|
209
|
+
packages
|
|
210
|
+
}).filter((issue) => {
|
|
211
|
+
if (seen.has(issue.message))
|
|
212
|
+
return false;
|
|
213
|
+
seen.add(issue.message);
|
|
214
|
+
return true;
|
|
215
|
+
});
|
|
216
|
+
issues.push(...groupIssues);
|
|
217
|
+
const failures = groupIssues.flatMap((issue) => issue.code === 'abi-mismatch' || issue.code === 'engines-mismatch'
|
|
218
|
+
? [
|
|
219
|
+
failureOf(issue.code === 'abi-mismatch' ? issue.addon : issue.package, issue.enginesBare)
|
|
220
|
+
]
|
|
221
|
+
: []);
|
|
222
|
+
if (failures.length > 0 && group.resolution.resolved) {
|
|
223
|
+
advice.push(await buildEnginesAdvice({
|
|
224
|
+
projectRoot,
|
|
225
|
+
hosts: group.hosts,
|
|
226
|
+
runtime: group.resolution.runtime,
|
|
227
|
+
failures,
|
|
228
|
+
packages: diagnostics.packages,
|
|
229
|
+
network: options.network,
|
|
230
|
+
onProgress: options.onProgress,
|
|
231
|
+
fetchPackageMetadata: options.fetchPackageMetadata
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
190
234
|
}
|
|
191
|
-
runtime = await resolveBareRuntime(runtimeOptions);
|
|
192
|
-
issues.push(...checkAbi({ addons, runtime }));
|
|
193
235
|
}
|
|
194
|
-
|
|
236
|
+
const result = {
|
|
195
237
|
addonsSource,
|
|
196
238
|
resolvedAddonsSource,
|
|
197
239
|
sourceKind,
|
|
@@ -200,6 +242,85 @@ export async function verifyBundle(options) {
|
|
|
200
242
|
addons,
|
|
201
243
|
issues
|
|
202
244
|
};
|
|
245
|
+
if (runtimes !== undefined)
|
|
246
|
+
result.runtimes = runtimes;
|
|
247
|
+
if (advice.length > 0)
|
|
248
|
+
result.advice = advice;
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
/** `name@version` ids as built by `formatAddonId`; scoped names keep their leading `@`. */
|
|
252
|
+
function failureOf(id, enginesBare) {
|
|
253
|
+
const at = id.lastIndexOf('@');
|
|
254
|
+
if (at <= 0)
|
|
255
|
+
return { name: id, enginesBare };
|
|
256
|
+
const version = id.slice(at + 1);
|
|
257
|
+
return version === 'unknown'
|
|
258
|
+
? { name: id.slice(0, at), enginesBare }
|
|
259
|
+
: { name: id.slice(0, at), version, enginesBare };
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Addons a runtime group loads: those the bundle links on one of its hosts.
|
|
263
|
+
* Without link information (`linkedHosts` unset), every addon counts, as in
|
|
264
|
+
* `checkPrebuilds`.
|
|
265
|
+
*/
|
|
266
|
+
function addonsLinkedOn(addons, hosts) {
|
|
267
|
+
return addons.filter((addon) => addon.linkedHosts === undefined || addon.linkedHosts.some((host) => hosts.includes(host)));
|
|
268
|
+
}
|
|
269
|
+
function enginesPackages(records) {
|
|
270
|
+
const byId = new Map();
|
|
271
|
+
for (const record of records) {
|
|
272
|
+
if (record.isAddon || record.enginesBare === undefined)
|
|
273
|
+
continue;
|
|
274
|
+
const id = `${record.name}@${record.version ?? 'unknown'}`;
|
|
275
|
+
if (byId.has(id))
|
|
276
|
+
continue;
|
|
277
|
+
const pkg = { name: record.name, enginesBare: record.enginesBare };
|
|
278
|
+
if (record.version !== undefined)
|
|
279
|
+
pkg.version = record.version;
|
|
280
|
+
byId.set(id, pkg);
|
|
281
|
+
}
|
|
282
|
+
return [...byId.values()];
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Mobile hosts run the Bare build inside react-native-bare-kit; desktop hosts
|
|
286
|
+
* run `bare-runtime` (or `bare`). An explicit version applies to every host.
|
|
287
|
+
* Without react-native-bare-kit installed, mobile hosts fall back to the
|
|
288
|
+
* desktop detection so projects that only bundle for mobile keep a runtime.
|
|
289
|
+
*/
|
|
290
|
+
async function resolveRuntimeGroups(options) {
|
|
291
|
+
const { projectRoot, hosts, explicit } = options;
|
|
292
|
+
if (explicit !== null) {
|
|
293
|
+
return [
|
|
294
|
+
{
|
|
295
|
+
hosts,
|
|
296
|
+
resolution: await resolveBareRuntime({
|
|
297
|
+
projectRoot,
|
|
298
|
+
explicitVersion: explicit.version,
|
|
299
|
+
explicitSource: explicit.source
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
];
|
|
303
|
+
}
|
|
304
|
+
const mobileHosts = hosts.filter(isMobileHost);
|
|
305
|
+
const desktopHosts = hosts.filter((host) => !isMobileHost(host));
|
|
306
|
+
const groups = [];
|
|
307
|
+
if (mobileHosts.length > 0) {
|
|
308
|
+
groups.push({
|
|
309
|
+
hosts: mobileHosts,
|
|
310
|
+
resolution: isReactNativeBareKitInstalled(projectRoot)
|
|
311
|
+
? await resolveMobileBareRuntime({
|
|
312
|
+
projectRoot,
|
|
313
|
+
network: options.network,
|
|
314
|
+
onProgress: options.onProgress,
|
|
315
|
+
fetchText: options.fetchText
|
|
316
|
+
})
|
|
317
|
+
: await resolveBareRuntime({ projectRoot })
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
if (desktopHosts.length > 0) {
|
|
321
|
+
groups.push({ hosts: desktopHosts, resolution: await resolveBareRuntime({ projectRoot }) });
|
|
322
|
+
}
|
|
323
|
+
return groups;
|
|
203
324
|
}
|
|
204
325
|
function buildInvalidPackageJsonIssues(records) {
|
|
205
326
|
return records.map((record) => {
|
|
@@ -262,6 +383,9 @@ export function formatVerifyBundleResult(result) {
|
|
|
262
383
|
sections.push('');
|
|
263
384
|
sections.push(...formatMissingPrebuilds(result.issues));
|
|
264
385
|
sections.push(...formatAbiMismatches(result.issues));
|
|
386
|
+
sections.push(...formatEnginesMismatches(result.issues));
|
|
387
|
+
for (const advice of result.advice ?? [])
|
|
388
|
+
sections.push(...formatEnginesAdvice(advice));
|
|
265
389
|
sections.push(...formatInvalidRuntimeVersions(result.issues));
|
|
266
390
|
sections.push(...formatMalformedEnginesBare(result.issues));
|
|
267
391
|
sections.push(...formatConfigLoadFailed(result.issues));
|
|
@@ -288,7 +412,9 @@ function formatMissingPrebuilds(issues) {
|
|
|
288
412
|
return [];
|
|
289
413
|
const lines = [' Missing prebuild:'];
|
|
290
414
|
for (const issue of matches) {
|
|
291
|
-
|
|
415
|
+
const pin = issue.platformPackage;
|
|
416
|
+
const needs = pin === undefined ? '' : ` (needs "${pin.name}": "${pin.version}" in package.json)`;
|
|
417
|
+
lines.push(` - ${issue.addon} for ${issue.host}${needs}`);
|
|
292
418
|
}
|
|
293
419
|
lines.push('');
|
|
294
420
|
return lines;
|
|
@@ -305,6 +431,18 @@ function formatAbiMismatches(issues) {
|
|
|
305
431
|
lines.push('');
|
|
306
432
|
return lines;
|
|
307
433
|
}
|
|
434
|
+
function formatEnginesMismatches(issues) {
|
|
435
|
+
const matches = issues.filter((issue) => issue.code === 'engines-mismatch');
|
|
436
|
+
if (matches.length === 0)
|
|
437
|
+
return [];
|
|
438
|
+
const lines = [' engines.bare mismatch:'];
|
|
439
|
+
for (const issue of matches) {
|
|
440
|
+
lines.push(` - ${issue.package} requires bare ${issue.enginesBare}, ` +
|
|
441
|
+
`runtime is ${issue.runtimeVersion}`);
|
|
442
|
+
}
|
|
443
|
+
lines.push('');
|
|
444
|
+
return lines;
|
|
445
|
+
}
|
|
308
446
|
function formatUnknownRuntime(issues) {
|
|
309
447
|
const matches = issues.filter((issue) => issue.code === 'unknown-runtime-version');
|
|
310
448
|
if (matches.length === 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fsp } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { deduplicateAddons, readAddonPackageJson } from './addon-source.js';
|
|
3
|
+
import { createLimiter, deduplicateAddons, FS_CONCURRENCY, readAddonPackageJson } from './addon-source.js';
|
|
4
4
|
export class InvalidNodeModulesSourceError extends Error {
|
|
5
5
|
nodeModulesPath;
|
|
6
6
|
constructor(nodeModulesPath, cause) {
|
|
@@ -14,63 +14,67 @@ export class InvalidNodeModulesSourceError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
export async function collectAddonsFromNodeModules(options) {
|
|
16
16
|
const { nodeModulesRoot, diagnostics } = options;
|
|
17
|
+
const limit = createLimiter(FS_CONCURRENCY);
|
|
18
|
+
const results = await walkNodeModules(nodeModulesRoot, limit, true);
|
|
17
19
|
const addons = [];
|
|
18
|
-
|
|
20
|
+
for (const result of results) {
|
|
21
|
+
if (result.record !== undefined && diagnostics !== undefined) {
|
|
22
|
+
diagnostics.packages.push(result.record);
|
|
23
|
+
}
|
|
24
|
+
if (result.isAddon && result.addon) {
|
|
25
|
+
addons.push(result.addon);
|
|
26
|
+
}
|
|
27
|
+
else if (result.invalid !== undefined && diagnostics !== undefined) {
|
|
28
|
+
diagnostics.invalidPackageJsons.push(result.invalid);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
19
31
|
return deduplicateAddons(addons);
|
|
20
32
|
}
|
|
21
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Directory reads run in parallel under `limit`; results are concatenated in
|
|
35
|
+
* directory-entry order so the output does not depend on I/O timing.
|
|
36
|
+
*/
|
|
37
|
+
async function walkNodeModules(nodeModulesDir, limit, isRoot) {
|
|
22
38
|
let entries;
|
|
23
39
|
try {
|
|
24
|
-
entries = await fsp.readdir(nodeModulesDir, { withFileTypes: true });
|
|
40
|
+
entries = await limit(() => fsp.readdir(nodeModulesDir, { withFileTypes: true }));
|
|
25
41
|
}
|
|
26
42
|
catch (error) {
|
|
27
43
|
if (isRoot)
|
|
28
44
|
throw new InvalidNodeModulesSourceError(nodeModulesDir, error);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
for (const entry of entries) {
|
|
32
|
-
if (entry.name.startsWith('.'))
|
|
33
|
-
continue;
|
|
34
|
-
if (!isPackageEntry(entry))
|
|
35
|
-
continue;
|
|
36
|
-
if (entry.name.startsWith('@')) {
|
|
37
|
-
await walkScopeDirectory(path.join(nodeModulesDir, entry.name), addons, diagnostics);
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
await visitPackageDirectory(path.join(nodeModulesDir, entry.name), entry.name, addons, diagnostics);
|
|
45
|
+
return [];
|
|
41
46
|
}
|
|
47
|
+
const visits = entries
|
|
48
|
+
.filter((entry) => !entry.name.startsWith('.') && isPackageEntry(entry))
|
|
49
|
+
.map((entry) => entry.name.startsWith('@')
|
|
50
|
+
? walkScopeDirectory(path.join(nodeModulesDir, entry.name), limit)
|
|
51
|
+
: visitPackageDirectory(path.join(nodeModulesDir, entry.name), entry.name, limit));
|
|
52
|
+
return (await Promise.all(visits)).flat();
|
|
42
53
|
}
|
|
43
|
-
async function walkScopeDirectory(scopeDir,
|
|
54
|
+
async function walkScopeDirectory(scopeDir, limit) {
|
|
44
55
|
const scope = path.basename(scopeDir);
|
|
45
56
|
let entries;
|
|
46
57
|
try {
|
|
47
|
-
entries = await fsp.readdir(scopeDir, { withFileTypes: true });
|
|
58
|
+
entries = await limit(() => fsp.readdir(scopeDir, { withFileTypes: true }));
|
|
48
59
|
}
|
|
49
60
|
catch {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
for (const entry of entries) {
|
|
53
|
-
if (entry.name.startsWith('.'))
|
|
54
|
-
continue;
|
|
55
|
-
if (!isPackageEntry(entry))
|
|
56
|
-
continue;
|
|
57
|
-
await visitPackageDirectory(path.join(scopeDir, entry.name), `${scope}/${entry.name}`, addons, diagnostics);
|
|
61
|
+
return [];
|
|
58
62
|
}
|
|
63
|
+
const visits = entries
|
|
64
|
+
.filter((entry) => !entry.name.startsWith('.') && isPackageEntry(entry))
|
|
65
|
+
.map((entry) => visitPackageDirectory(path.join(scopeDir, entry.name), `${scope}/${entry.name}`, limit));
|
|
66
|
+
return (await Promise.all(visits)).flat();
|
|
59
67
|
}
|
|
60
68
|
function isPackageEntry(entry) {
|
|
61
69
|
return entry.isDirectory() || entry.isSymbolicLink();
|
|
62
70
|
}
|
|
63
|
-
async function visitPackageDirectory(packageDir, packageName,
|
|
64
|
-
const result = await
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
diagnostics.invalidPackageJsons.push(result.invalid);
|
|
73
|
-
}
|
|
74
|
-
const nestedNodeModules = path.join(packageDir, 'node_modules');
|
|
75
|
-
await walkNodeModules(nestedNodeModules, addons, diagnostics, false);
|
|
71
|
+
async function visitPackageDirectory(packageDir, packageName, limit) {
|
|
72
|
+
const [result, nested] = await Promise.all([
|
|
73
|
+
limit(() => readAddonPackageJson({
|
|
74
|
+
packageJsonPath: path.join(packageDir, 'package.json'),
|
|
75
|
+
expectedName: packageName
|
|
76
|
+
})),
|
|
77
|
+
walkNodeModules(path.join(packageDir, 'node_modules'), limit, false)
|
|
78
|
+
]);
|
|
79
|
+
return [result, ...nested];
|
|
76
80
|
}
|
|
@@ -6,7 +6,20 @@ export interface MissingPrebuildIssue {
|
|
|
6
6
|
host: string;
|
|
7
7
|
message: string;
|
|
8
8
|
packageRoot: string;
|
|
9
|
+
/**
|
|
10
|
+
* The per-platform package to declare for this mobile host, at the exact
|
|
11
|
+
* version it must be installed at. Set when the addon's `#host-addon` map
|
|
12
|
+
* names one and it is either not installed or installed at another version.
|
|
13
|
+
* Never set for desktop hosts, whose platform packages install as the
|
|
14
|
+
* addon's os/cpu-filtered optionalDependencies.
|
|
15
|
+
*/
|
|
16
|
+
platformPackage?: PlatformPackagePin;
|
|
9
17
|
}
|
|
18
|
+
export interface PlatformPackagePin {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function isMobileHost(host: string): boolean;
|
|
10
23
|
export interface CheckPrebuildsOptions {
|
|
11
24
|
addon: NativeAddon;
|
|
12
25
|
hosts: string[];
|
|
@@ -19,6 +32,12 @@ export interface CheckPrebuildsOptions {
|
|
|
19
32
|
export interface PrebuildLocation {
|
|
20
33
|
hostDir: string;
|
|
21
34
|
platformPackage?: string;
|
|
35
|
+
/**
|
|
36
|
+
* The installed platform package's version when it differs from the
|
|
37
|
+
* addon's. Platform packages are version-locked to their addon, so a
|
|
38
|
+
* prebuild in a mismatched one does not count.
|
|
39
|
+
*/
|
|
40
|
+
mismatchedVersion?: string;
|
|
22
41
|
}
|
|
23
42
|
/**
|
|
24
43
|
* Directories to search for an addon's `<host>` prebuild, in precedence order:
|