crossbind 2.0.0-beta.72 → 2.0.0-beta.73
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/package.json +2 -2
- package/src/actions/addExtension.js +2 -3
- package/src/actions/buildApp.js +28 -4
- package/src/actions/extensionCommands.js +2 -1
- package/src/actions/getDependLibs.js +5 -4
- package/src/app/index.js +8 -7
- package/src/bin.js +25 -13
- package/src/index.js +3 -2
- package/src/state/index.js +8 -6
- package/src/utils/appApiHeader.js +5 -5
- package/src/utils/appBuild.js +8 -0
- package/src/utils/appProject.js +24 -0
- package/src/utils/resolveAppHost.js +10 -0
- package/src/utils/xcframeworkSlice.js +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossbind",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.73",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://crossbind.dev",
|
|
6
6
|
"repository": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"upath": "^3.0.8"
|
|
52
52
|
},
|
|
53
53
|
"optionalDependencies": {
|
|
54
|
-
"@crossbind/bridge-tools": "2.0.0-beta.
|
|
54
|
+
"@crossbind/bridge-tools": "2.0.0-beta.73"
|
|
55
55
|
},
|
|
56
56
|
"keywords": [
|
|
57
57
|
"webassembly",
|
|
@@ -5,6 +5,7 @@ import { configFileOf, writeExtension } from '../utils/configWriter.js';
|
|
|
5
5
|
import { isUnderBase } from '../utils/replaceBasePathForDocker.js';
|
|
6
6
|
import { ownPackages } from '../utils/appExtensions.js';
|
|
7
7
|
import { packagesOfCrossbind } from '../utils/packageSources.js';
|
|
8
|
+
import { assertAppProject } from '../utils/appProject.js';
|
|
8
9
|
import resolveAppHostRoot from '../utils/resolveAppHost.js';
|
|
9
10
|
|
|
10
11
|
// The lockfiles by which a project names the package manager it is installed with.
|
|
@@ -144,9 +145,7 @@ function installedName(spec, before, after, manifest) {
|
|
|
144
145
|
export default function addExtension(spec, {
|
|
145
146
|
config, env = process.env, platform = process.platform, spawn = spawnSync,
|
|
146
147
|
}) {
|
|
147
|
-
|
|
148
|
-
throw new Error("crossbind: an extension builds into an app, which this project is not: set target: { runtimeEnv: 'app' } in crossbind.config.js, as the desktop-app template does.");
|
|
149
|
-
}
|
|
148
|
+
assertAppProject(config, 'an extension builds into an app');
|
|
150
149
|
assertSpec(spec, platform);
|
|
151
150
|
assertNotOwn(spec);
|
|
152
151
|
const { project, base } = config.paths;
|
package/src/actions/buildApp.js
CHANGED
|
@@ -393,6 +393,33 @@ export function clearAppOutputs() {
|
|
|
393
393
|
else fs.rmSync(appOutputsFile(), { force: true });
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
// The app target an entry of the folder the bundler builds into was written for, by its name: the target's app, or what
|
|
397
|
+
// packages it.
|
|
398
|
+
function outputTarget(entry) {
|
|
399
|
+
const { name } = state.config.general;
|
|
400
|
+
return state.targets.filter((t) => t.runtimeEnv === 'app').find((t) => [appOutputName(t), `${name}-${t.platform}-${t.arch}`]
|
|
401
|
+
.some((app) => entry === app || entry.startsWith(`${app}.`) || entry.startsWith(`${app}-`)));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// The paths a recorded output names: each desktop app with its installers' files, or a mobile app's project with its APK,
|
|
405
|
+
// bundle and debug symbols.
|
|
406
|
+
const outputPaths = (output) => (Array.isArray(output)
|
|
407
|
+
? output.flatMap(({ app, installers = [] }) => [app, ...installers.flatMap(({ files }) => files)])
|
|
408
|
+
: Object.values(output)).filter(Boolean);
|
|
409
|
+
|
|
410
|
+
// Empties the folder the bundler builds an app into, as the bundler would, but for git's own folder and what builds for the
|
|
411
|
+
// app's other platforms wrote there: their apps, named after their targets, and the files their installers recorded.
|
|
412
|
+
export function emptyAppOutDir(dir, platform = appPlatform()) {
|
|
413
|
+
if (!fs.existsSync(dir)) return;
|
|
414
|
+
const others = new Set(Object.entries(readAppOutputs()).filter(([name]) => name !== platform)
|
|
415
|
+
.flatMap(([, output]) => outputPaths(output)).map((file) => path.resolve(file)));
|
|
416
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
417
|
+
const target = outputTarget(entry);
|
|
418
|
+
const isOthers = target ? (DESKTOP_PLATFORMS[target.platform] ?? target.platform) !== platform : others.has(path.resolve(dir, entry));
|
|
419
|
+
if (entry !== '.git' && !isOthers) fs.rmSync(path.join(dir, entry), { recursive: true, force: true });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
396
423
|
// What a publisher's upload gets of the app the build wrote for a platform: a desktop platform's apps, one for each arch.
|
|
397
424
|
export function appOutputOf(platform) {
|
|
398
425
|
const output = readAppOutputs()[platform];
|
|
@@ -475,11 +502,8 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
475
502
|
// The app may land inside the page's own directory, which it then leaves out, with every app built there and what
|
|
476
503
|
// packages it: what is named after a target of the app, and every installer's files as a build recorded them, which
|
|
477
504
|
// a folder the bundler left as it was holds still.
|
|
478
|
-
const apps = state.targets.filter((t) => t.runtimeEnv === 'app')
|
|
479
|
-
.flatMap((t) => [appOutputName(t), `${state.config.general.name}-${t.platform}-${t.arch}`]).filter((name) => !name.endsWith('.app'));
|
|
480
505
|
const shipped = shippedFiles();
|
|
481
|
-
const isApp = (entry) => entry === path.basename(to) || entry.endsWith('.app')
|
|
482
|
-
|| apps.some((app) => entry === app || entry.startsWith(`${app}.`) || entry.startsWith(`${app}-`))
|
|
506
|
+
const isApp = (entry) => entry === path.basename(to) || entry.endsWith('.app') || Boolean(outputTarget(entry))
|
|
483
507
|
|| shipped.has(path.resolve(page, entry));
|
|
484
508
|
fs.readdirSync(page).filter((entry) => !isApp(entry)).forEach((entry) => {
|
|
485
509
|
fs.cpSync(`${page}/${entry}`, `${to}/${appLayout(target).resources}/page/${entry}`, { recursive: true });
|
|
@@ -3,6 +3,7 @@ import extensionApi from './extensionApi.js';
|
|
|
3
3
|
import state from '../state/index.js';
|
|
4
4
|
import { hasTerminal } from '../utils/ask.js';
|
|
5
5
|
import { extensionStep, ownExtensions, withoutNamed } from '../utils/appExtensions.js';
|
|
6
|
+
import { isAppProject } from '../utils/appProject.js';
|
|
6
7
|
import resolveAppHostRoot from '../utils/resolveAppHost.js';
|
|
7
8
|
|
|
8
9
|
const kebab = (name) => name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
@@ -23,7 +24,7 @@ function ownOf(listed, app) {
|
|
|
23
24
|
// Adds to crossbind app the commands of an app's extensions, crossbind's own and those crossbind.config.js lists: a name
|
|
24
25
|
// another has stops the run, naming both.
|
|
25
26
|
export default function addExtensionCommands(parent, config = state.config) {
|
|
26
|
-
if (config?.target
|
|
27
|
+
if (!config?.target || !isAppProject(config)) return;
|
|
27
28
|
const listed = config.extensions ?? [];
|
|
28
29
|
const owners = new Map(parent.commands.map((command) => [command.name(), 'crossbind']));
|
|
29
30
|
for (const { name: extension, commands = {} } of [...ownOf(listed, config.app), ...listed]) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import findFiles from '../utils/findFiles.js';
|
|
2
|
+
import xcframeworkSliceOf from '../utils/xcframeworkSlice.js';
|
|
2
3
|
import state from '../state/index.js';
|
|
3
4
|
import { getFilteredTargetSpec } from './target.js';
|
|
4
5
|
|
|
@@ -21,10 +22,10 @@ export default function getDependLibs(target) {
|
|
|
21
22
|
if (libPaths.length === 0) {
|
|
22
23
|
libPaths = libsIn(target.releasePath);
|
|
23
24
|
}
|
|
24
|
-
// A published port can leave its iOS archive to its xcframework
|
|
25
|
-
|
|
26
|
-
if (libPaths.length === 0 &&
|
|
27
|
-
libPaths = findFiles(
|
|
25
|
+
// A published port can leave its iOS archive to its xcframework: the one of this package, as a project
|
|
26
|
+
// can list the port's packages of other platforms, whose libraries share the name.
|
|
27
|
+
if (libPaths.length === 0 && target.platform === 'ios') {
|
|
28
|
+
libPaths = findFiles(`${xcframeworkSliceOf(d.paths.project, fileName, target)}/lib${fileName}.a`, { cwd: d.paths.project });
|
|
28
29
|
}
|
|
29
30
|
dependLibs.push(...libPaths);
|
|
30
31
|
});
|
package/src/app/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
// What an app's host does for its page: the app's folders, quitting, its windows, the events of the app's native code and
|
|
2
|
-
// the functions of its extensions, by their names. Each call goes to the host of an app
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// the functions of its extensions, by their names. Each call goes to the host of an app, which crossbind app builds; in a
|
|
3
|
+
// browser it rejects, or events.on throws. A page imports these from crossbind/app, which an app's bundler plugin takes
|
|
4
|
+
// from the app host, with what crossbind's own extensions give the page, as crossbind/app/core alone holds these.
|
|
5
5
|
|
|
6
|
-
// The app's crossbind.js, which initNative() loads, hands the calls to the host.
|
|
6
|
+
// The app's crossbind.js, which initNative() loads, hands the calls to the host. A browser has crossbind's glue as well,
|
|
7
|
+
// but no token of a host, and none of its functions.
|
|
7
8
|
function hostOf(member, name) {
|
|
8
9
|
const host = globalThis.Crossbind?.[member];
|
|
9
10
|
if (host) return host;
|
|
11
|
+
if (!globalThis.__crossbind_token && !globalThis.Crossbind?.app) throw new Error(`crossbind: ${name} works in an app alone, which crossbind app builds`);
|
|
10
12
|
if (globalThis.Crossbind) throw new Error(`crossbind: this app's host is older than crossbind/app; update @crossbind/core-app-host to call ${name}`);
|
|
11
|
-
|
|
12
|
-
throw new Error(`crossbind: ${name} works in an app alone, whose crossbind.config.js sets target.runtimeEnv: 'app'`);
|
|
13
|
+
throw new Error(`crossbind: ${name} works once the page has called initNative()`);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const call = (name, options) => hostOf('app', name)(name, options);
|
|
@@ -106,7 +107,7 @@ function deviceOf(name) {
|
|
|
106
107
|
const device = globalThis.__crossbind_device;
|
|
107
108
|
if (device) return device[name];
|
|
108
109
|
if (globalThis.__crossbind_token) throw new Error(`crossbind: this app's host is older than crossbind/app; update @crossbind/core-app-host to read device.${name}`);
|
|
109
|
-
throw new Error(`crossbind: device.${name} works in an app alone,
|
|
110
|
+
throw new Error(`crossbind: device.${name} works in an app alone, which crossbind app builds`);
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
// Where the app runs, the same through its run, which the page reads at once, as it starts: the platform and the arch
|
package/src/bin.js
CHANGED
|
@@ -15,6 +15,7 @@ import buildWasiCommand from './actions/buildWasiCommand.js';
|
|
|
15
15
|
import buildNode, { addonsInPackages, bundleNodeLoader, publishNodeData } from './actions/buildNode.js';
|
|
16
16
|
import buildNativeCommand from './actions/buildNativeCommand.js';
|
|
17
17
|
import buildApp, { appOutputPath, assertAppRunner, publishApp } from './actions/buildApp.js';
|
|
18
|
+
import { assertAppProject } from './utils/appProject.js';
|
|
18
19
|
import {
|
|
19
20
|
makeUploadKey, releaseSigningEnv, showUploadKeyPassword, useUploadKey,
|
|
20
21
|
} from './actions/uploadKey.js';
|
|
@@ -113,19 +114,34 @@ function declaredSbomPath(projectDir) {
|
|
|
113
114
|
// An app's page is the project's Vite build, which @crossbind/plugin-vite makes into the app of the platform named.
|
|
114
115
|
const APP_PLATFORMS = ['android', 'ios', 'linux', 'macos', 'windows'];
|
|
115
116
|
|
|
117
|
+
// Vite's CLI beside the entry of the package vite resolves to, where Vite keeps it and vite-plus-core too, whose vp runs
|
|
118
|
+
// it from there. An entry outside the package is another one's, which a lookup past it found.
|
|
119
|
+
function viteCliBesideEntry(require, packageDir) {
|
|
120
|
+
try {
|
|
121
|
+
const entry = require.resolve('vite');
|
|
122
|
+
const cli = path.join(path.dirname(entry), 'cli.js');
|
|
123
|
+
return entry.startsWith(`${packageDir}${path.sep}`) && fs.existsSync(cli) ? cli : null;
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
116
129
|
function projectVite() {
|
|
130
|
+
const require = createRequire(`${state.config.paths.project}/package.json`);
|
|
117
131
|
let manifestPath;
|
|
118
132
|
try {
|
|
119
|
-
manifestPath =
|
|
133
|
+
manifestPath = require.resolve('vite/package.json');
|
|
120
134
|
} catch {
|
|
121
135
|
throw new Error("crossbind: crossbind app builds the app's page with the project's Vite, which the project does not have. Add vite and @crossbind/plugin-vite to it, as the desktop-app template has them.");
|
|
122
136
|
}
|
|
123
|
-
// A workspace can map vite to a package of Vite's API alone.
|
|
137
|
+
// A workspace can map vite to a package of Vite's API alone, as vite-plus maps it to vite-plus-core.
|
|
124
138
|
const { name, version, bin } = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
125
|
-
if (
|
|
126
|
-
|
|
139
|
+
if (bin?.vite) return path.join(path.dirname(manifestPath), bin.vite);
|
|
140
|
+
const cli = viteCliBesideEntry(require, path.dirname(manifestPath));
|
|
141
|
+
if (!cli) {
|
|
142
|
+
throw new Error(`crossbind: crossbind app runs the project's Vite, and its vite resolves to ${name} ${version}, which has neither a vite command nor Vite's cli.js beside its entry. Have vite resolve to Vite itself for the project, with an override where its workspace maps vite to another package.`);
|
|
127
143
|
}
|
|
128
|
-
return
|
|
144
|
+
return cli;
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
// The project's Vite, as `crossbind app dev`, `build` and `publish` run it, and its exit code once it ends. A signal sent
|
|
@@ -143,11 +159,7 @@ function runVite(args, env) {
|
|
|
143
159
|
});
|
|
144
160
|
}
|
|
145
161
|
|
|
146
|
-
|
|
147
|
-
if (state.config.target.runtimeEnv !== 'app') {
|
|
148
|
-
throw new Error("crossbind: crossbind app builds an app, which this project is not: set target: { runtimeEnv: 'app' } in crossbind.config.js, as the desktop-app template does.");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
162
|
+
const assertAppCommand = () => assertAppProject(state.config, 'crossbind app builds an app');
|
|
151
163
|
|
|
152
164
|
const commandApp = program.command('app').description('build the project as an app, or open it on the dev server');
|
|
153
165
|
[
|
|
@@ -164,12 +176,12 @@ const commandApp = program.command('app').description('build the project as an a
|
|
|
164
176
|
.allowUnknownOption()
|
|
165
177
|
.allowExcessArguments()
|
|
166
178
|
.action(async ({ platform, device }, command) => {
|
|
167
|
-
|
|
179
|
+
assertAppCommand();
|
|
168
180
|
// An Android build checks its upload key before the native build, not after it.
|
|
169
181
|
const isAndroidBuild = name === 'build' && (platform ?? process.env.CROSSBIND_APP_PLATFORM) === 'android';
|
|
170
182
|
const { env: signing } = isAndroidBuild ? await releaseSigningEnv() : { env: {} };
|
|
171
183
|
const onDevice = device === undefined ? {} : { CROSSBIND_APP_DEVICE: iosDeviceOption(device, platform ?? process.env.CROSSBIND_APP_PLATFORM) };
|
|
172
|
-
const env = { ...process.env, ...signing, ...(platform ? { CROSSBIND_APP_PLATFORM: platform } : {}), ...onDevice };
|
|
184
|
+
const env = { ...process.env, ...signing, CROSSBIND_APP_BUILD: '1', ...(platform ? { CROSSBIND_APP_PLATFORM: platform } : {}), ...onDevice };
|
|
173
185
|
process.exit(await runVite([...(name === 'build' ? ['build'] : []), ...command.args], env));
|
|
174
186
|
});
|
|
175
187
|
});
|
|
@@ -195,7 +207,7 @@ commandApp.command('publish')
|
|
|
195
207
|
.allowUnknownOption()
|
|
196
208
|
.allowExcessArguments()
|
|
197
209
|
.action(async ({ platform }, command) => {
|
|
198
|
-
|
|
210
|
+
assertAppCommand();
|
|
199
211
|
await publish(command.args, { platform, envPlatform: process.env.CROSSBIND_APP_PLATFORM, runVite });
|
|
200
212
|
});
|
|
201
213
|
|
package/src/index.js
CHANGED
|
@@ -4,10 +4,11 @@ export { default as createLib } from './actions/createLib.js';
|
|
|
4
4
|
export { default as createBridgeFile } from './actions/createInterface.js';
|
|
5
5
|
export { default as buildWasm } from './actions/buildWasm.js';
|
|
6
6
|
export {
|
|
7
|
-
default as buildApp, buildProjectApp, publishApp, appLayout, appOutputName, appPlatform, appTargetParams,
|
|
7
|
+
default as buildApp, buildProjectApp, publishApp, appLayout, appOutputName, appPlatform, appTargetParams, emptyAppOutDir,
|
|
8
8
|
} from './actions/buildApp.js';
|
|
9
9
|
export { checkInstaller, shipApp } from './actions/appInstaller.js';
|
|
10
|
-
export { resolveAppPageApi } from './utils/resolveAppHost.js';
|
|
10
|
+
export { findAppPageApi, resolveAppPageApi } from './utils/resolveAppHost.js';
|
|
11
|
+
export { default as isAppBuild } from './utils/appBuild.js';
|
|
11
12
|
export { default as openInSimulator, buildForSimulator } from './actions/iosSimulator.js';
|
|
12
13
|
export {
|
|
13
14
|
default as openOnIosDevice, buildForIosDevice, connectedIosDevice, iosDeviceAddress, iosDevServer,
|
package/src/state/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { exportWasiSdkPath } from '../utils/wasiToolchain.js';
|
|
|
10
10
|
import loadConfig, { assertBinarySelection } from './loadConfig.js';
|
|
11
11
|
import { configChangesOf } from '../utils/configChanges.js';
|
|
12
12
|
import refreshPackageSources from './refreshPackageSources.js';
|
|
13
|
+
import xcframeworkSliceOf from '../utils/xcframeworkSlice.js';
|
|
13
14
|
|
|
14
15
|
const cacheDir = `${process.cwd()}/.crossbind`;
|
|
15
16
|
|
|
@@ -97,6 +98,7 @@ export function setAllDependecyPaths() {
|
|
|
97
98
|
state.config.allDependencyPaths = {};
|
|
98
99
|
state.targets.forEach((target) => {
|
|
99
100
|
state.config.allDependencyPaths[target.path] = { cmake: {} };
|
|
101
|
+
const owners = new Map();
|
|
100
102
|
state.config.allDependencies.forEach((d) => {
|
|
101
103
|
state.config.allDependencyPaths[target.path].cmake[d.general.name] = `${d.paths.output}/prebuilt`;
|
|
102
104
|
// A package source's recipe can name a library on another platform only (libpng is png16 on Windows).
|
|
@@ -105,6 +107,11 @@ export function setAllDependecyPaths() {
|
|
|
105
107
|
.some((targetPath) => fs.existsSync(`${d.paths.output}/prebuilt/${targetPath}/lib/lib${name}.a`)))
|
|
106
108
|
: d.export.libName;
|
|
107
109
|
libNames.forEach((name) => {
|
|
110
|
+
// A project can list a port's packages for several platforms, which share the library's name: the one
|
|
111
|
+
// that serves the target keeps the entry.
|
|
112
|
+
const owner = owners.get(name);
|
|
113
|
+
if (owner && owner !== d && d.functions?.isEnabled?.(target) === false) return;
|
|
114
|
+
owners.set(name, d);
|
|
108
115
|
state.config.allDependencyPaths[target.path][name] = {
|
|
109
116
|
root: `${d.paths.output}/prebuilt/${target.path}`,
|
|
110
117
|
};
|
|
@@ -118,12 +125,7 @@ export function setAllDependecyPaths() {
|
|
|
118
125
|
const isDynamicLib = target.platform === 'android' && platformConfig.libType !== 'static';
|
|
119
126
|
const dep = state.config.allDependencyPaths[target.path][name];
|
|
120
127
|
if (target.platform === 'ios') {
|
|
121
|
-
|
|
122
|
-
if (target.arch === 'iphoneos') {
|
|
123
|
-
xcRoot = `${d.paths.project}/${name}.xcframework/ios-arm64`;
|
|
124
|
-
} else if (target.arch === 'iphonesimulator') {
|
|
125
|
-
xcRoot = `${d.paths.project}/${name}.xcframework/ios-arm64-simulator`;
|
|
126
|
-
}
|
|
128
|
+
const xcRoot = xcframeworkSliceOf(d.paths.project, name, target);
|
|
127
129
|
dep.header = `${xcRoot}/Headers`;
|
|
128
130
|
dep.libPath = xcRoot;
|
|
129
131
|
dep.lib = `${dep.libPath}/lib${name}.a`;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// <crossbind/app.h>, which an app's own code includes for the host's functions (docs/api/app.md).
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
-
import
|
|
4
|
+
import isAppBuild from './appBuild.js';
|
|
5
5
|
import resolveAppHostRoot from './resolveAppHost.js';
|
|
6
6
|
import { runtimeEnvsOf } from './targets.js';
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
// on, and in every flavour of that platform's archives, which share one build. A library and an
|
|
10
|
-
// neither.
|
|
8
|
+
// In a run that builds an app its project's native code compiles against the header, with CROSSBIND_APP defined, on every
|
|
9
|
+
// platform an app runs on, and in every flavour of that platform's archives, which share one build. A library and an
|
|
10
|
+
// app's web build get neither.
|
|
11
11
|
export function compilesAsApp(target) {
|
|
12
|
-
return
|
|
12
|
+
return isAppBuild() && runtimeEnvsOf(target.platform).includes('app');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// What an app's own code compiles with: CROSSBIND_APP, and on iOS its debug information, from which the archive's dSYM
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import state from '../state/index.js';
|
|
2
|
+
|
|
3
|
+
// Whether this run builds an app: crossbind app says so to the bundler it starts, CROSSBIND_APP_PLATFORM names an app's
|
|
4
|
+
// platform, or the project is an app alone by target.runtimeEnv. Without any of these a bundler plugin builds what a
|
|
5
|
+
// browser loads, so one project makes both by the command that builds it.
|
|
6
|
+
export default function isAppBuild(env = process.env) {
|
|
7
|
+
return env.CROSSBIND_APP_BUILD === '1' || Boolean(env.CROSSBIND_APP_PLATFORM) || state.config.target?.runtimeEnv === 'app';
|
|
8
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const APP_HOST = '@crossbind/core-app-host';
|
|
5
|
+
|
|
6
|
+
function listsAppHost(project) {
|
|
7
|
+
const manifest = path.join(project, 'package.json');
|
|
8
|
+
if (!fs.existsSync(manifest)) return false;
|
|
9
|
+
const { dependencies, devDependencies } = JSON.parse(fs.readFileSync(manifest, 'utf8'));
|
|
10
|
+
return Boolean(dependencies?.[APP_HOST] ?? devDependencies?.[APP_HOST]);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// A project an app builds from: one that says it is an app, or that has the app host and leaves its environment to the
|
|
14
|
+
// command that builds it.
|
|
15
|
+
export const isAppProject = ({ target, paths }) => target?.runtimeEnv === 'app' || (!target?.runtimeEnv && listsAppHost(paths.project));
|
|
16
|
+
|
|
17
|
+
// Stops what only an app does, in a project that is none, saying what would make it one.
|
|
18
|
+
export function assertAppProject(config, doing) {
|
|
19
|
+
if (isAppProject(config)) return;
|
|
20
|
+
const { runtimeEnv } = config.target;
|
|
21
|
+
throw new Error(runtimeEnv
|
|
22
|
+
? `crossbind: ${doing}, and this project's target.runtimeEnv says ${runtimeEnv}: leave it out, or set it to 'app'.`
|
|
23
|
+
: `crossbind: ${doing}, which this project is not: add ${APP_HOST} to its devDependencies, as the desktop-app template does.`);
|
|
24
|
+
}
|
|
@@ -26,3 +26,13 @@ export function resolveAppPageApi() {
|
|
|
26
26
|
if (!fs.existsSync(file)) throw new Error(`crossbind: the app host at ${resolveAppHostRoot()} has no crossbind/app for an app's page: update ${PKG} to match crossbind.`);
|
|
27
27
|
return fs.realpathSync(file);
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
// The same for a page a bundler builds for the browser, where a project has the app host: code an app shares with its
|
|
31
|
+
// web build still bundles, and what it calls rejects as outside an app. A project without the host has none.
|
|
32
|
+
export function findAppPageApi() {
|
|
33
|
+
try {
|
|
34
|
+
return resolveAppPageApi();
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The slice of a package's <name>.xcframework that an iOS target links: crossbind's packages carry arm64 alone.
|
|
2
|
+
const SLICES = { iphoneos: 'ios-arm64', iphonesimulator: 'ios-arm64-simulator' };
|
|
3
|
+
|
|
4
|
+
export default function xcframeworkSliceOf(projectPath, name, target) {
|
|
5
|
+
return `${projectPath}/${name}.xcframework/${SLICES[target.arch]}`;
|
|
6
|
+
}
|