crossbind 2.0.0-beta.60 → 2.0.0-beta.63

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 (118) hide show
  1. package/README.md +8 -6
  2. package/package.json +6 -1
  3. package/src/actions/buildDependencies.js +37 -9
  4. package/src/actions/buildExternal.js +9 -1
  5. package/src/actions/buildJs.js +1 -1
  6. package/src/actions/buildLib.js +27 -8
  7. package/src/actions/buildNativeCommand.js +84 -0
  8. package/src/actions/buildNode.js +190 -0
  9. package/src/actions/buildTypes.js +8 -2
  10. package/src/actions/buildWasiCommand.js +0 -4
  11. package/src/actions/buildWasm.js +92 -95
  12. package/src/actions/cloudCommands.js +133 -0
  13. package/src/actions/createImportedBridges.js +64 -0
  14. package/src/actions/createInterface.js +318 -65
  15. package/src/actions/createLib.js +60 -43
  16. package/src/actions/getAllBridges.js +1 -1
  17. package/src/actions/getData.js +4 -1
  18. package/src/actions/getLinkInputs.js +70 -0
  19. package/src/actions/isSourceNewer.js +2 -0
  20. package/src/actions/licenses.js +175 -17
  21. package/src/actions/prepareConanDependencies.js +51 -0
  22. package/src/actions/publishBinary.js +32 -0
  23. package/src/actions/run.js +124 -32
  24. package/src/actions/runnerCommands.js +421 -0
  25. package/src/actions/writeRuntimeEntry.js +69 -0
  26. package/src/assets/cmake/CMakeLists.txt +1 -1
  27. package/src/assets/cmake/command/CMakeLists.txt +21 -0
  28. package/src/assets/cmake/command/command.cpp +1 -0
  29. package/src/assets/cmake/dist.cmake +2 -1
  30. package/src/assets/configure/install.sh +22 -0
  31. package/src/assets/js-runtime/adapters/path-url.js +6 -3
  32. package/src/assets/js-runtime/adapters/unboundMessage.js +2 -0
  33. package/src/assets/js-runtime/adapters/worker-comlink.js +87 -56
  34. package/src/assets/js-runtime/core.js +11 -0
  35. package/src/assets/node-import-hooks.mjs +61 -0
  36. package/src/assets/toolchain-digests.json +27 -13
  37. package/src/assets/wasi-runtime/stubs.c +29 -0
  38. package/src/bin.js +290 -37
  39. package/src/index.js +6 -1
  40. package/src/integration/bindHeaderImports.js +18 -0
  41. package/src/integration/getCrossbindScript.js +53 -19
  42. package/src/integration/getDependFilePath.js +18 -12
  43. package/src/integration/resolveNativeImport.js +14 -0
  44. package/src/node/dev.mjs +19 -0
  45. package/src/runner/blobs.js +135 -0
  46. package/src/runner/files.js +193 -0
  47. package/src/runner/server.js +320 -0
  48. package/src/state/attachConanDependencies.js +56 -0
  49. package/src/state/index.js +31 -20
  50. package/src/state/loadConfig.js +36 -24
  51. package/src/state/refreshBuiltDependencies.js +20 -0
  52. package/src/state/refreshConanDependencies.js +17 -0
  53. package/src/utils/androidToolchain.js +4 -0
  54. package/src/utils/appRustCrates.js +17 -1
  55. package/src/utils/appSources.js +112 -0
  56. package/src/utils/archiveFlags.js +12 -0
  57. package/src/utils/boundHeaders.js +79 -0
  58. package/src/utils/bridgeExtras.js +486 -0
  59. package/src/utils/bridgeRuntimeFixes.js +48 -0
  60. package/src/utils/bundledLicenses.js +26 -0
  61. package/src/utils/cargoImport.js +44 -0
  62. package/src/utils/cargoTarget.js +23 -3
  63. package/src/utils/cloudAccount.js +55 -0
  64. package/src/utils/conanDependencies.js +66 -0
  65. package/src/utils/conanImport.js +58 -0
  66. package/src/utils/conanInstall.js +154 -0
  67. package/src/utils/conanProfile.js +203 -0
  68. package/src/utils/conanStage.js +292 -0
  69. package/src/utils/copyToDistSource.js +13 -0
  70. package/src/utils/cppDts.js +134 -55
  71. package/src/utils/darwinToolchain.js +11 -0
  72. package/src/utils/dirLock.js +137 -28
  73. package/src/utils/downloadAndExtractFile.js +34 -7
  74. package/src/utils/embindArgumentGuards.js +84 -0
  75. package/src/utils/embindCallArguments.js +19 -0
  76. package/src/utils/embindJspiQueue.js +25 -0
  77. package/src/utils/embindRsFingerprint.js +26 -0
  78. package/src/utils/execContainer.js +49 -0
  79. package/src/utils/headerImports.js +56 -0
  80. package/src/utils/hostPlatform.js +21 -0
  81. package/src/utils/inputStamp.js +23 -0
  82. package/src/utils/iosToolchain.js +6 -0
  83. package/src/utils/isSourceCmakePackage.js +10 -0
  84. package/src/utils/licenseReport.js +11 -2
  85. package/src/utils/linkLayout.js +8 -1
  86. package/src/utils/linuxToolchain.js +24 -0
  87. package/src/utils/makeTreeWritable.js +12 -0
  88. package/src/utils/nativeImports.js +23 -0
  89. package/src/utils/nativeSourceStamp.js +36 -0
  90. package/src/utils/nodeDevBuild.js +92 -0
  91. package/src/utils/packageLicense.js +101 -0
  92. package/src/utils/pthreadMainScript.js +14 -0
  93. package/src/utils/pullDockerImage.js +10 -5
  94. package/src/utils/relocatePrebuilt.js +119 -0
  95. package/src/utils/remoteClient.js +127 -0
  96. package/src/utils/remoteOutputs.js +39 -0
  97. package/src/utils/remoteRunner.js +97 -0
  98. package/src/utils/remoteUpload.js +134 -0
  99. package/src/utils/replaceFile.js +8 -0
  100. package/src/utils/resolveCompanionPackage.js +51 -0
  101. package/src/utils/resolveEmbindNapi.js +22 -0
  102. package/src/utils/resolveEmbindRust.js +17 -52
  103. package/src/utils/runCargo.js +51 -53
  104. package/src/utils/runConan.js +257 -0
  105. package/src/utils/runtimeEntries.js +56 -0
  106. package/src/utils/rustBridgeGen.js +903 -253
  107. package/src/utils/rustMt.js +4 -0
  108. package/src/utils/scopedEmbind.js +22 -0
  109. package/src/utils/selectRunner.js +81 -0
  110. package/src/utils/sourceFingerprint.js +4 -3
  111. package/src/utils/swigInterface.js +86 -6
  112. package/src/utils/systemKeys.js +43 -3
  113. package/src/utils/targets.js +104 -3
  114. package/src/utils/toolchainNotices.js +5 -0
  115. package/src/utils/wasiToolchain.js +14 -1
  116. package/src/utils/windowsToolchain.js +18 -0
  117. package/vitest.config.js +11 -0
  118. package/src/utils/cppFieldBindings.js +0 -23
@@ -0,0 +1,257 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import getOsUserAndGroupId from './getOsUserAndGroupId.js';
6
+ import makeTreeWritable from './makeTreeWritable.js';
7
+ import pullDockerImage, { getDockerImage, getDockerContainerName, imageRoleFor } from './pullDockerImage.js';
8
+ import { DOCKER_RUN_SECURITY_ARGS } from './dockerSecurity.js';
9
+ import assertExecContainer from './execContainer.js';
10
+ import { IOS_DEVELOPER_DIR, XCODE_TOOLCHAIN_BIN } from './iosToolchain.js';
11
+ import { DARWIN_CC, DARWIN_TOOLS_BIN } from './darwinToolchain.js';
12
+ import { HOST_BUILT_PLATFORMS } from './targets.js';
13
+ import { remoteExecParams } from './remoteRunner.js';
14
+ import { runnerFor } from './selectRunner.js';
15
+
16
+ // Every conan invocation crossbind makes goes through here. The config is passed in instead of read
17
+ // from state, because state attaches the staged Conan packages while it is still being built.
18
+ //
19
+ // A Conan home holds code conan runs (plugins, hooks) and settings every recipe obeys (global.conf,
20
+ // remotes), and the recipes it builds can write to it. So every run gets a home of its own, made in a
21
+ // fresh work directory and deleted with it; only the package store outlives a run. On the host the
22
+ // environment is rebuilt from an allowlist, so CONAN_DEFAULT_PROFILE, a stray CC or a remote's
23
+ // credentials never reach conan.
24
+ const ALLOWED_ENV = [
25
+ /^PATH$/,
26
+ /^HOME$/,
27
+ /^USER$/,
28
+ /^LOGNAME$/,
29
+ /^SHELL$/,
30
+ /^TERM$/,
31
+ /^TMPDIR$/,
32
+ /^TEMP$/,
33
+ /^TMP$/,
34
+ /^LANG$/,
35
+ /^LC_[A-Z_]+$/,
36
+ /^(HTTP|HTTPS|ALL|NO)_PROXY$/i,
37
+ /^SSL_CERT_(FILE|DIR)$/,
38
+ /^REQUESTS_CA_BUNDLE$/,
39
+ // A host Emscripten under RUNNER=LOCAL.
40
+ /^EMSDK(_NODE|_PYTHON)?$/,
41
+ /^EM_(CONFIG|CACHE)$/,
42
+ /^SYSTEMROOT$/i,
43
+ /^WINDIR$/i,
44
+ /^USERPROFILE$/i,
45
+ /^PROGRAMDATA$/i,
46
+ ];
47
+
48
+ // What conan can build on the host: wasm with a host Emscripten, iOS and macOS with Xcode.
49
+ const HOST_PLATFORMS = ['wasm', 'ios', 'darwin'];
50
+ const APPLE_PLATFORM_NAMES = { ios: 'iOS', darwin: 'macOS' };
51
+ // Where a container sees conanRoot(): an exec container mounts all of it here, a run container only
52
+ // the store and its own work directory, at the same places.
53
+ const CONTAINER_ROOT = '/var/cache/crossbind/conan';
54
+ // The package folders conan reports, which the host stages from: downloaded binaries sit in p/<ref>/p, ones
55
+ // conan built in b/<ref>/p. Sources and build folders stay on a remote runner.
56
+ const REMOTE_STORE_OUTPUTS = ['p/*/p', 'b/*/p'];
57
+ // A recipe's build log is the whole compiler output of a library.
58
+ const MAX_BUFFER = 256 * 1024 * 1024;
59
+ // The JSON graph carries conandata, the source URL and SHA-256 of the license rows, from 2.19 on.
60
+ const MIN_CONAN_VERSION = [2, 19];
61
+
62
+ export function conanRunner(config, target) {
63
+ const { runner } = runnerFor(imageRoleFor(target), config.system);
64
+ // Xcode runs on the Mac alone, so iOS and macOS packages build there whatever runner the rest use.
65
+ return HOST_BUILT_PLATFORMS.includes(target?.platform) ? 'LOCAL' : runner;
66
+ }
67
+
68
+ // Machine-wide like the cargo cache: built packages are reused by every project, and `pnpm run clear`
69
+ // in one project must not throw them away. Containers write to the store and conan runs the recipes
70
+ // it keeps there, so conan on the host never shares a store with them.
71
+ export function conanRoot(runner = 'DOCKER_RUN') {
72
+ return path.join(os.homedir(), '.crossbind', runner === 'LOCAL' ? 'conan-local' : 'conan');
73
+ }
74
+
75
+ // The directory one conan run reads its inputs from and writes its outputs to, with the home it runs
76
+ // with. conanPath names a file of it as conan sees it.
77
+ export function createConanWork(runner) {
78
+ const root = conanRoot(runner);
79
+ const store = path.join(root, 'store');
80
+ fs.mkdirSync(store, { recursive: true });
81
+ fs.mkdirSync(path.join(root, 'work'), { recursive: true });
82
+ const dir = fs.mkdtempSync(path.join(root, 'work', 'run-'));
83
+ const conanDir = runner === 'LOCAL' ? dir : `${CONTAINER_ROOT}/work/${path.basename(dir)}`;
84
+ const conanPath = (file) => (runner === 'LOCAL' ? path.join(dir, file) : `${conanDir}/${file}`);
85
+ // conan takes its home from the first .conanrc at or above its working directory, ahead of
86
+ // CONAN_HOME; one in the working directory itself is always that first one.
87
+ fs.writeFileSync(path.join(dir, '.conanrc'), 'conan_home=./home\n');
88
+ fs.mkdirSync(path.join(dir, 'home'));
89
+ fs.writeFileSync(path.join(dir, 'home', 'global.conf'), [
90
+ `core.cache:storage_path=${runner === 'LOCAL' ? store : `${CONTAINER_ROOT}/store`}`,
91
+ 'core:non_interactive=True',
92
+ '',
93
+ ].join('\n'));
94
+ return {
95
+ runner, root, store, dir, conanDir, conanPath,
96
+ };
97
+ }
98
+
99
+ function removeTree(dir) {
100
+ if (!fs.existsSync(dir)) return;
101
+ makeTreeWritable(dir);
102
+ fs.rmSync(dir, { recursive: true, force: true });
103
+ }
104
+
105
+ export const removeConanWork = (work) => removeTree(work.dir);
106
+
107
+ // What runs that were killed left behind. Only while holding the install lock: no run is going then.
108
+ export const clearConanWork = (runner) => removeTree(path.join(conanRoot(runner), 'work'));
109
+
110
+ // conan reports paths as it sees them. In a container that is through the mounts, which all sit under
111
+ // CONTAINER_ROOT, so mapping the prefix back is exact; a path anywhere else is none crossbind gave it.
112
+ export function toHostPath(reported, work) {
113
+ if (work.runner === 'LOCAL') return path.resolve(reported);
114
+ const clean = path.posix.normalize(reported);
115
+ // A backslash is a separator again once the path is joined on a Windows host.
116
+ if (reported.includes('\\') || !clean.startsWith(`${CONTAINER_ROOT}/`)) {
117
+ throw new Error(`crossbind: conan reported ${JSON.stringify(reported)}, outside the directories crossbind mounts for it.`);
118
+ }
119
+ return path.join(work.root, clean.slice(CONTAINER_ROOT.length + 1));
120
+ }
121
+
122
+ function allowedEnv() {
123
+ return Object.fromEntries(Object.entries(process.env).filter(([key]) => ALLOWED_ENV.some((allowed) => allowed.test(key))));
124
+ }
125
+
126
+ // The Xcode every crossbind iOS archive builds with, whichever one xcode-select points at.
127
+ const iosEnv = () => ({ ...allowedEnv(), DEVELOPER_DIR: IOS_DEVELOPER_DIR });
128
+
129
+ // A GNU ar or nm ahead of Apple's on a Mac's PATH (Homebrew's binutils) writes archives Apple's linker
130
+ // cannot read, and recipes, the build tools they build and Meson all take these tools by name. So the
131
+ // run gets links to Xcode's first on its PATH; for macOS to the ones xcode-select picks, as the macOS
132
+ // ports are built with.
133
+ const APPLE_TOOLS = {
134
+ ar: 'ar', as: 'as', nm: 'llvm-nm', ranlib: 'ranlib', strip: 'strip',
135
+ };
136
+ const appleToolOf = (target, name) => (target.platform === 'darwin'
137
+ ? path.join(DARWIN_TOOLS_BIN, name)
138
+ : path.join(XCODE_TOOLCHAIN_BIN, APPLE_TOOLS[name]));
139
+
140
+ function withAppleTools(work, env, target) {
141
+ if (process.platform !== 'darwin') return env;
142
+ const dir = path.join(work.dir, 'apple-tools');
143
+ if (!fs.existsSync(dir)) {
144
+ fs.mkdirSync(dir);
145
+ Object.keys(APPLE_TOOLS).forEach((name) => fs.symlinkSync(appleToolOf(target, name), path.join(dir, name)));
146
+ }
147
+ return { ...env, PATH: [dir, env.PATH].filter(Boolean).join(path.delimiter) };
148
+ }
149
+
150
+ const toolVersions = new Map();
151
+ // The first line a tool prints for --version, asked once.
152
+ function toolVersion(command, env = allowedEnv(), cwd = undefined) {
153
+ const key = command.join(' ');
154
+ if (!toolVersions.has(key)) {
155
+ const [program, ...args] = command;
156
+ const result = spawnSync(program, [...args, '--version'], { encoding: 'utf8', env, cwd });
157
+ toolVersions.set(key, `${result.stdout ?? ''}`.split('\n')[0].trim());
158
+ }
159
+ return toolVersions.get(key);
160
+ }
161
+
162
+ // conan migrates the home it starts in, even for --version, so it is asked from a home of crossbind's
163
+ // instead of the user's ~/.conan2.
164
+ function conanVersion() {
165
+ const dir = path.join(conanRoot('LOCAL'), 'probe');
166
+ fs.mkdirSync(dir, { recursive: true });
167
+ fs.writeFileSync(path.join(dir, '.conanrc'), 'conan_home=./home\n');
168
+ return toolVersion(['conan'], allowedEnv(), dir);
169
+ }
170
+
171
+ function compilerIdentity(target) {
172
+ if (target?.platform === 'ios') return toolVersion(['xcrun', 'clang'], iosEnv());
173
+ if (target?.platform === 'darwin') return toolVersion([DARWIN_CC]);
174
+ return toolVersion(['emcc']);
175
+ }
176
+
177
+ // What a host conan builds with; for a container the image digest says the same.
178
+ export function localToolchainIdentity(target) {
179
+ return `${compilerIdentity(target)}\n${conanVersion()}`;
180
+ }
181
+
182
+ function assertLocalConan(target) {
183
+ const conan = conanVersion();
184
+ const [major, minor] = (conan.match(/(\d+)\.(\d+)/) ?? []).slice(1).map(Number);
185
+ const [needMajor, needMinor] = MIN_CONAN_VERSION;
186
+ if (major > needMajor || (major === needMajor && minor >= needMinor)) return;
187
+ const need = `Conan ${MIN_CONAN_VERSION.join('.')} or later; found ${conan || 'none'}.`;
188
+ throw new Error(HOST_BUILT_PLATFORMS.includes(target.platform)
189
+ ? `crossbind: Conan packages for ${APPLE_PLATFORM_NAMES[target.platform]} build on this Mac, with the conan on the PATH, which must be ${need} Install or upgrade it with \`brew install conan\` (\`brew upgrade conan\`) or \`pipx install conan\`.`
190
+ : `crossbind: RUNNER=LOCAL runs conan from the PATH and needs ${need}`);
191
+ }
192
+
193
+ export default function runConan(args, { config, target, work }) {
194
+ const options = { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', maxBuffer: MAX_BUFFER };
195
+ const role = imageRoleFor(target);
196
+ if (work.runner === 'LOCAL') {
197
+ if (!HOST_PLATFORMS.includes(target.platform)) {
198
+ throw new Error(`crossbind: Conan packages for ${target.platform} build in the ${role} image, which RUNNER=LOCAL does not use.`);
199
+ }
200
+ if (HOST_BUILT_PLATFORMS.includes(target.platform) && process.platform !== 'darwin') {
201
+ throw new Error(`crossbind: Conan packages for ${APPLE_PLATFORM_NAMES[target.platform]} build with Xcode, on a Mac.`);
202
+ }
203
+ assertLocalConan(target);
204
+ const env = withAppleTools(work, { ...(target.platform === 'ios' ? iosEnv() : allowedEnv()), CONAN_HOME: path.join(work.dir, 'home') }, target);
205
+ return spawnSync('conan', args, { ...options, cwd: work.dir, env });
206
+ }
207
+
208
+ // Google ships the linux NDK for x86_64 only.
209
+ const platform = target.platform === 'android' ? 'linux/amd64' : undefined;
210
+ if (work.runner === 'REMOTE') {
211
+ return spawnSync(...remoteExecParams({
212
+ remote: runnerFor(role, config.system).remote,
213
+ role,
214
+ image: getDockerImage(role, platform),
215
+ mounts: [
216
+ { host: work.store, container: `${CONTAINER_ROOT}/store`, inputRoots: [], outputRoots: REMOTE_STORE_OUTPUTS },
217
+ { host: work.dir, container: work.conanDir, inputRoots: ['.'], outputRoots: ['.'] },
218
+ ],
219
+ cwd: work.conanDir,
220
+ argv: ['conan', ...args],
221
+ env: { CONAN_HOME: work.conanPath('home') },
222
+ }, options));
223
+ }
224
+
225
+ const env = ['-e', `CONAN_HOME=${work.conanPath('home')}`];
226
+ let runnerArgs;
227
+ if (work.runner === 'DOCKER_EXEC') {
228
+ const name = getDockerContainerName(config.paths.base, role);
229
+ assertExecContainer({
230
+ name,
231
+ mounts: [[CONTAINER_ROOT, work.root]],
232
+ workdir: work.conanDir,
233
+ hint: `Recreate it with \`crossbind docker stop ${role}\`, \`crossbind docker delete ${role}\` and \`crossbind docker create ${role}\`, which mount ${work.root} at ${CONTAINER_ROOT}.`,
234
+ mountNote: 'It predates the conan mount.',
235
+ });
236
+ runnerArgs = ['exec', ...env, '--user', getOsUserAndGroupId(), '--workdir', work.conanDir, name];
237
+ } else {
238
+ pullDockerImage(role, platform);
239
+ runnerArgs = [
240
+ 'run',
241
+ '--rm',
242
+ ...(platform ? ['--platform', platform] : []),
243
+ ...DOCKER_RUN_SECURITY_ARGS,
244
+ '-v',
245
+ `${work.store}:${CONTAINER_ROOT}/store`,
246
+ '-v',
247
+ `${work.dir}:${work.conanDir}`,
248
+ ...env,
249
+ '--user',
250
+ getOsUserAndGroupId(),
251
+ '--workdir',
252
+ work.conanDir,
253
+ getDockerImage(role, platform),
254
+ ];
255
+ }
256
+ return spawnSync('docker', [...runnerArgs, 'conan', ...args], options);
257
+ }
@@ -0,0 +1,56 @@
1
+ // The one module a package publishes per runtime binary, dist/<runtimeEnv>/<format>.mjs. It exports every
2
+ // bound name as an ESM live binding that initNative() fills, the way a bundler's module for a header does,
3
+ // so `import { initNative, X } from '<package>/node/napi'` works with or without init options.
4
+
5
+ const PROXY_NAMES = new Set(['initNative', 'AllSymbols']);
6
+
7
+ // A name is a plain string or { local, wire }: the second exports `local` while reading the differently
8
+ // registered `wire` off the module. Two headers may bind one name; the module exports it once.
9
+ export function uniqueBindings(names) {
10
+ const seen = new Set();
11
+ return names
12
+ .map((name) => (typeof name === 'string' ? { local: name, wire: name } : name))
13
+ .filter(({ local }) => {
14
+ if (PROXY_NAMES.has(local) || seen.has(local)) return false;
15
+ seen.add(local);
16
+ return true;
17
+ });
18
+ }
19
+
20
+ // Each runtime's loader, imported as `boot`, and the config it boots with; the binaries sit in dist, one
21
+ // directory above the entry.
22
+ export const ENTRY_RUNTIMES = {
23
+ // The Node-API loader and the Node wasm build are both CommonJS.
24
+ node: (loader) => ({
25
+ imports: ["import { createRequire } from 'node:module';", `const boot = createRequire(import.meta.url)('../${loader}');`],
26
+ config: 'config',
27
+ }),
28
+ // Edge runtimes cannot compile WebAssembly from bytes, so the binary is a module import their bundler resolves.
29
+ edge: (loader, wasm) => ({
30
+ imports: [`import boot from '../${loader}';`, `import wasm from '../${wasm}';`],
31
+ config: '{ getWasmFunction: () => wasm, ...config }',
32
+ }),
33
+ };
34
+
35
+ // A bound name may be a JavaScript reserved word, which an export list accepts but a declaration does not.
36
+ export function runtimeEntryModule(names, { imports, config }) {
37
+ const bindings = uniqueBindings(names);
38
+ return [
39
+ '// Generated by crossbind - do not edit.',
40
+ ...imports,
41
+ '',
42
+ 'export let AllSymbols = {};',
43
+ ...bindings.map(({ local }) => `let $${local} = null;`),
44
+ ...(bindings.length ? [`export { ${bindings.map(({ local }) => `$${local} as ${local}`).join(', ')} };`] : []),
45
+ '',
46
+ 'export function initNative(config = {}) {',
47
+ ` return boot(${config}).then((m) => {`,
48
+ ' AllSymbols = m;',
49
+ ...bindings.map(({ local, wire }) => ` $${local} = m.${wire};`),
50
+ ' return m;',
51
+ ' });',
52
+ '}',
53
+ 'initNative.terminate = () => boot.terminate();',
54
+ '',
55
+ ].join('\n');
56
+ }