@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
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { parseBuiltinSpecifier } from './plugins.js';
|
|
2
|
-
export function generateWorkerEntry(pluginSpecifiers, sdkName, resolveImport = (specifier) => specifier) {
|
|
2
|
+
export function generateWorkerEntry(pluginSpecifiers, sdkName, resolveImport = (specifier) => specifier, rpcServerProvider) {
|
|
3
3
|
const imports = [];
|
|
4
4
|
const registrations = [];
|
|
5
5
|
let varIndex = 0;
|
|
6
|
+
if (rpcServerProvider) {
|
|
7
|
+
imports.push(`import { registerRpcServerProvider } from ${JSON.stringify(resolveImport(`${sdkName}/rpc-server-provider`))};`, `import rpcServerProvider from ${JSON.stringify(resolveImport(rpcServerProvider))};`);
|
|
8
|
+
registrations.push('registerRpcServerProvider(rpcServerProvider);');
|
|
9
|
+
}
|
|
6
10
|
for (const specifier of pluginSpecifiers) {
|
|
7
11
|
const builtin = parseBuiltinSpecifier(specifier, sdkName);
|
|
8
12
|
if (builtin) {
|
|
@@ -46,9 +50,9 @@ if (hasRPCConfig) {
|
|
|
46
50
|
}
|
|
47
51
|
`;
|
|
48
52
|
}
|
|
49
|
-
export function generateWorkerEntries(pluginSpecifiers, sdkName, resolveBundleImport) {
|
|
53
|
+
export function generateWorkerEntries(pluginSpecifiers, sdkName, resolveBundleImport, rpcServerProvider) {
|
|
50
54
|
return {
|
|
51
|
-
runtimeEntry: generateWorkerEntry(pluginSpecifiers, sdkName),
|
|
52
|
-
bundleEntry: generateWorkerEntry(pluginSpecifiers, sdkName, resolveBundleImport)
|
|
55
|
+
runtimeEntry: generateWorkerEntry(pluginSpecifiers, sdkName, undefined, rpcServerProvider),
|
|
56
|
+
bundleEntry: generateWorkerEntry(pluginSpecifiers, sdkName, resolveBundleImport, rpcServerProvider)
|
|
53
57
|
};
|
|
54
58
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type HostPrebuildPackage } from '../host-prebuilds/index.js';
|
|
1
2
|
export interface BundleSdkOptions {
|
|
2
3
|
projectRoot?: string | undefined;
|
|
3
4
|
configPath?: string | undefined;
|
|
@@ -6,6 +7,23 @@ export interface BundleSdkOptions {
|
|
|
6
7
|
defer?: string[] | undefined;
|
|
7
8
|
quiet?: boolean | undefined;
|
|
8
9
|
verbose?: boolean | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Install the addon platform packages the bundle needs for its mobile hosts
|
|
12
|
+
* with the project's package manager (see `ensureHostPrebuilds`), then
|
|
13
|
+
* bundle again. Off by default: without it, bundling never changes
|
|
14
|
+
* package.json or node_modules. When the install is refused, the bundle and
|
|
15
|
+
* its manifest are still written before `HostPrebuildsInstallRefusedError`
|
|
16
|
+
* is thrown.
|
|
17
|
+
*/
|
|
18
|
+
installMissingPrebuilds?: boolean | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Check the bundled packages' engines.bare against the Bare runtime of each
|
|
21
|
+
* host and warn on a mismatch. Defaults to true; callers that run
|
|
22
|
+
* `verifyBundle` on the result can turn it off.
|
|
23
|
+
*/
|
|
24
|
+
checkEngines?: boolean | undefined;
|
|
25
|
+
/** Allow network lookups during the engines check. Defaults to true. */
|
|
26
|
+
network?: boolean | undefined;
|
|
9
27
|
}
|
|
10
28
|
export interface BundleSdkResult {
|
|
11
29
|
bundlePath: string;
|
|
@@ -15,6 +33,8 @@ export interface BundleSdkResult {
|
|
|
15
33
|
worker: string;
|
|
16
34
|
};
|
|
17
35
|
manifestPath: string;
|
|
36
|
+
/** Platform packages `installMissingPrebuilds` installed; empty when it is off. */
|
|
37
|
+
installedPrebuilds: HostPrebuildPackage[];
|
|
18
38
|
}
|
|
19
39
|
export declare function bundleSdk(options?: BundleSdkOptions): Promise<BundleSdkResult>;
|
|
20
40
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,13 +3,18 @@ import path from 'node:path';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { DEFAULT_HOSTS, DEFAULT_SDK_NAME } from './constants.js';
|
|
5
5
|
import { CONFIG_CANDIDATES, resolveConfigForProject } from '../../client/config-loader/resolve-config.node.js';
|
|
6
|
-
import {
|
|
7
|
-
import { BareImportsMapNotFoundError } from '../../utils/errors-client.js';
|
|
6
|
+
import { createCommandLogger } from '../command-logger.js';
|
|
7
|
+
import { BareImportsMapNotFoundError, HostPrebuildsInstallRefusedError } from '../../utils/errors-client.js';
|
|
8
8
|
import { resolvePluginSpecifiers, parseBuiltinSpecifier } from './plugins.js';
|
|
9
9
|
import { generateWorkerEntries } from './entry-gen.js';
|
|
10
10
|
import { runBarePack } from './bare-pack.js';
|
|
11
11
|
import { generateAddonsManifest } from './manifest.js';
|
|
12
12
|
import { createSdkImportResolver } from './resolve-sdk-import.js';
|
|
13
|
+
import { installMissingHostPrebuilds } from '../host-prebuilds/index.js';
|
|
14
|
+
import { collectAddonsFromBundle } from '../verify/bundle-source.js';
|
|
15
|
+
import { verifyBundle } from '../verify/index.js';
|
|
16
|
+
import { formatRuntimeSource } from '../verify/abi.js';
|
|
17
|
+
import { formatEnginesAdvice } from '../verify/engines-advice.js';
|
|
13
18
|
const require = createRequire(import.meta.url);
|
|
14
19
|
function resolveSdkPath(projectRoot, explicitSdkPath) {
|
|
15
20
|
if (explicitSdkPath) {
|
|
@@ -49,14 +54,43 @@ function resolveImportsMapPath(sdkPath, sdkName) {
|
|
|
49
54
|
}
|
|
50
55
|
throw new BareImportsMapNotFoundError(sdkName, importsMapPath);
|
|
51
56
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
const ENGINES_ISSUE_CODES = new Set(['abi-mismatch', 'engines-mismatch']);
|
|
58
|
+
async function checkBundleEngines(options) {
|
|
59
|
+
const { projectRoot, bundlePath, hosts, configPath, network, logger } = options;
|
|
60
|
+
logger.info('\n🔎 Checking engines.bare against the Bare runtime of each host...');
|
|
61
|
+
const result = await verifyBundle({
|
|
62
|
+
projectRoot,
|
|
63
|
+
addonsSource: bundlePath,
|
|
64
|
+
hosts,
|
|
65
|
+
...(configPath !== undefined ? { configPath } : {}),
|
|
66
|
+
...(network !== undefined ? { network } : {}),
|
|
67
|
+
onProgress: (message) => logger.info(` ${message}`)
|
|
68
|
+
});
|
|
69
|
+
const checkedHosts = [];
|
|
70
|
+
for (const group of result.runtimes ?? []) {
|
|
71
|
+
const label = group.hosts.join(', ');
|
|
72
|
+
if (group.resolution.resolved) {
|
|
73
|
+
checkedHosts.push(...group.hosts);
|
|
74
|
+
logger.info(` ${label}: Bare ${group.resolution.runtime.version} (from ${formatRuntimeSource(group.resolution.runtime)})`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
logger.warn(`engines.bare not checked for ${label}: ${group.resolution.error.reason}`);
|
|
78
|
+
}
|
|
55
79
|
}
|
|
56
|
-
|
|
57
|
-
|
|
80
|
+
const mismatches = result.issues.filter((issue) => ENGINES_ISSUE_CODES.has(issue.code) && issue.level === 'error');
|
|
81
|
+
if (mismatches.length === 0) {
|
|
82
|
+
if (checkedHosts.length > 0) {
|
|
83
|
+
logger.info(` No engines.bare mismatches for ${checkedHosts.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
58
86
|
}
|
|
59
|
-
|
|
87
|
+
const lines = ['The bundle contains packages that require a newer Bare runtime:'];
|
|
88
|
+
for (const issue of mismatches)
|
|
89
|
+
lines.push(` - ${issue.message}`);
|
|
90
|
+
lines.push('');
|
|
91
|
+
for (const advice of result.advice ?? [])
|
|
92
|
+
lines.push(...formatEnginesAdvice(advice));
|
|
93
|
+
logger.warn(lines.join('\n').trimEnd());
|
|
60
94
|
}
|
|
61
95
|
export async function bundleSdk(options = {}) {
|
|
62
96
|
const startTime = Date.now();
|
|
@@ -94,7 +128,7 @@ export async function bundleSdk(options = {}) {
|
|
|
94
128
|
await fsp.mkdir(outputDir, { recursive: true });
|
|
95
129
|
logger.info('\n📝 Generating worker entry...');
|
|
96
130
|
const resolveSdkImport = createSdkImportResolver(sdkPath, sdkName);
|
|
97
|
-
const { runtimeEntry, bundleEntry } = generateWorkerEntries(pluginSpecifiers, sdkName, resolveSdkImport);
|
|
131
|
+
const { runtimeEntry, bundleEntry } = generateWorkerEntries(pluginSpecifiers, sdkName, resolveSdkImport, config.rpcServerProvider);
|
|
98
132
|
await fsp.writeFile(entryPath, runtimeEntry, 'utf8');
|
|
99
133
|
logger.info(` Created: ${path.relative(projectRoot, entryPath)}`);
|
|
100
134
|
logger.info(` Using: ${path.relative(projectRoot, importsMapPath)}`);
|
|
@@ -103,9 +137,11 @@ export async function bundleSdk(options = {}) {
|
|
|
103
137
|
if (deferModules.length > 0) {
|
|
104
138
|
logger.debug(` Deferred: ${deferModules.join(', ')}`);
|
|
105
139
|
}
|
|
140
|
+
let installedPrebuilds = [];
|
|
141
|
+
let installRefused;
|
|
106
142
|
try {
|
|
107
143
|
await fsp.writeFile(bundleEntryPath, bundleEntry, 'utf8');
|
|
108
|
-
|
|
144
|
+
const barePackOptions = {
|
|
109
145
|
entryPath: bundleEntryPath,
|
|
110
146
|
outputPath: bundlePath,
|
|
111
147
|
hosts,
|
|
@@ -113,7 +149,34 @@ export async function bundleSdk(options = {}) {
|
|
|
113
149
|
deferModules,
|
|
114
150
|
quiet: options.quiet === true,
|
|
115
151
|
logger
|
|
116
|
-
}
|
|
152
|
+
};
|
|
153
|
+
await runBarePack(barePackOptions);
|
|
154
|
+
if (options.installMissingPrebuilds === true) {
|
|
155
|
+
try {
|
|
156
|
+
const { installed } = await installMissingHostPrebuilds({
|
|
157
|
+
projectRoot,
|
|
158
|
+
hosts,
|
|
159
|
+
addons: await collectAddonsFromBundle({ bundlePath, projectRoot, hosts }),
|
|
160
|
+
quiet: options.quiet === true,
|
|
161
|
+
logger
|
|
162
|
+
});
|
|
163
|
+
installedPrebuilds = installed;
|
|
164
|
+
// Where a platform package was missing, bare-pack resolved the addon's
|
|
165
|
+
// `#host-addon` import to its fallback module; bundle again to pick up
|
|
166
|
+
// the installed package.
|
|
167
|
+
if (installed.length > 0) {
|
|
168
|
+
logger.info('\n🔨 Bundling again with the installed platform packages...');
|
|
169
|
+
await runBarePack(barePackOptions);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (!(error instanceof HostPrebuildsInstallRefusedError))
|
|
174
|
+
throw error;
|
|
175
|
+
// A refusal happens before anything is installed, so the bundle
|
|
176
|
+
// already written is final and only its manifest is left to write.
|
|
177
|
+
installRefused = error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
117
180
|
}
|
|
118
181
|
finally {
|
|
119
182
|
await fsp.rm(bundleEntryPath, { force: true });
|
|
@@ -128,6 +191,18 @@ export async function bundleSdk(options = {}) {
|
|
|
128
191
|
projectRoot,
|
|
129
192
|
logger
|
|
130
193
|
});
|
|
194
|
+
if (options.checkEngines !== false) {
|
|
195
|
+
await checkBundleEngines({
|
|
196
|
+
projectRoot,
|
|
197
|
+
bundlePath,
|
|
198
|
+
hosts,
|
|
199
|
+
configPath: configPath ?? undefined,
|
|
200
|
+
network: options.network,
|
|
201
|
+
logger
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (installRefused !== undefined)
|
|
205
|
+
throw installRefused;
|
|
131
206
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
132
207
|
logger.info(`\n🎉 Done in ${elapsed}s!\n`);
|
|
133
208
|
logger.info('Generated files:');
|
|
@@ -143,6 +218,7 @@ export async function bundleSdk(options = {}) {
|
|
|
143
218
|
entryPaths: {
|
|
144
219
|
worker: entryPath
|
|
145
220
|
},
|
|
146
|
-
manifestPath: manifestResult.manifestPath
|
|
221
|
+
manifestPath: manifestResult.manifestPath,
|
|
222
|
+
installedPrebuilds
|
|
147
223
|
};
|
|
148
224
|
}
|
|
@@ -17,7 +17,7 @@ export function parseBuiltinSpecifier(specifier, sdkName) {
|
|
|
17
17
|
}
|
|
18
18
|
export function resolvePluginSpecifiers(config, sdkName, logger) {
|
|
19
19
|
let plugins = config.plugins ?? [];
|
|
20
|
-
if (plugins.length === 0) {
|
|
20
|
+
if (plugins.length === 0 && !(config.rpcServerProvider && config.plugins !== undefined)) {
|
|
21
21
|
const allBuiltins = BUILTIN_SUFFIXES.map((suffix) => buildBuiltinSpecifier(sdkName, suffix));
|
|
22
22
|
logger.warn('No plugins specified — bundling ALL built-in plugins.');
|
|
23
23
|
logger.info(" For smaller bundles, add a 'plugins' array to qvac.config.*\n");
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface CommandLoggerOptions {
|
|
2
|
+
quiet?: boolean | undefined;
|
|
3
|
+
verbose?: boolean | undefined;
|
|
4
|
+
}
|
|
5
|
+
export declare function createCommandLogger(options: CommandLoggerOptions): import("../logging/index.js").Logger;
|
|
6
|
+
//# sourceMappingURL=command-logger.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getClientLogger } from '../logging/index.js';
|
|
2
|
+
export function createCommandLogger(options) {
|
|
3
|
+
if (options.quiet) {
|
|
4
|
+
return getClientLogger({ level: 'error', enableConsole: false });
|
|
5
|
+
}
|
|
6
|
+
// The client logger keeps console output off unless asked; a command's
|
|
7
|
+
// progress and warnings are meant for the person running it.
|
|
8
|
+
if (options.verbose) {
|
|
9
|
+
return getClientLogger({ level: 'debug', enableConsole: true });
|
|
10
|
+
}
|
|
11
|
+
return getClientLogger({ enableConsole: true });
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Logger } from '../../logging/types.js';
|
|
2
|
+
import { type NativeAddon } from '../verify/addon-source.js';
|
|
3
|
+
import { type PackageManagerName } from './package-manager.js';
|
|
4
|
+
export type { PackageManagerName } from './package-manager.js';
|
|
5
|
+
export interface EnsureHostPrebuildsOptions {
|
|
6
|
+
/** The package whose package.json receives the pins; its package manager installs them. */
|
|
7
|
+
projectRoot: string;
|
|
8
|
+
/** Bare hosts to cover. Only mobile hosts (`android-*`, `ios-*`) can need an install. */
|
|
9
|
+
hosts: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Addon package names to cover. Defaults to every addon installed for the
|
|
12
|
+
* project: in its own node_modules or in that of a workspace root listing it.
|
|
13
|
+
*/
|
|
14
|
+
addons?: string[] | undefined;
|
|
15
|
+
/** Install with this package manager instead of detecting the project's own. */
|
|
16
|
+
packageManager?: PackageManagerName | undefined;
|
|
17
|
+
quiet?: boolean | undefined;
|
|
18
|
+
verbose?: boolean | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface HostPrebuildPackage {
|
|
21
|
+
/** The platform package, e.g. `@qvac/tts-ggml-android-arm64`. */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Always the addon's own version: platform packages are version-locked to it. */
|
|
24
|
+
version: string;
|
|
25
|
+
/** The addon the package ships prebuilds for. */
|
|
26
|
+
addon: string;
|
|
27
|
+
/** The requested hosts the package covers. */
|
|
28
|
+
hosts: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface EnsureHostPrebuildsResult {
|
|
31
|
+
/** The packages this call installed; empty when every prebuild already resolved. */
|
|
32
|
+
installed: HostPrebuildPackage[];
|
|
33
|
+
/** The package manager that installed them, or null when nothing was missing. */
|
|
34
|
+
packageManager: PackageManagerName | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Installs the per-platform prebuild packages that addons need for mobile
|
|
38
|
+
* hosts and that the project does not have yet.
|
|
39
|
+
*
|
|
40
|
+
* A split addon names one platform package per host in the `#host-addon` map
|
|
41
|
+
* of its package.json (`<addon>-ios` covers every iOS host). Desktop platform
|
|
42
|
+
* packages install as the addon's os/cpu-filtered optionalDependencies, but a
|
|
43
|
+
* build host never reports a mobile os, so mobile ones have to be declared.
|
|
44
|
+
* This declares the missing ones in package.json, pinned to each addon's
|
|
45
|
+
* exact version, using the project's own package manager.
|
|
46
|
+
*
|
|
47
|
+
* A host whose prebuild already resolves is skipped: a local
|
|
48
|
+
* `prebuilds/<host>` in the addon (source builds) or its platform package at
|
|
49
|
+
* the addon's version. Throws `HostPrebuildsInstallRefusedError` when no
|
|
50
|
+
* supported package manager (npm 7+, pnpm, bun, Yarn Berry) is found, and
|
|
51
|
+
* `HostPrebuildsInstallFailedError` when the install does not produce the
|
|
52
|
+
* packages.
|
|
53
|
+
*/
|
|
54
|
+
export declare function ensureHostPrebuilds(options: EnsureHostPrebuildsOptions): Promise<EnsureHostPrebuildsResult>;
|
|
55
|
+
export interface InstallMissingHostPrebuildsOptions {
|
|
56
|
+
projectRoot: string;
|
|
57
|
+
hosts: string[];
|
|
58
|
+
addons: NativeAddon[];
|
|
59
|
+
packageManager?: PackageManagerName | undefined;
|
|
60
|
+
quiet: boolean;
|
|
61
|
+
logger: Logger;
|
|
62
|
+
}
|
|
63
|
+
/** `ensureHostPrebuilds` for addons the caller has already located. */
|
|
64
|
+
export declare function installMissingHostPrebuilds(options: InstallMissingHostPrebuildsOptions): Promise<EnsureHostPrebuildsResult>;
|
|
65
|
+
/**
|
|
66
|
+
* The platform packages `addons` still need for the mobile hosts in `hosts`,
|
|
67
|
+
* one entry per package. Throws when two installed versions of one addon need
|
|
68
|
+
* the same platform package, since only one version of it can be installed.
|
|
69
|
+
*/
|
|
70
|
+
export declare function findMissingHostPrebuilds(addons: NativeAddon[], hosts: string[]): Promise<HostPrebuildPackage[]>;
|
|
71
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { promises as fsp } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
import { createCommandLogger } from '../command-logger.js';
|
|
5
|
+
import { readAddonPackageJson } from '../verify/addon-source.js';
|
|
6
|
+
import { checkPrebuilds, isMobileHost } from '../verify/prebuilds.js';
|
|
7
|
+
import { checkPackageManagerSupport, detectPackageManager, installArgs, isPackageManagerName, isWorkspaceMember, runPackageManager } from './package-manager.js';
|
|
8
|
+
import { HostPrebuildsInstallFailedError, HostPrebuildsInstallRefusedError } from '../../utils/errors-client.js';
|
|
9
|
+
/** npm's package-name grammar. On Windows the names pass through a shell. */
|
|
10
|
+
const PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
11
|
+
/**
|
|
12
|
+
* Installs the per-platform prebuild packages that addons need for mobile
|
|
13
|
+
* hosts and that the project does not have yet.
|
|
14
|
+
*
|
|
15
|
+
* A split addon names one platform package per host in the `#host-addon` map
|
|
16
|
+
* of its package.json (`<addon>-ios` covers every iOS host). Desktop platform
|
|
17
|
+
* packages install as the addon's os/cpu-filtered optionalDependencies, but a
|
|
18
|
+
* build host never reports a mobile os, so mobile ones have to be declared.
|
|
19
|
+
* This declares the missing ones in package.json, pinned to each addon's
|
|
20
|
+
* exact version, using the project's own package manager.
|
|
21
|
+
*
|
|
22
|
+
* A host whose prebuild already resolves is skipped: a local
|
|
23
|
+
* `prebuilds/<host>` in the addon (source builds) or its platform package at
|
|
24
|
+
* the addon's version. Throws `HostPrebuildsInstallRefusedError` when no
|
|
25
|
+
* supported package manager (npm 7+, pnpm, bun, Yarn Berry) is found, and
|
|
26
|
+
* `HostPrebuildsInstallFailedError` when the install does not produce the
|
|
27
|
+
* packages.
|
|
28
|
+
*/
|
|
29
|
+
export async function ensureHostPrebuilds(options) {
|
|
30
|
+
const logger = createCommandLogger(options);
|
|
31
|
+
const addons = await resolveAddons(options.projectRoot, options.addons, logger);
|
|
32
|
+
return installMissingHostPrebuilds({
|
|
33
|
+
projectRoot: options.projectRoot,
|
|
34
|
+
hosts: options.hosts,
|
|
35
|
+
addons,
|
|
36
|
+
packageManager: options.packageManager,
|
|
37
|
+
quiet: options.quiet === true,
|
|
38
|
+
logger
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** `ensureHostPrebuilds` for addons the caller has already located. */
|
|
42
|
+
export async function installMissingHostPrebuilds(options) {
|
|
43
|
+
const { projectRoot, hosts, addons, quiet, logger } = options;
|
|
44
|
+
const missing = await findMissingHostPrebuilds(addons, hosts);
|
|
45
|
+
if (missing.length === 0)
|
|
46
|
+
return { installed: [], packageManager: null };
|
|
47
|
+
const dependencies = toDependencies(missing);
|
|
48
|
+
const manager = await choosePackageManager(projectRoot, options.packageManager, dependencies);
|
|
49
|
+
const args = installArgs(manager, missing.map((pkg) => `${pkg.name}@${pkg.version}`));
|
|
50
|
+
const command = `${manager} ${args.join(' ')}`;
|
|
51
|
+
logger.info(`\n📦 Installing addon platform packages with ${manager}:`);
|
|
52
|
+
for (const pkg of missing) {
|
|
53
|
+
logger.info(` ${pkg.name}@${pkg.version} (${pkg.hosts.join(', ')})`);
|
|
54
|
+
}
|
|
55
|
+
const result = await runPackageManager(manager, args, { cwd: projectRoot, quiet });
|
|
56
|
+
if (result.error !== undefined || result.code !== 0) {
|
|
57
|
+
const outcome = result.error?.message ?? `exited with code ${result.code ?? 'unknown'}`;
|
|
58
|
+
const stderr = result.stderr.trim();
|
|
59
|
+
throw new HostPrebuildsInstallFailedError(`\`${command}\` ${outcome}${stderr.length > 0 ? `\n\n${stderr}` : ''}`, dependencies, result.error);
|
|
60
|
+
}
|
|
61
|
+
const stillMissing = await findMissingHostPrebuilds(addons, hosts);
|
|
62
|
+
if (stillMissing.length > 0) {
|
|
63
|
+
const names = stillMissing.map((pkg) => pkg.name).join(', ');
|
|
64
|
+
const pnpHint = manager === 'yarn'
|
|
65
|
+
? "; Yarn Plug'n'Play installs no node_modules, so set `nodeLinker: node-modules`"
|
|
66
|
+
: '';
|
|
67
|
+
throw new HostPrebuildsInstallFailedError(`\`${command}\` succeeded, but ${names} is still not installed under node_modules${pnpHint}`, toDependencies(stillMissing));
|
|
68
|
+
}
|
|
69
|
+
logger.info(` Installed ${missing.length} platform package${missing.length === 1 ? '' : 's'}`);
|
|
70
|
+
return { installed: missing, packageManager: manager };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The platform packages `addons` still need for the mobile hosts in `hosts`,
|
|
74
|
+
* one entry per package. Throws when two installed versions of one addon need
|
|
75
|
+
* the same platform package, since only one version of it can be installed.
|
|
76
|
+
*/
|
|
77
|
+
export async function findMissingHostPrebuilds(addons, hosts) {
|
|
78
|
+
const mobileHosts = hosts.filter(isMobileHost);
|
|
79
|
+
const packages = new Map();
|
|
80
|
+
if (mobileHosts.length === 0)
|
|
81
|
+
return [];
|
|
82
|
+
for (const addon of addons) {
|
|
83
|
+
for (const issue of await checkPrebuilds({ addon, hosts: mobileHosts })) {
|
|
84
|
+
const pin = issue.platformPackage;
|
|
85
|
+
if (pin === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
const known = packages.get(pin.name);
|
|
88
|
+
if (known === undefined) {
|
|
89
|
+
packages.set(pin.name, { ...pin, addon: addon.name, hosts: [issue.host] });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (known.version !== pin.version) {
|
|
93
|
+
throw new HostPrebuildsInstallRefusedError(`${addon.name} is installed at both ${known.version} and ${pin.version}, but ` +
|
|
94
|
+
`${pin.name} can be installed at only one version. ` +
|
|
95
|
+
`Deduplicate ${addon.name} to a single version and try again`, {});
|
|
96
|
+
}
|
|
97
|
+
if (!known.hosts.includes(issue.host))
|
|
98
|
+
known.hosts.push(issue.host);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const missing = [...packages.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
102
|
+
for (const pkg of missing) {
|
|
103
|
+
if (!PACKAGE_NAME.test(pkg.name) || semver.valid(pkg.version) === null) {
|
|
104
|
+
throw new HostPrebuildsInstallRefusedError(`${pkg.addon} names "${pkg.name}@${pkg.version}", which is not a valid package name and version`, {});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return missing;
|
|
108
|
+
}
|
|
109
|
+
async function choosePackageManager(projectRoot, requested, dependencies) {
|
|
110
|
+
let manager;
|
|
111
|
+
let source = '';
|
|
112
|
+
if (requested !== undefined) {
|
|
113
|
+
if (!isPackageManagerName(requested)) {
|
|
114
|
+
throw new HostPrebuildsInstallRefusedError(`packageManager "${String(requested)}" is not npm, pnpm, bun, or yarn`, dependencies);
|
|
115
|
+
}
|
|
116
|
+
manager = requested;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const detected = await detectPackageManager(projectRoot);
|
|
120
|
+
if (detected.manager === null) {
|
|
121
|
+
throw new HostPrebuildsInstallRefusedError(`could not tell which package manager the project uses (${detected.reason})`, dependencies);
|
|
122
|
+
}
|
|
123
|
+
manager = detected.manager;
|
|
124
|
+
source = ` (detected from ${detected.source})`;
|
|
125
|
+
}
|
|
126
|
+
const unsupported = await checkPackageManagerSupport(manager, projectRoot);
|
|
127
|
+
if (unsupported !== null) {
|
|
128
|
+
throw new HostPrebuildsInstallRefusedError(`${unsupported}${source}`, dependencies);
|
|
129
|
+
}
|
|
130
|
+
return manager;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The installed addons named in `names`, or every installed addon when
|
|
134
|
+
* `names` is omitted. For each name the copy in the nearest modules directory
|
|
135
|
+
* wins, as it does for Node's resolver.
|
|
136
|
+
*/
|
|
137
|
+
async function resolveAddons(projectRoot, names, logger) {
|
|
138
|
+
const moduleDirs = await reachableModuleDirs(projectRoot);
|
|
139
|
+
const wanted = names === undefined ? null : new Set(names);
|
|
140
|
+
const found = new Map();
|
|
141
|
+
for (const modulesDir of moduleDirs) {
|
|
142
|
+
const candidates = wanted === null ? await listPackageNames(modulesDir) : [...wanted];
|
|
143
|
+
for (const name of candidates) {
|
|
144
|
+
if (found.has(name))
|
|
145
|
+
continue;
|
|
146
|
+
const result = await readAddonPackageJson({
|
|
147
|
+
packageJsonPath: path.join(modulesDir, ...name.split('/'), 'package.json'),
|
|
148
|
+
expectedName: name
|
|
149
|
+
});
|
|
150
|
+
if (result.addon !== undefined)
|
|
151
|
+
found.set(name, result.addon);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const name of wanted ?? []) {
|
|
155
|
+
if (!found.has(name))
|
|
156
|
+
logger.warn(`${name} is not an installed native addon; skipping it`);
|
|
157
|
+
}
|
|
158
|
+
return [...found.values()];
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Every modules directory a package installed for `projectRoot` can sit in,
|
|
162
|
+
* nearest first: the project's `node_modules` and that of each workspace root
|
|
163
|
+
* above it that lists the project (npm, Yarn, and bun hoist workspace
|
|
164
|
+
* dependencies to the root), plus the views pnpm (`.pnpm/node_modules`) and
|
|
165
|
+
* bun's isolated layout (`.bun/node_modules`) keep of the packages that are not
|
|
166
|
+
* direct dependencies. Any other enclosing project installed its node_modules
|
|
167
|
+
* for itself, so its addons do not belong to this one.
|
|
168
|
+
*/
|
|
169
|
+
async function reachableModuleDirs(projectRoot) {
|
|
170
|
+
const start = path.resolve(projectRoot);
|
|
171
|
+
const dirs = [];
|
|
172
|
+
let dir = start;
|
|
173
|
+
for (;;) {
|
|
174
|
+
if (dir === start || (await isWorkspaceMember(dir, start))) {
|
|
175
|
+
const nodeModules = path.join(dir, 'node_modules');
|
|
176
|
+
for (const candidate of [
|
|
177
|
+
nodeModules,
|
|
178
|
+
path.join(nodeModules, '.pnpm', 'node_modules'),
|
|
179
|
+
path.join(nodeModules, '.bun', 'node_modules')
|
|
180
|
+
]) {
|
|
181
|
+
if (await isDirectory(candidate))
|
|
182
|
+
dirs.push(candidate);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const parent = path.dirname(dir);
|
|
186
|
+
if (parent === dir)
|
|
187
|
+
return dirs;
|
|
188
|
+
dir = parent;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function listPackageNames(modulesDir) {
|
|
192
|
+
const names = [];
|
|
193
|
+
for (const entry of await readDirNames(modulesDir)) {
|
|
194
|
+
if (entry.startsWith('.'))
|
|
195
|
+
continue;
|
|
196
|
+
if (!entry.startsWith('@')) {
|
|
197
|
+
names.push(entry);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
for (const scoped of await readDirNames(path.join(modulesDir, entry))) {
|
|
201
|
+
if (!scoped.startsWith('.'))
|
|
202
|
+
names.push(`${entry}/${scoped}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return names;
|
|
206
|
+
}
|
|
207
|
+
async function readDirNames(dir) {
|
|
208
|
+
try {
|
|
209
|
+
return (await fsp.readdir(dir, { withFileTypes: true }))
|
|
210
|
+
.filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
|
|
211
|
+
.map((entry) => entry.name);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function isDirectory(dir) {
|
|
218
|
+
try {
|
|
219
|
+
return (await fsp.stat(dir)).isDirectory();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function toDependencies(packages) {
|
|
226
|
+
return Object.fromEntries(packages.map((pkg) => [pkg.name, pkg.version]));
|
|
227
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type PackageManagerName = 'npm' | 'pnpm' | 'bun' | 'yarn';
|
|
2
|
+
/**
|
|
3
|
+
* The package manager that owns `projectRoot`, from the project's own
|
|
4
|
+
* `packageManager` field, lockfile, or node_modules install files. A project
|
|
5
|
+
* with none of those is a workspace member only if the nearest directory above
|
|
6
|
+
* it that has them is a workspace root listing it; any other enclosing project
|
|
7
|
+
* belongs to someone else, and its package manager is not this one's.
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectPackageManager(projectRoot: string): Promise<{
|
|
10
|
+
manager: null;
|
|
11
|
+
reason: string;
|
|
12
|
+
source?: never;
|
|
13
|
+
} | {
|
|
14
|
+
manager: PackageManagerName;
|
|
15
|
+
source: string;
|
|
16
|
+
reason?: never;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function isPackageManagerName(value: string): value is PackageManagerName;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the workspace globs declared at `rootDir` (package.json `workspaces`
|
|
21
|
+
* for npm, Yarn, and bun; `pnpm-workspace.yaml` for pnpm) include `memberDir`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isWorkspaceMember(rootDir: string, memberDir: string): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* The `packages` list of a pnpm-workspace.yaml, in block (`- 'apps/*'`) or
|
|
26
|
+
* flow (`packages: ['apps/*']`) form. The SDK has no YAML parser, and the
|
|
27
|
+
* file's other keys are irrelevant here.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parsePnpmWorkspacePackages(yaml: string): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Why `manager` cannot install in `cwd`, or null when it can. The supported
|
|
32
|
+
* set matches the installers the split addons document: npm 7+, pnpm, bun,
|
|
33
|
+
* and Yarn Berry.
|
|
34
|
+
*/
|
|
35
|
+
export declare function checkPackageManagerSupport(manager: PackageManagerName, cwd: string): Promise<string | null>;
|
|
36
|
+
/** Arguments that add `specs` as exact-version dependencies of the package in cwd. */
|
|
37
|
+
export declare function installArgs(manager: PackageManagerName, specs: string[]): string[];
|
|
38
|
+
export interface RunPackageManagerResult {
|
|
39
|
+
code: number | null;
|
|
40
|
+
stdout: string;
|
|
41
|
+
stderr: string;
|
|
42
|
+
error?: Error;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Quiet runs capture stdout and stderr; other runs stream them to this
|
|
46
|
+
* process. Windows resolves the package managers' `.cmd` shims through the
|
|
47
|
+
* shell, which does not escape arguments, so callers must pass only
|
|
48
|
+
* validated ones.
|
|
49
|
+
*/
|
|
50
|
+
export declare function runPackageManager(manager: PackageManagerName, args: string[], options: {
|
|
51
|
+
cwd: string;
|
|
52
|
+
quiet: boolean;
|
|
53
|
+
}): Promise<RunPackageManagerResult>;
|
|
54
|
+
//# sourceMappingURL=package-manager.d.ts.map
|