@qvac/sdk 0.19.0 → 0.20.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.
Files changed (79) hide show
  1. package/NOTICE +111 -82
  2. package/dist/examples/abot-world.js +3 -3
  3. package/dist/examples/advisory-model-fit.d.ts +59 -0
  4. package/dist/examples/advisory-model-fit.js +156 -0
  5. package/dist/examples/asr/parakeet-nemotron-filesystem.d.ts +2 -0
  6. package/dist/examples/asr/parakeet-nemotron-filesystem.js +170 -0
  7. package/dist/examples/assess-model-fit.js +6 -1
  8. package/dist/examples/audiogen/edit-music.d.ts +2 -0
  9. package/dist/examples/audiogen/edit-music.js +121 -0
  10. package/dist/examples/audiogen/understand-music.d.ts +2 -0
  11. package/dist/examples/audiogen/understand-music.js +73 -0
  12. package/dist/examples/diffusion-ic-lora-ltx.js +3 -3
  13. package/dist/examples/diffusion-img2vid.js +1 -1
  14. package/dist/examples/diffusion-txt2vid-minimax-h3.d.ts +2 -0
  15. package/dist/examples/diffusion-txt2vid-minimax-h3.js +64 -0
  16. package/dist/examples/diffusion-txt2vid.js +1 -1
  17. package/dist/examples/kv-cache-cleanup.js +5 -2
  18. package/dist/examples/model-fit-ui/server.js +33 -7
  19. package/dist/examples/rag/rag-turbovec.d.ts +2 -0
  20. package/dist/examples/rag/rag-turbovec.js +58 -0
  21. package/dist/examples/tts/audio8.js +5 -5
  22. package/dist/examples/tts/chatterbox-enhanced.js +4 -5
  23. package/dist/examples/tts/chatterbox-multilingual.js +5 -5
  24. package/dist/examples/tts/chatterbox.js +5 -5
  25. package/dist/examples/tts/cosyvoice3.js +5 -5
  26. package/dist/examples/tts/llm-to-tts-streaming.js +12 -2
  27. package/dist/examples/tts/parler.js +5 -5
  28. package/dist/examples/tts/supertonic-enhanced.js +4 -5
  29. package/dist/examples/tts/supertonic-multilingual.js +5 -5
  30. package/dist/examples/tts/supertonic.js +5 -5
  31. package/dist/scripts/contract/build-contract.d.ts +1 -1
  32. package/dist/scripts/contract/constants-registry.d.ts +31 -0
  33. package/dist/scripts/contract/constants-registry.js +13 -2
  34. package/dist/scripts/contract/method-shapes.d.ts +3 -0
  35. package/dist/scripts/contract/method-shapes.js +3 -0
  36. package/dist/src/client/api/assess-model-fit.js +2 -0
  37. package/dist/src/client/api/audio-edit.d.ts +36 -0
  38. package/dist/src/client/api/audio-edit.js +38 -0
  39. package/dist/src/client/api/audio-gen-result.d.ts +5 -1
  40. package/dist/src/client/api/audio-gen-result.js +105 -52
  41. package/dist/src/client/api/audio-gen.d.ts +38 -1
  42. package/dist/src/client/api/audio-gen.js +38 -1
  43. package/dist/src/client/api/audio-understand.d.ts +25 -0
  44. package/dist/src/client/api/audio-understand.js +27 -0
  45. package/dist/src/client/api/delete-cache.d.ts +7 -1
  46. package/dist/src/client/api/delete-cache.js +11 -1
  47. package/dist/src/client/api/index.d.ts +3 -0
  48. package/dist/src/client/api/index.js +3 -0
  49. package/dist/src/client/api/text-to-speech.d.ts +31 -4
  50. package/dist/src/client/api/text-to-speech.js +118 -21
  51. package/dist/src/client/api/vector-index.d.ts +143 -0
  52. package/dist/src/client/api/vector-index.js +168 -0
  53. package/dist/src/client/api/world.d.ts +2 -2
  54. package/dist/src/commands/bundle/manifest.d.ts +2 -0
  55. package/dist/src/commands/verify/addon-source.d.ts +6 -0
  56. package/dist/src/commands/verify/bundle-source.d.ts +2 -0
  57. package/dist/src/commands/verify/bundle-source.js +155 -1
  58. package/dist/src/commands/verify/index.js +1 -0
  59. package/dist/src/commands/verify/prebuilds.d.ts +23 -0
  60. package/dist/src/commands/verify/prebuilds.js +129 -13
  61. package/dist/src/commands/verify/resource-collectors.js +2 -1
  62. package/dist/src/expo/plugins/patches/qvac-platform-addons.d.ts +23 -0
  63. package/dist/src/expo/plugins/patches/qvac-platform-addons.js +94 -0
  64. package/dist/src/expo/plugins/withMobileBundle.d.ts +12 -2
  65. package/dist/src/expo/plugins/withMobileBundle.js +62 -31
  66. package/dist/src/index.d.ts +3 -3
  67. package/dist/src/index.js +7 -3
  68. package/dist/src/schemas/sdk-errors-server.d.ts +4 -0
  69. package/dist/src/schemas/sdk-errors-server.js +22 -0
  70. package/dist/src/utils/aggregate-events.d.ts +3 -1
  71. package/dist/src/utils/aggregate-events.js +7 -1
  72. package/dist/src/utils/errors-server.d.ts +12 -0
  73. package/dist/src/utils/errors-server.js +21 -0
  74. package/docs/system-resources-support-matrix.md +19 -19
  75. package/package.json +22 -15
  76. package/src/electron-forge/index.cjs +86 -14
  77. package/src/expo/plugins/patches/android-link.mjs +18 -9
  78. package/src/expo/plugins/patches/ios-link.mjs +18 -9
  79. package/src/expo/plugins/patches/qvac-platform-addons.ts +124 -0
@@ -1,6 +1,18 @@
1
1
  import { promises as fsp } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { DEFAULT_HOSTS } from '../bundle/constants.js';
2
4
  import { buildNestedPathIndex, extractBarePackHeader, extractPackedString } from '../bundle/manifest.js';
3
5
  import { deduplicateAddons, readAddonPackageJson } from './addon-source.js';
6
+ /** bare-pack resolves a linked native addon to this prefix instead of a module path. */
7
+ const LINKED_PREFIX = 'linked:';
8
+ /** Reserved resolution key naming the package.json that owns a module. */
9
+ const PACKAGE_KEY = '#package';
10
+ /** Export conditions that name a platform. */
11
+ const PLATFORMS = new Set(DEFAULT_HOSTS.map(platformOf));
12
+ /** `win32-x64` -> `win32`, `ios-arm64-simulator` -> `ios`. */
13
+ function platformOf(host) {
14
+ return host.split('-')[0] ?? host;
15
+ }
4
16
  export class InvalidBundleSourceError extends Error {
5
17
  bundlePath;
6
18
  constructor(bundlePath, cause) {
@@ -13,7 +25,7 @@ export class InvalidBundleSourceError extends Error {
13
25
  }
14
26
  }
15
27
  export async function collectAddonsFromBundle(options) {
16
- const { bundlePath, projectRoot, diagnostics } = options;
28
+ const { bundlePath, projectRoot, hosts, diagnostics } = options;
17
29
  let bundleText;
18
30
  try {
19
31
  bundleText = await fsp.readFile(bundlePath, 'utf8');
@@ -22,10 +34,14 @@ export async function collectAddonsFromBundle(options) {
22
34
  throw new InvalidBundleSourceError(bundlePath, error);
23
35
  }
24
36
  let resolutions;
37
+ let main;
38
+ let imports;
25
39
  try {
26
40
  const packed = extractPackedString(bundleText);
27
41
  const header = extractBarePackHeader(packed);
28
42
  resolutions = header.resolutions ?? {};
43
+ main = header.main;
44
+ imports = header.imports ?? {};
29
45
  }
30
46
  catch (error) {
31
47
  throw new InvalidBundleSourceError(bundlePath, error);
@@ -34,6 +50,14 @@ export async function collectAddonsFromBundle(options) {
34
50
  diagnostics.emptyResolutions = true;
35
51
  }
36
52
  const pathsByPackage = buildNestedPathIndex(resolutions, projectRoot);
53
+ const analysed = analyseLinkedHosts({
54
+ resolutions,
55
+ main,
56
+ imports,
57
+ hosts: hosts ?? [],
58
+ projectRoot,
59
+ pathsByPackage
60
+ });
37
61
  const addons = [];
38
62
  for (const [pkgName, candidates] of pathsByPackage) {
39
63
  for (const candidate of candidates) {
@@ -42,6 +66,8 @@ export async function collectAddonsFromBundle(options) {
42
66
  expectedName: pkgName
43
67
  });
44
68
  if (result.isAddon && result.addon) {
69
+ if (analysed !== null)
70
+ result.addon.linkedHosts = analysed.get(candidate) ?? [];
45
71
  addons.push(result.addon);
46
72
  }
47
73
  else if (result.invalid !== undefined && diagnostics !== undefined) {
@@ -51,3 +77,131 @@ export async function collectAddonsFromBundle(options) {
51
77
  }
52
78
  return deduplicateAddons(addons);
53
79
  }
80
+ /**
81
+ * `mapLinkedHosts`, rejected unless its keys line up with the paths the path
82
+ * index found. A disagreement would read as "nothing is linked" and drop every
83
+ * prebuild check, so require an overlap before trusting the map.
84
+ */
85
+ function analyseLinkedHosts(options) {
86
+ const { pathsByPackage, ...mapOptions } = options;
87
+ const linkedHosts = mapLinkedHosts(mapOptions);
88
+ if (linkedHosts === null)
89
+ return null;
90
+ for (const candidates of pathsByPackage.values()) {
91
+ for (const candidate of candidates) {
92
+ if (linkedHosts.has(candidate))
93
+ return linkedHosts;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ /**
99
+ * Which of `hosts` link each package's addon, keyed by the owning package.json.
100
+ * A host counts only when the bundle both reaches a module of that package on
101
+ * its platform *and* resolves a `linked:` addon there.
102
+ *
103
+ * Both halves are needed because bare-pack emits two graph shapes: for a single
104
+ * host it resolves conditions eagerly, dropping the binding module of a package
105
+ * whose platform export is a JS stub; for several hosts it keeps every branch
106
+ * and links a name per host, so only reachability separates the platforms.
107
+ *
108
+ * Null leaves every host checked, as if no analysis had run.
109
+ */
110
+ function mapLinkedHosts(options) {
111
+ const { resolutions, main, imports, hosts, projectRoot } = options;
112
+ if (hosts.length === 0)
113
+ return null;
114
+ if (main === undefined || !Object.prototype.hasOwnProperty.call(resolutions, main))
115
+ return null;
116
+ // `main` is the only root we walk; an import map could reach past it.
117
+ if (Object.keys(imports).length > 0)
118
+ return null;
119
+ const linked = new Map();
120
+ const ownersByPlatform = new Map();
121
+ for (const host of hosts) {
122
+ const platform = platformOf(host);
123
+ let owners = ownersByPlatform.get(platform);
124
+ if (owners === undefined) {
125
+ owners = linkedOwnersOn(resolutions, main, platform, projectRoot);
126
+ ownersByPlatform.set(platform, owners);
127
+ }
128
+ if (owners === null)
129
+ return null;
130
+ for (const owner of owners) {
131
+ let forOwner = linked.get(owner);
132
+ if (forOwner === undefined) {
133
+ forOwner = new Set();
134
+ linked.set(owner, forOwner);
135
+ }
136
+ forOwner.add(host);
137
+ }
138
+ }
139
+ return new Map([...linked].map(([owner, forOwner]) => [owner, [...forOwner].sort()]));
140
+ }
141
+ /**
142
+ * package.json paths of the addons linked on `platform`, walking from `main`.
143
+ * Null if a linked module names no owning package.
144
+ */
145
+ function linkedOwnersOn(resolutions, main, platform, projectRoot) {
146
+ const owners = new Set();
147
+ const seen = new Set();
148
+ const stack = [main];
149
+ while (stack.length > 0) {
150
+ const module = stack.pop();
151
+ if (seen.has(module))
152
+ continue;
153
+ seen.add(module);
154
+ const edges = asRecord(resolutions[module]);
155
+ if (edges === null)
156
+ continue;
157
+ let linksAddon = false;
158
+ for (const [specifier, target] of Object.entries(edges)) {
159
+ if (specifier === PACKAGE_KEY)
160
+ continue;
161
+ for (const resolved of targetsOn(target, platform)) {
162
+ if (resolved.startsWith(LINKED_PREFIX))
163
+ linksAddon = true;
164
+ else
165
+ stack.push(resolved);
166
+ }
167
+ }
168
+ if (!linksAddon)
169
+ continue;
170
+ const owner = edges[PACKAGE_KEY];
171
+ if (typeof owner !== 'string')
172
+ return null;
173
+ owners.add(path.join(projectRoot, owner));
174
+ }
175
+ return owners;
176
+ }
177
+ /**
178
+ * Every target a specifier can resolve to on `platform`, first-match-wins: this
179
+ * platform or `default` settles it, another platform is ruled out. A condition
180
+ * the host cannot decide (`require`, `bare`, ...) is kept and the scan goes on,
181
+ * so reachability is over- rather than under-reported.
182
+ */
183
+ function targetsOn(target, platform) {
184
+ if (typeof target === 'string')
185
+ return [target];
186
+ const conditions = asRecord(target);
187
+ if (conditions === null)
188
+ return [];
189
+ const targets = [];
190
+ for (const [condition, value] of Object.entries(conditions)) {
191
+ if (PLATFORMS.has(condition)) {
192
+ if (condition !== platform)
193
+ continue;
194
+ targets.push(...targetsOn(value, platform));
195
+ break;
196
+ }
197
+ targets.push(...targetsOn(value, platform));
198
+ if (condition === 'default')
199
+ break;
200
+ }
201
+ return targets;
202
+ }
203
+ function asRecord(value) {
204
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
205
+ return null;
206
+ return value;
207
+ }
@@ -126,6 +126,7 @@ export async function verifyBundle(options) {
126
126
  ? await collectAddonsFromBundle({
127
127
  bundlePath: resolvedAddonsSource,
128
128
  projectRoot,
129
+ hosts,
129
130
  diagnostics
130
131
  })
131
132
  : await collectAddonsFromNodeModules({
@@ -11,6 +11,29 @@ export interface CheckPrebuildsOptions {
11
11
  addon: NativeAddon;
12
12
  hosts: string[];
13
13
  }
14
+ /**
15
+ * One place an addon's prebuild for a host may live. `platformPackage` is set
16
+ * when the directory belongs to a per-platform prebuild package rather than
17
+ * the addon package itself.
18
+ */
19
+ export interface PrebuildLocation {
20
+ hostDir: string;
21
+ platformPackage?: string;
22
+ }
23
+ /**
24
+ * Directories to search for an addon's `<host>` prebuild, in precedence order:
25
+ *
26
+ * 1. `<packageRoot>/prebuilds/<host>` — the fat layout every addon used to
27
+ * publish, and where source builds and `linked:` checkouts still land.
28
+ * 2. `<platformRoot>/addon/prebuilds/<host>` — the per-platform package the
29
+ * addon's `#host-addon` map names for this host, when that package is
30
+ * installed. Each platform package embeds an inner `addon/` package named
31
+ * after the meta addon so the `.bare` file keeps its name.
32
+ *
33
+ * A local `prebuilds/` always wins, matching the precedence the addons apply
34
+ * in their own `binding.js` (`require.addon()` first, platform package second).
35
+ */
36
+ export declare function resolvePrebuildLocations(addon: NativeAddon, host: string): Promise<PrebuildLocation[]>;
14
37
  export declare function checkPrebuilds(options: CheckPrebuildsOptions): Promise<MissingPrebuildIssue[]>;
15
38
  export declare function listBarePrebuildFiles(hostDir: string): Promise<string[]>;
16
39
  //# sourceMappingURL=prebuilds.d.ts.map
@@ -1,26 +1,142 @@
1
1
  import { promises as fsp } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { formatAddonId } from './addon-source.js';
4
+ import { HOST_ADDON_IMPORT, resolveAddonPlatformPackage } from '../../expo/plugins/patches/qvac-platform-addons.js';
5
+ const PREBUILDS_DIR = 'prebuilds';
6
+ const PLATFORM_ADDON_DIR = 'addon';
7
+ /**
8
+ * Directories to search for an addon's `<host>` prebuild, in precedence order:
9
+ *
10
+ * 1. `<packageRoot>/prebuilds/<host>` — the fat layout every addon used to
11
+ * publish, and where source builds and `linked:` checkouts still land.
12
+ * 2. `<platformRoot>/addon/prebuilds/<host>` — the per-platform package the
13
+ * addon's `#host-addon` map names for this host, when that package is
14
+ * installed. Each platform package embeds an inner `addon/` package named
15
+ * after the meta addon so the `.bare` file keeps its name.
16
+ *
17
+ * A local `prebuilds/` always wins, matching the precedence the addons apply
18
+ * in their own `binding.js` (`require.addon()` first, platform package second).
19
+ */
20
+ export async function resolvePrebuildLocations(addon, host) {
21
+ const locations = [
22
+ { hostDir: path.join(addon.packageRoot, PREBUILDS_DIR, host) }
23
+ ];
24
+ const platformPackage = await platformPackageForHost(addon, host);
25
+ if (platformPackage === null)
26
+ return locations;
27
+ const platformRoot = await findInstalledPackage(await realPackageRoot(addon.packageRoot), platformPackage);
28
+ if (platformRoot !== null) {
29
+ locations.push({
30
+ hostDir: path.join(platformRoot, PLATFORM_ADDON_DIR, PREBUILDS_DIR, host),
31
+ platformPackage
32
+ });
33
+ }
34
+ return locations;
35
+ }
36
+ /**
37
+ * The directory the addon really lives in. Under pnpm's isolated layout
38
+ * `node_modules/@qvac/tts-ggml` is a symlink into the virtual store
39
+ * (`node_modules/.pnpm/<id>/node_modules/@qvac/tts-ggml`), and the addon's
40
+ * dependencies — its platform package included — are linked next to it in
41
+ * that store directory rather than at the project's top-level `node_modules`.
42
+ * Node resolves from the real path, so the platform-package search starts
43
+ * there too. An unresolvable path (a fixture, a bundle path that no longer
44
+ * exists) is searched as given.
45
+ */
46
+ async function realPackageRoot(packageRoot) {
47
+ try {
48
+ return await fsp.realpath(packageRoot);
49
+ }
50
+ catch {
51
+ return path.resolve(packageRoot);
52
+ }
53
+ }
4
54
  export async function checkPrebuilds(options) {
5
55
  const { addon, hosts } = options;
6
56
  const issues = [];
7
57
  for (const host of hosts) {
8
- const hostDir = path.join(addon.packageRoot, 'prebuilds', host);
9
- const present = (await listBarePrebuildFiles(hostDir)).length > 0;
10
- if (!present) {
11
- issues.push({
12
- code: 'missing-prebuild',
13
- level: 'error',
14
- addon: formatAddonId(addon),
15
- host,
16
- packageRoot: addon.packageRoot,
17
- message: `${formatAddonId(addon)} is missing a prebuild for ${host} ` +
18
- `(expected ${path.join(hostDir, '*.bare')}).`
19
- });
20
- }
58
+ if (addon.linkedHosts !== undefined && !addon.linkedHosts.includes(host))
59
+ continue;
60
+ const locations = await resolvePrebuildLocations(addon, host);
61
+ if (await anyLocationHasPrebuild(locations))
62
+ continue;
63
+ issues.push({
64
+ code: 'missing-prebuild',
65
+ level: 'error',
66
+ addon: formatAddonId(addon),
67
+ host,
68
+ packageRoot: addon.packageRoot,
69
+ message: await describeMissingPrebuild(addon, host, locations)
70
+ });
21
71
  }
22
72
  return issues;
23
73
  }
74
+ async function anyLocationHasPrebuild(locations) {
75
+ for (const location of locations) {
76
+ if ((await listBarePrebuildFiles(location.hostDir)).length > 0)
77
+ return true;
78
+ }
79
+ return false;
80
+ }
81
+ async function describeMissingPrebuild(addon, host, locations) {
82
+ const expected = locations.map((location) => path.join(location.hostDir, '*.bare'));
83
+ const platformPackage = await platformPackageForHost(addon, host);
84
+ const searchedPlatformPackage = locations.some((location) => location.platformPackage === platformPackage);
85
+ return (`${formatAddonId(addon)} is missing a prebuild for ${host} ` +
86
+ `(expected ${expected.join(' or ')}).` +
87
+ missingPlatformPackageHint(addon, platformPackage, searchedPlatformPackage));
88
+ }
89
+ function missingPlatformPackageHint(addon, platformPackage, searchedPlatformPackage) {
90
+ if (platformPackage === null || searchedPlatformPackage)
91
+ return '';
92
+ const pin = addon.version === undefined ? platformPackage : `"${platformPackage}": "${addon.version}"`;
93
+ return (` Add this exact dependency to package.json (same version as ${addon.name})` +
94
+ ` and reinstall: ${pin}`);
95
+ }
96
+ async function platformPackageForHost(addon, host) {
97
+ return resolveAddonPlatformPackage(addon.name, await readHostAddonMap(addon), host);
98
+ }
99
+ async function readHostAddonMap(addon) {
100
+ try {
101
+ const raw = await fsp.readFile(path.join(addon.packageRoot, 'package.json'), 'utf8');
102
+ const parsed = JSON.parse(raw);
103
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
104
+ return undefined;
105
+ const imports = parsed.imports;
106
+ return imports?.[HOST_ADDON_IMPORT];
107
+ }
108
+ catch {
109
+ return undefined;
110
+ }
111
+ }
112
+ /**
113
+ * Locates `packageName` the way Node's resolver would from `fromDir`: in
114
+ * `<dir>/node_modules/<packageName>` for `fromDir` and each of its ancestors.
115
+ * Covers a hoisted platform package next to the meta package, one nested
116
+ * under the meta package's own `node_modules`, and — given the meta package's
117
+ * real path — the sibling link pnpm places in its virtual-store directory.
118
+ */
119
+ async function findInstalledPackage(fromDir, packageName) {
120
+ let dir = path.resolve(fromDir);
121
+ for (;;) {
122
+ const candidate = path.join(dir, 'node_modules', packageName);
123
+ if (await isPackageDir(candidate))
124
+ return candidate;
125
+ const parent = path.dirname(dir);
126
+ if (parent === dir)
127
+ return null;
128
+ dir = parent;
129
+ }
130
+ }
131
+ async function isPackageDir(dir) {
132
+ try {
133
+ const stat = await fsp.stat(path.join(dir, 'package.json'));
134
+ return stat.isFile();
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }
24
140
  export async function listBarePrebuildFiles(hostDir) {
25
141
  let entries;
26
142
  try {
@@ -15,7 +15,8 @@ const DEFAULT_MEASUREMENT_DEPENDENCIES = {
15
15
  export async function acceptResourceCollectorPackaging(options) {
16
16
  const addons = await collectAddonsFromBundle({
17
17
  bundlePath: options.bundlePath,
18
- projectRoot: options.projectRoot
18
+ projectRoot: options.projectRoot,
19
+ hosts: options.hosts
19
20
  });
20
21
  const collectors = new Map(addons
21
22
  .filter((addon) => isResourceCollectorPackage(addon.name))
@@ -0,0 +1,23 @@
1
+ export declare const HOST_ADDON_IMPORT = "#host-addon";
2
+ export interface PlatformAddonRoot {
3
+ dir: string;
4
+ pkg: {
5
+ name?: string;
6
+ version?: string;
7
+ addon?: boolean;
8
+ };
9
+ }
10
+ export declare function resolvePlatformAddonRoots(projectRoot: string, addonNames: string[], platform: string): PlatformAddonRoot[];
11
+ /**
12
+ * Package `#host-addon` names for `host`, if that name belongs to `metaName`.
13
+ * `host` is a Bare host (`android-arm64`, `ios-arm64-simulator`, `darwin-arm64`)
14
+ * or a mobile build target (`android`, `ios`).
15
+ */
16
+ export declare function resolveAddonPlatformPackage(metaName: string, hostAddon: unknown, host: string): string | null;
17
+ /**
18
+ * First package name a `#host-addon` map points at for `host`.
19
+ * Android nests the package under its architecture; iOS is a flat list.
20
+ * A platform-only `android` target uses arm64, matching the current mobile host.
21
+ */
22
+ export declare function resolvePlatformPackageName(hostAddon: unknown, host: string): string | null;
23
+ //# sourceMappingURL=qvac-platform-addons.d.ts.map
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Resolves split-addon platform packages from the meta package's `#host-addon`
3
+ * map. Linker, verify, and the missing-prebuild error all call this module so
4
+ * the slice name cannot drift from what the addon actually imports.
5
+ *
6
+ * Keep this file free of `@/` imports: the compiled JS is copied next to the
7
+ * patched `link.mjs` as `qvac-platform-addons.mjs`.
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ export const HOST_ADDON_IMPORT = '#host-addon';
12
+ const DEFAULT_ANDROID_CPU = 'arm64';
13
+ export function resolvePlatformAddonRoots(projectRoot, addonNames, platform) {
14
+ const roots = [];
15
+ for (const name of addonNames) {
16
+ const root = resolvePlatformAddonRoot(projectRoot, name, platform);
17
+ if (root !== null)
18
+ roots.push(root);
19
+ }
20
+ return roots;
21
+ }
22
+ function resolvePlatformAddonRoot(projectRoot, metaName, platform) {
23
+ const metaManifest = readManifest(packageDir(projectRoot, metaName));
24
+ if (metaManifest === null)
25
+ return null;
26
+ const platformPackage = resolveAddonPlatformPackage(metaName, metaManifest.imports?.[HOST_ADDON_IMPORT], platform);
27
+ if (platformPackage === null)
28
+ return null;
29
+ const addonDir = path.join(packageDir(projectRoot, platformPackage), 'addon');
30
+ const addonManifest = readManifest(addonDir);
31
+ if (addonManifest === null || addonManifest.addon !== true)
32
+ return null;
33
+ if (!fs.existsSync(path.join(addonDir, 'prebuilds')))
34
+ return null;
35
+ return { dir: addonDir, pkg: addonManifest };
36
+ }
37
+ function packageDir(projectRoot, packageName) {
38
+ return path.join(projectRoot, 'node_modules', ...packageName.split('/'));
39
+ }
40
+ function readManifest(dir) {
41
+ try {
42
+ const parsed = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
43
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
44
+ return null;
45
+ return parsed;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /**
52
+ * Package `#host-addon` names for `host`, if that name belongs to `metaName`.
53
+ * `host` is a Bare host (`android-arm64`, `ios-arm64-simulator`, `darwin-arm64`)
54
+ * or a mobile build target (`android`, `ios`).
55
+ */
56
+ export function resolveAddonPlatformPackage(metaName, hostAddon, host) {
57
+ const name = resolvePlatformPackageName(hostAddon, host);
58
+ if (name === null)
59
+ return null;
60
+ if (!name.startsWith(`${metaName}-`))
61
+ return null;
62
+ return name;
63
+ }
64
+ /**
65
+ * First package name a `#host-addon` map points at for `host`.
66
+ * Android nests the package under its architecture; iOS is a flat list.
67
+ * A platform-only `android` target uses arm64, matching the current mobile host.
68
+ */
69
+ export function resolvePlatformPackageName(hostAddon, host) {
70
+ if (typeof host !== 'string' || host.length === 0)
71
+ return null;
72
+ const separator = host.indexOf('-');
73
+ const platform = separator === -1 ? host : host.slice(0, separator);
74
+ const cpu = separator === -1 ? '' : host.slice(separator + 1);
75
+ const branch = readBranch(hostAddon, platform);
76
+ const direct = packageName(branch);
77
+ if (direct !== null)
78
+ return direct;
79
+ const cpuKey = cpu.length > 0 ? cpu : DEFAULT_ANDROID_CPU;
80
+ return packageName(readBranch(branch, cpuKey));
81
+ }
82
+ function packageName(candidate) {
83
+ const name = Array.isArray(candidate) ? candidate[0] : candidate;
84
+ if (typeof name !== 'string' || name.startsWith('.'))
85
+ return null;
86
+ return name;
87
+ }
88
+ function readBranch(value, key) {
89
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
90
+ return undefined;
91
+ if (!Object.hasOwn(value, key))
92
+ return undefined;
93
+ return value[key];
94
+ }
@@ -1,4 +1,13 @@
1
1
  import type { ExpoConfig } from 'expo/config';
2
+ /**
3
+ * Desktop-only spawn path, deferred so bare-pack does not walk `bare-process`
4
+ * -> `bare-posix` (no `android-arm64` prebuild). Mobile advisory uses
5
+ * in-process `@qvac/model-fit` (`fitParams`), not this subprocess.
6
+ */
7
+ declare const MOBILE_UNSUPPORTED_MODULES: string[];
8
+ type MobilePlatform = 'android' | 'ios';
9
+ declare const MOBILE_HOSTS_BY_PLATFORM: Record<MobilePlatform, string[]>;
10
+ /** Every mobile host, in platform order. The bundle covers all of them. */
2
11
  declare const MOBILE_HOSTS: string[];
3
12
  type BareKitLinkerPaths = {
4
13
  android: string | null;
@@ -11,6 +20,7 @@ type BareKitLinkerPaths = {
11
20
  * Uses `qvac.config.*` if present.
12
21
  */
13
22
  declare function withMobileBundle(config: ExpoConfig): ExpoConfig;
23
+ declare function mobileHostsForPlatform(platform: string): string[];
14
24
  /** Runs the patched iOS linker after bundleSdk has written the current addons manifest. */
15
25
  declare function runIOSAddonLinker(linkerPath: string): Promise<void>;
16
26
  /**
@@ -18,7 +28,7 @@ declare function runIOSAddonLinker(linkerPath: string): Promise<void>;
18
28
  * the paths for each platform that was successfully patched.
19
29
  */
20
30
  declare function patchBareKitLinkers(projectRoot: string, qvacSdkPath: string): BareKitLinkerPaths;
21
- export { MOBILE_HOSTS, patchBareKitLinkers, runIOSAddonLinker };
22
- export type { BareKitLinkerPaths };
31
+ export { MOBILE_HOSTS, MOBILE_HOSTS_BY_PLATFORM, MOBILE_UNSUPPORTED_MODULES, mobileHostsForPlatform, patchBareKitLinkers, runIOSAddonLinker };
32
+ export type { BareKitLinkerPaths, MobilePlatform };
23
33
  export default withMobileBundle;
24
34
  //# sourceMappingURL=withMobileBundle.d.ts.map