crossbind 2.0.0-beta.62 → 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 (107) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -1
  3. package/src/actions/buildDependencies.js +15 -2
  4. package/src/actions/buildExternal.js +9 -1
  5. package/src/actions/buildLib.js +25 -8
  6. package/src/actions/buildNativeCommand.js +84 -0
  7. package/src/actions/buildNode.js +48 -7
  8. package/src/actions/buildTypes.js +8 -2
  9. package/src/actions/buildWasiCommand.js +0 -4
  10. package/src/actions/buildWasm.js +49 -29
  11. package/src/actions/cloudCommands.js +133 -0
  12. package/src/actions/createImportedBridges.js +64 -0
  13. package/src/actions/createInterface.js +247 -68
  14. package/src/actions/createLib.js +57 -41
  15. package/src/actions/getData.js +4 -1
  16. package/src/actions/getLinkInputs.js +12 -5
  17. package/src/actions/isSourceNewer.js +2 -0
  18. package/src/actions/licenses.js +143 -19
  19. package/src/actions/prepareConanDependencies.js +51 -0
  20. package/src/actions/publishBinary.js +32 -0
  21. package/src/actions/run.js +63 -66
  22. package/src/actions/runnerCommands.js +421 -0
  23. package/src/actions/writeRuntimeEntry.js +69 -0
  24. package/src/assets/cmake/command/CMakeLists.txt +21 -0
  25. package/src/assets/cmake/command/command.cpp +1 -0
  26. package/src/assets/cmake/dist.cmake +2 -1
  27. package/src/assets/configure/install.sh +22 -0
  28. package/src/assets/js-runtime/adapters/path-url.js +6 -3
  29. package/src/assets/js-runtime/adapters/unboundMessage.js +2 -0
  30. package/src/assets/js-runtime/adapters/worker-comlink.js +87 -60
  31. package/src/assets/js-runtime/core.js +11 -0
  32. package/src/assets/node-import-hooks.mjs +61 -0
  33. package/src/assets/toolchain-digests.json +19 -19
  34. package/src/assets/wasi-runtime/stubs.c +29 -0
  35. package/src/bin.js +257 -50
  36. package/src/index.js +3 -0
  37. package/src/integration/bindHeaderImports.js +18 -0
  38. package/src/integration/getCrossbindScript.js +46 -10
  39. package/src/integration/getDependFilePath.js +9 -0
  40. package/src/integration/resolveNativeImport.js +14 -0
  41. package/src/node/dev.mjs +19 -0
  42. package/src/runner/blobs.js +135 -0
  43. package/src/runner/files.js +193 -0
  44. package/src/runner/server.js +320 -0
  45. package/src/state/attachConanDependencies.js +56 -0
  46. package/src/state/index.js +17 -9
  47. package/src/state/loadConfig.js +33 -24
  48. package/src/state/refreshBuiltDependencies.js +20 -0
  49. package/src/state/refreshConanDependencies.js +17 -0
  50. package/src/utils/androidToolchain.js +4 -0
  51. package/src/utils/appRustCrates.js +15 -1
  52. package/src/utils/appSources.js +97 -6
  53. package/src/utils/archiveFlags.js +12 -0
  54. package/src/utils/boundHeaders.js +79 -0
  55. package/src/utils/bridgeExtras.js +486 -0
  56. package/src/utils/bridgeRuntimeFixes.js +48 -0
  57. package/src/utils/bundledLicenses.js +26 -0
  58. package/src/utils/cargoTarget.js +21 -3
  59. package/src/utils/cloudAccount.js +55 -0
  60. package/src/utils/conanDependencies.js +66 -0
  61. package/src/utils/conanImport.js +58 -0
  62. package/src/utils/conanInstall.js +154 -0
  63. package/src/utils/conanProfile.js +203 -0
  64. package/src/utils/conanStage.js +292 -0
  65. package/src/utils/copyToDistSource.js +13 -0
  66. package/src/utils/cppDts.js +58 -15
  67. package/src/utils/darwinToolchain.js +11 -0
  68. package/src/utils/dirLock.js +137 -28
  69. package/src/utils/downloadAndExtractFile.js +34 -7
  70. package/src/utils/embindArgumentGuards.js +41 -2
  71. package/src/utils/embindCallArguments.js +19 -0
  72. package/src/utils/embindJspiQueue.js +25 -0
  73. package/src/utils/execContainer.js +49 -0
  74. package/src/utils/headerImports.js +16 -5
  75. package/src/utils/hostPlatform.js +21 -0
  76. package/src/utils/iosToolchain.js +6 -0
  77. package/src/utils/isSourceCmakePackage.js +10 -0
  78. package/src/utils/licenseReport.js +11 -2
  79. package/src/utils/linuxToolchain.js +24 -0
  80. package/src/utils/makeTreeWritable.js +12 -0
  81. package/src/utils/nativeImports.js +23 -0
  82. package/src/utils/nativeSourceStamp.js +36 -0
  83. package/src/utils/nodeDevBuild.js +92 -0
  84. package/src/utils/packageLicense.js +101 -0
  85. package/src/utils/pthreadMainScript.js +14 -0
  86. package/src/utils/pullDockerImage.js +3 -1
  87. package/src/utils/relocatePrebuilt.js +119 -0
  88. package/src/utils/remoteClient.js +127 -0
  89. package/src/utils/remoteOutputs.js +39 -0
  90. package/src/utils/remoteRunner.js +97 -0
  91. package/src/utils/remoteUpload.js +134 -0
  92. package/src/utils/replaceFile.js +8 -0
  93. package/src/utils/runCargo.js +48 -51
  94. package/src/utils/runConan.js +257 -0
  95. package/src/utils/runtimeEntries.js +56 -0
  96. package/src/utils/rustBridgeGen.js +9 -4
  97. package/src/utils/rustMt.js +4 -0
  98. package/src/utils/scopedEmbind.js +22 -0
  99. package/src/utils/selectRunner.js +81 -0
  100. package/src/utils/sourceFingerprint.js +4 -3
  101. package/src/utils/swigInterface.js +69 -4
  102. package/src/utils/systemKeys.js +35 -3
  103. package/src/utils/targets.js +49 -4
  104. package/src/utils/toolchainNotices.js +5 -0
  105. package/src/utils/wasiToolchain.js +14 -1
  106. package/src/utils/windowsToolchain.js +18 -0
  107. package/vitest.config.js +11 -0
@@ -0,0 +1,133 @@
1
+ import {
2
+ CREDENTIALS_FILE, cloudCredentials, readCredentials, writeCredentials,
3
+ } from '../utils/cloudAccount.js';
4
+
5
+ // `crossbind login`, `logout` and `usage`, against tooling/cloud/worker/account-api.js.
6
+
7
+ const GITHUB_ORIGIN = 'https://github.com';
8
+ const TOKEN_PATTERN = /^cbt_[A-Za-z0-9_-]{43}$/;
9
+ const MAX_NAME_LENGTH = 64;
10
+ // GitHub's defaults for a device code; it asks for no faster polling than this.
11
+ const MIN_INTERVAL_SECONDS = 5;
12
+ const DEFAULT_EXPIRES_SECONDS = 900;
13
+
14
+ const wait = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); });
15
+ // What the cloud sends ends up on the terminal: no escape sequences.
16
+ const printable = (value) => String(value ?? '').replace(/\p{Cc}/gu, '');
17
+ const paceOf = (seconds, fallback) => Math.max(MIN_INTERVAL_SECONDS, Number.isFinite(seconds) ? seconds : fallback);
18
+
19
+ async function call(fetcher, url, route, { method = 'GET', token, body } = {}) {
20
+ let response;
21
+ try {
22
+ // A redirect would carry the token to an address nobody configured.
23
+ response = await fetcher(`${url}${route}`, {
24
+ method,
25
+ headers: { ...(token ? { authorization: `Bearer ${token}` } : {}), ...(body ? { 'content-type': 'application/json' } : {}) },
26
+ body: body && JSON.stringify(body),
27
+ redirect: 'error',
28
+ });
29
+ } catch (error) {
30
+ throw new Error(`crossbind: crossbind cloud at ${url} is unreachable (${error.cause?.code ?? error.message}).`, { cause: error });
31
+ }
32
+ const answer = await response.json().catch(() => null);
33
+ return { status: response.status, ok: response.ok, body: answer !== null && typeof answer === 'object' ? answer : {} };
34
+ }
35
+
36
+ const refused = ({ status, body }) => new Error(`crossbind: ${printable(body.error ?? `crossbind cloud answered ${status}.`)}`);
37
+
38
+ // { revoked: true }, or { revoked: false, reason }.
39
+ async function revoke(fetcher, url, token, all) {
40
+ try {
41
+ const answer = await call(fetcher, url, all ? '/v1/tokens' : '/v1/token', { method: 'DELETE', token });
42
+ // 401: the cloud no longer knows this token, so there is nothing left to revoke, unless every token was asked for.
43
+ return answer.ok || (!all && answer.status === 401) ? { revoked: true } : { revoked: false, reason: refused(answer).message };
44
+ } catch (error) {
45
+ return { revoked: false, reason: error.message };
46
+ }
47
+ }
48
+
49
+ // GitHub's device flow: the user approves a code on github.com while this polls crossbind cloud, which asks GitHub.
50
+ export async function login({
51
+ url, file = CREDENTIALS_FILE, fetcher = fetch, sleep = wait, now = Date.now, print = console.log,
52
+ }) {
53
+ const device = await call(fetcher, url, '/v1/login/device', { method: 'POST' });
54
+ if (!device.ok) throw refused(device);
55
+ const { deviceCode, userCode, verificationUri } = device.body;
56
+ if (URL.parse(verificationUri)?.origin !== GITHUB_ORIGIN || typeof userCode !== 'string') {
57
+ throw new Error('crossbind: crossbind cloud sent a sign-in page that is not on github.com.');
58
+ }
59
+ print(`crossbind: open ${printable(verificationUri)} and enter the code ${printable(userCode)} to sign in with GitHub.`);
60
+ let pace = paceOf(device.body.interval, MIN_INTERVAL_SECONDS);
61
+ const deadline = now() + (device.body.expiresIn ?? DEFAULT_EXPIRES_SECONDS) * 1000;
62
+ while (now() < deadline) {
63
+ await sleep(pace * 1000);
64
+ const answer = await call(fetcher, url, '/v1/login/token', { method: 'POST', body: { deviceCode } });
65
+ if (answer.status === 202) {
66
+ pace = paceOf(answer.body.interval, pace);
67
+ } else {
68
+ if (!answer.ok) throw refused(answer);
69
+ const { token } = answer.body;
70
+ if (!TOKEN_PATTERN.test(token ?? '')) throw new Error('crossbind: crossbind cloud sent no usable token.');
71
+ const name = printable(answer.body.login).slice(0, MAX_NAME_LENGTH);
72
+ const previous = cloudCredentials(url, file);
73
+ writeCredentials({ ...readCredentials(file), [url]: { token, login: name } }, file);
74
+ if (previous && previous.token !== token) {
75
+ const outcome = await revoke(fetcher, url, previous.token, false);
76
+ if (!outcome.revoked) print(`crossbind: the token this sign-in replaces still works (${outcome.reason.replace(/^crossbind: /, '')}); crossbind logout --all revokes it.`);
77
+ }
78
+ return { login: name };
79
+ }
80
+ }
81
+ throw new Error('crossbind: the code expired before it was entered; run crossbind login again.');
82
+ }
83
+
84
+ // Forgets the token here whatever the cloud answers; `revoked` says whether the cloud dropped it too.
85
+ export async function logout({
86
+ url, all = false, file = CREDENTIALS_FILE, fetcher = fetch,
87
+ }) {
88
+ const { [url]: entry, ...rest } = readCredentials(file);
89
+ if (!entry) return null;
90
+ const outcome = await revoke(fetcher, url, entry.token, all);
91
+ writeCredentials(rest, file);
92
+ return { login: entry.login, ...outcome };
93
+ }
94
+
95
+ // The account, with the sign-in of every machine; the sign-in here is forgotten only once the cloud confirms.
96
+ export async function deleteAccount({ url, file = CREDENTIALS_FILE, fetcher = fetch }) {
97
+ const { [url]: entry, ...rest } = readCredentials(file);
98
+ if (!entry) throw new Error(`crossbind: not signed in to ${url}; run crossbind login first.`);
99
+ const answer = await call(fetcher, url, '/v1/account', { method: 'DELETE', token: entry.token });
100
+ if (answer.status === 401) throw new Error('crossbind: this sign-in is no longer valid; run crossbind login, then delete the account.');
101
+ if (!answer.ok) throw refused(answer);
102
+ writeCredentials(rest, file);
103
+ return { login: entry.login };
104
+ }
105
+
106
+ export async function fetchUsage({ url, file = CREDENTIALS_FILE, fetcher = fetch }) {
107
+ const entry = cloudCredentials(url, file);
108
+ if (!entry) throw new Error(`crossbind: not signed in to ${url}; run crossbind login.`);
109
+ const answer = await call(fetcher, url, '/v1/usage', { token: entry.token });
110
+ if (answer.status === 401) throw new Error('crossbind: this sign-in is no longer valid; run crossbind login.');
111
+ if (!answer.ok) throw refused(answer);
112
+ return answer.body;
113
+ }
114
+
115
+ function formatSeconds(seconds) {
116
+ const whole = Math.max(0, Math.round(seconds));
117
+ const minutes = Math.floor(whole / 60);
118
+ const rest = whole % 60;
119
+ if (minutes === 0) return `${rest}s`;
120
+ return rest === 0 ? `${minutes}m` : `${minutes}m ${rest}s`;
121
+ }
122
+
123
+ export function usageLines({ login: name, builds, playground }) {
124
+ const images = Object.entries(builds.byImage).sort(([, a], [, b]) => b - a);
125
+ const column = Math.max(...images.map(([image]) => image.length)) + 3;
126
+ const left = builds.limitSeconds - builds.usedSeconds;
127
+ return [
128
+ `Signed in as ${printable(name)}.`,
129
+ `Cloud builds in ${printable(builds.month)}: ${formatSeconds(builds.usedSeconds)} of ${formatSeconds(builds.limitSeconds)} used, ${formatSeconds(left)} left.`,
130
+ ...images.map(([image, seconds]) => ` ${printable(image).padEnd(column)}${formatSeconds(seconds)}`),
131
+ `Playground: ${playground.remaining} of ${playground.limit} compiles left today.`,
132
+ ];
133
+ }
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs';
2
+ import upath from 'upath';
3
+ import state from '../state/index.js';
4
+ import createBridgeFile from './createInterface.js';
5
+ import getDependFilePath from '../integration/getDependFilePath.js';
6
+ import resolveNativeImport from '../integration/resolveNativeImport.js';
7
+ import { getRustSymbols } from '../integration/getCrossbindScript.js';
8
+ import { findNativeImportsIn, nativeSpecifierTest } from '../utils/nativeImports.js';
9
+
10
+ const SCHEME = /^(cargo|conan):/;
11
+ const isBare = (specifier) => !specifier.startsWith('.') && !upath.isAbsolute(specifier);
12
+
13
+ function realPath(file) {
14
+ try {
15
+ return upath.normalize(fs.realpathSync(file));
16
+ } catch (e) {
17
+ return upath.resolve(file);
18
+ }
19
+ }
20
+
21
+ // The first target whose packages ship the file. A cargo: or conan: import that none resolves fails the build with
22
+ // what to declare or install, as it does in a bundler.
23
+ function resolveImport(specifier, importer, targets) {
24
+ let failure = null;
25
+ for (const target of targets) {
26
+ try {
27
+ const file = SCHEME.test(specifier) ? getDependFilePath(specifier, target) : resolveNativeImport(specifier, importer, target);
28
+ if (file && fs.existsSync(file)) return { target, file };
29
+ } catch (error) {
30
+ failure ??= error;
31
+ }
32
+ }
33
+ if (failure) throw failure;
34
+ return null;
35
+ }
36
+
37
+ // The native files the app's own sources import, by real path, each under the first target whose packages ship it
38
+ // and with the bare specifiers that name it, which Node cannot resolve on its own.
39
+ export function findAppNativeImports(targets) {
40
+ const projectDir = state.config.paths.project;
41
+ const imports = new Map();
42
+ if (!projectDir || !fs.existsSync(projectDir)) return imports;
43
+ for (const { importer, specifier } of findNativeImportsIn(projectDir, nativeSpecifierTest(state.config.ext))) {
44
+ const resolved = resolveImport(specifier, importer, targets);
45
+ if (!resolved) {
46
+ console.warn(`crossbind: ${upath.relative(projectDir, importer)} imports ${specifier}, which no dependency ships for ${targets.map((t) => t.path).join(', ')}.`);
47
+ continue;
48
+ }
49
+ const file = realPath(resolved.file);
50
+ const found = imports.get(file) ?? { file, target: resolved.target, specifiers: [] };
51
+ const specifiers = isBare(specifier) && !found.specifiers.includes(specifier) ? [...found.specifiers, specifier] : found.specifiers;
52
+ imports.set(file, { ...found, specifiers });
53
+ }
54
+ return imports;
55
+ }
56
+
57
+ // The imported files no bridge of the build covers yet, bound the way a bundler plugin binds what it transforms. A Rust
58
+ // import has no bridge file: its crate links through buildAppRustCrates or its package, so it brings its names.
59
+ export function createImportedBridges(bound, imports) {
60
+ const boundFiles = new Set(bound.map(({ file }) => realPath(file)));
61
+ return [...imports.values()].filter(({ file }) => !boundFiles.has(file)).map(({ file, target }) => (file.endsWith('.rs')
62
+ ? { file, names: getRustSymbols(file) }
63
+ : { file, bridge: createBridgeFile(file, target) }));
64
+ }