crossbind 2.0.0-beta.68 → 2.0.0-beta.72
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/README.md +1 -1
- package/package.json +70 -59
- package/src/actions/addExtension.js +196 -0
- package/src/actions/androidGradle.js +26 -17
- package/src/actions/appInstaller.js +69 -153
- package/src/actions/appSwift.js +66 -0
- package/src/actions/buildApp.js +177 -74
- package/src/actions/buildDependencies.js +3 -3
- package/src/actions/buildNode.js +6 -1
- package/src/actions/buildWasm.js +0 -9
- package/src/actions/createImportedBridges.js +10 -8
- package/src/actions/createInterface.js +40 -29
- package/src/actions/createLib.js +3 -7
- package/src/actions/extensionApi.js +298 -0
- package/src/actions/extensionCommands.js +41 -0
- package/src/actions/extensionsInApp.js +19 -0
- package/src/actions/getAllBridges.js +20 -5
- package/src/actions/getDependLibs.js +5 -0
- package/src/actions/getLinkInputs.js +2 -1
- package/src/actions/gradleProject.js +207 -16
- package/src/actions/iosDevice.js +2 -2
- package/src/actions/iosSimulator.js +3 -3
- package/src/actions/licenses.js +113 -58
- package/src/actions/linuxPackages.js +48 -0
- package/src/actions/mavenVerification.js +37 -0
- package/src/actions/{prepareConanDependencies.js → preparePackageSources.js} +13 -12
- package/src/actions/publish.js +220 -0
- package/src/actions/windowsSigning.js +1 -1
- package/src/actions/writeRuntimeEntry.js +2 -2
- package/src/actions/xcodeProject.js +60 -29
- package/src/actions/xcodeTargets.js +122 -0
- package/src/actions/xcodebuild.js +65 -0
- package/src/app/index.d.ts +144 -132
- package/src/app/index.js +147 -185
- package/src/assets/configure/install.sh +0 -0
- package/src/assets/node-import-hooks.mjs +7 -6
- package/src/assets/toolchain-digests.json +20 -20
- package/src/bin.js +37 -37
- package/src/index.js +4 -3
- package/src/integration/getCrossbindScript.js +3 -2
- package/src/integration/getDependFilePath.js +10 -10
- package/src/program.js +32 -3
- package/src/state/{attachConanDependencies.js → attachPackageSources.js} +20 -16
- package/src/state/index.js +8 -6
- package/src/state/loadConfig.js +26 -9
- package/src/state/refreshPackageSources.js +19 -0
- package/src/utils/appExtensions.js +777 -0
- package/src/utils/appFiles.js +58 -0
- package/src/utils/appLaunches.js +249 -0
- package/src/utils/appOptions.js +142 -112
- package/src/utils/appRustCrates.js +2 -2
- package/src/utils/appTargets.js +168 -0
- package/src/utils/bridgeTools.js +71 -0
- package/src/utils/configChanges.js +30 -0
- package/src/utils/configWriter.js +143 -26
- package/src/utils/cppDts.js +11 -11
- package/src/utils/extensionSteps.js +172 -0
- package/src/utils/nativeImports.js +9 -2
- package/src/utils/nodeDevBuild.js +2 -1
- package/src/utils/openUrl.js +2 -1
- package/src/utils/packageSourceApi.js +87 -0
- package/src/utils/packageSourceImport.js +69 -0
- package/src/utils/packageSourceInstall.js +162 -0
- package/src/utils/{conanStage.js → packageSourceStage.js} +64 -90
- package/src/utils/packageSources.js +108 -0
- package/src/utils/plist.js +5 -0
- package/src/utils/printableText.js +5 -0
- package/src/utils/resolveAppHost.js +7 -0
- package/src/utils/runPackageSource.js +251 -0
- package/src/utils/rustBridgeGen.js +3 -3
- package/src/utils/systemConfig.js +16 -3
- package/src/utils/targetToolchain.js +77 -0
- package/vitest.config.js +6 -1
- package/src/actions/appSetup.js +0 -116
- package/src/actions/appStoreConnect.js +0 -188
- package/src/actions/appUpdate.js +0 -68
- package/src/actions/extensions.js +0 -7
- package/src/actions/googlePlay.js +0 -213
- package/src/actions/googleSignIn.js +0 -78
- package/src/actions/playSetup.js +0 -19
- package/src/actions/publishAndroid.js +0 -102
- package/src/actions/publishIos.js +0 -146
- package/src/state/refreshConanDependencies.js +0 -17
- package/src/utils/conanDependencies.js +0 -66
- package/src/utils/conanImport.js +0 -58
- package/src/utils/conanInstall.js +0 -154
- package/src/utils/conanProfile.js +0 -203
- package/src/utils/runConan.js +0 -257
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { xmlPlist } from '../utils/plist.js';
|
|
4
|
+
import { PRIVACY_CATEGORIES } from '../utils/appOptions.js';
|
|
5
|
+
import { isHeader, targetFrameworks, targetModule, targetPlist, widgetsWithoutBackground } from '../utils/appTargets.js';
|
|
6
|
+
|
|
7
|
+
const APP_GROUPS = 'com.apple.security.application-groups';
|
|
8
|
+
// The kind of each file Xcode compiles or copies into a target, by its ending: a folder is copied as it is, and an asset
|
|
9
|
+
// catalog compiled.
|
|
10
|
+
const FILE_TYPES = {
|
|
11
|
+
'.swift': 'sourcecode.swift', '.m': 'sourcecode.c.objc', '.mm': 'sourcecode.cpp.objcpp', '.c': 'sourcecode.c.c', '.cpp': 'sourcecode.cpp.cpp',
|
|
12
|
+
'.cc': 'sourcecode.cpp.cpp', '.cxx': 'sourcecode.cpp.cpp', '.h': 'sourcecode.c.h', '.hpp': 'sourcecode.cpp.h', '.xcassets': 'folder.assetcatalog',
|
|
13
|
+
'.storyboard': 'file.storyboard', '.xib': 'file.xib', '.strings': 'text.plist.strings', '.xcstrings': 'text.json.xcstrings', '.png': 'image.png',
|
|
14
|
+
'.json': 'text.json',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// The privacy manifest of these reasons for Apple's required reason API, which App Store Connect asks of each bundle that
|
|
18
|
+
// calls one.
|
|
19
|
+
export const privacyManifest = (privacy) => xmlPlist({
|
|
20
|
+
NSPrivacyAccessedAPITypes: Object.entries(privacy).map(([name, reasons]) => ({
|
|
21
|
+
NSPrivacyAccessedAPIType: PRIVACY_CATEGORIES[name].type,
|
|
22
|
+
NSPrivacyAccessedAPITypeReasons: reasons,
|
|
23
|
+
})),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Xcode names its objects by 24 hex digits: each target's go by its place among them, apart from the app's own.
|
|
27
|
+
const idOf = (target, index) => `CE${target.toString(16).toUpperCase().padStart(4, '0')}${index.toString(16).toUpperCase().padStart(18, '0')}`;
|
|
28
|
+
|
|
29
|
+
// Writes a target's folder beside the app's project, targets/<name>/: its sources and resources in files/, where they are
|
|
30
|
+
// in their extension, its Info.plist, and its entitlements, the App Group among them where it asks for it, and its
|
|
31
|
+
// privacy manifest where it has them.
|
|
32
|
+
function writeTargetFiles(to, target, { label, appGroup }) {
|
|
33
|
+
const dir = `targets/${target.name}`;
|
|
34
|
+
fs.mkdirSync(`${to}/${dir}/files`, { recursive: true });
|
|
35
|
+
[...target.sources, ...(target.resources ?? [])].forEach((file) => fs.cpSync(path.join(target.root, file), `${to}/${dir}/files/${file}`, { recursive: true }));
|
|
36
|
+
fs.writeFileSync(`${to}/${dir}/Info.plist`, xmlPlist(targetPlist(target, label, appGroup)));
|
|
37
|
+
const own = target.entitlements ?? {};
|
|
38
|
+
const entitlements = target.appGroup ? { ...own, [APP_GROUPS]: [...new Set([...(own[APP_GROUPS] ?? []), appGroup])] } : own;
|
|
39
|
+
const hasEntitlements = Object.keys(entitlements).length > 0;
|
|
40
|
+
if (hasEntitlements) fs.writeFileSync(`${to}/${dir}/${target.name}.entitlements`, xmlPlist(entitlements));
|
|
41
|
+
const hasPrivacy = Object.keys(target.privacy ?? {}).length > 0;
|
|
42
|
+
if (hasPrivacy) fs.writeFileSync(`${to}/${dir}/PrivacyInfo.xcprivacy`, privacyManifest(target.privacy));
|
|
43
|
+
return { dir, hasEntitlements, hasPrivacy };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const linkedFrameworks = (frameworks) => (frameworks.length > 0 ? { OTHER_LDFLAGS: ['$(inherited)', ...frameworks.flatMap((name) => ['-framework', name])] } : {});
|
|
47
|
+
|
|
48
|
+
// A target's build settings: its bundle, of the app's id and its name, signed as the app is, with the app's version and
|
|
49
|
+
// build, and its own deployment target and frameworks.
|
|
50
|
+
const targetSettings = (target, { dir, hasEntitlements }, { identity, version, build }) => ({
|
|
51
|
+
PRODUCT_NAME: target.name,
|
|
52
|
+
PRODUCT_MODULE_NAME: targetModule(target.name),
|
|
53
|
+
PRODUCT_BUNDLE_IDENTIFIER: `${identity.id}.${target.name}`,
|
|
54
|
+
DEVELOPMENT_TEAM: identity.team,
|
|
55
|
+
CODE_SIGN_STYLE: 'Automatic',
|
|
56
|
+
...(hasEntitlements ? { CODE_SIGN_ENTITLEMENTS: `${dir}/${target.name}.entitlements` } : {}),
|
|
57
|
+
INFOPLIST_FILE: `${dir}/Info.plist`,
|
|
58
|
+
MARKETING_VERSION: version,
|
|
59
|
+
CURRENT_PROJECT_VERSION: build,
|
|
60
|
+
SKIP_INSTALL: 'YES',
|
|
61
|
+
APPLICATION_EXTENSION_API_ONLY: 'YES',
|
|
62
|
+
// Objective-C's @import, and the frameworks it links of itself.
|
|
63
|
+
CLANG_ENABLE_MODULES: 'YES',
|
|
64
|
+
LD_RUNPATH_SEARCH_PATHS: ['$(inherited)', '@executable_path/Frameworks', '@executable_path/../../Frameworks'],
|
|
65
|
+
...(target.sources.some((file) => file.endsWith('.swift')) ? { SWIFT_VERSION: '5.0' } : {}),
|
|
66
|
+
...(target.deploymentTarget ? { IPHONEOS_DEPLOYMENT_TARGET: target.deploymentTarget } : {}),
|
|
67
|
+
...linkedFrameworks(targetFrameworks(target)),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// The project's objects of one target, by ids of its place among them: its files, build phases, configurations and the
|
|
71
|
+
// target, with what the app's target takes of it, a dependency on it and the copy of its product into the app's PlugIns.
|
|
72
|
+
function targetObjects(target, place, written, options) {
|
|
73
|
+
const objects = {};
|
|
74
|
+
let count = 0;
|
|
75
|
+
const add = (object) => {
|
|
76
|
+
count += 1;
|
|
77
|
+
objects[idOf(place, count)] = object;
|
|
78
|
+
return idOf(place, count);
|
|
79
|
+
};
|
|
80
|
+
const reference = (file, type) => add({ isa: 'PBXFileReference', lastKnownFileType: type, path: file, sourceTree: '<group>' });
|
|
81
|
+
const typeOf = (file) => FILE_TYPES[path.extname(file)] ?? (fs.statSync(path.join(target.root, file)).isDirectory() ? 'folder' : 'file');
|
|
82
|
+
const phase = (isa, files) => add({ isa, buildActionMask: 2147483647, files: files.map((fileRef) => add({ isa: 'PBXBuildFile', fileRef })), runOnlyForDeploymentPostprocessing: 0 });
|
|
83
|
+
// Its headers are in its group, which its sources import from beside them, and compile with them.
|
|
84
|
+
const headers = target.sources.filter(isHeader).map((file) => reference(`files/${file}`, typeOf(file)));
|
|
85
|
+
const sources = target.sources.filter((file) => !isHeader(file)).map((file) => reference(`files/${file}`, typeOf(file)));
|
|
86
|
+
const resources = [...(target.resources ?? []).map((file) => reference(`files/${file}`, typeOf(file))),
|
|
87
|
+
...(written.hasPrivacy ? [reference('PrivacyInfo.xcprivacy', 'text.xml')] : [])];
|
|
88
|
+
const info = reference('Info.plist', 'text.plist.xml');
|
|
89
|
+
const signed = written.hasEntitlements ? [reference(`${target.name}.entitlements`, 'text.plist.entitlements')] : [];
|
|
90
|
+
const product = add({ isa: 'PBXFileReference', explicitFileType: 'wrapper.app-extension', includeInIndex: 0, path: `${target.name}.appex`, sourceTree: 'BUILT_PRODUCTS_DIR' });
|
|
91
|
+
const group = add({ isa: 'PBXGroup', children: [info, ...signed, ...headers, ...sources, ...resources], path: written.dir, sourceTree: '<group>' });
|
|
92
|
+
const phases = [phase('PBXSourcesBuildPhase', sources), phase('PBXFrameworksBuildPhase', []), phase('PBXResourcesBuildPhase', resources)];
|
|
93
|
+
const buildSettings = targetSettings(target, written, options);
|
|
94
|
+
const configurations = ['Debug', 'Release'].map((name) => add({ isa: 'XCBuildConfiguration', buildSettings, name }));
|
|
95
|
+
const list = add({ isa: 'XCConfigurationList', buildConfigurations: configurations, defaultConfigurationIsVisible: 0, defaultConfigurationName: 'Release' });
|
|
96
|
+
const native = add({
|
|
97
|
+
isa: 'PBXNativeTarget', buildConfigurationList: list, buildPhases: phases, buildRules: [], dependencies: [], name: target.name,
|
|
98
|
+
productName: target.name, productReference: product, productType: 'com.apple.product-type.app-extension',
|
|
99
|
+
});
|
|
100
|
+
const proxy = add({ isa: 'PBXContainerItemProxy', containerPortal: options.project, proxyType: 1, remoteGlobalIDString: native, remoteInfo: target.name });
|
|
101
|
+
const dependency = add({ isa: 'PBXTargetDependency', target: native, targetProxy: proxy });
|
|
102
|
+
const embedded = add({ isa: 'PBXBuildFile', fileRef: product, settings: { ATTRIBUTES: ['RemoveHeadersOnCopy'] } });
|
|
103
|
+
return { objects, group, product, native, dependency, embedded };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Writes the folder of each target of Apple's app extensions the app's extensions add, and gives the project's objects
|
|
107
|
+
// for them: each a target whose bundle is the app's id and its name, which the app builds first and embeds in its PlugIns.
|
|
108
|
+
export function writeTargets(to, targets, options) {
|
|
109
|
+
widgetsWithoutBackground(targets, (target, file) => fs.readFileSync(path.join(target.root, file), 'utf8')).forEach((name) => {
|
|
110
|
+
console.warn(`crossbind: the widget target ${name} names no containerBackground in its Swift, which iOS 17 asks of a widget's view: a debug build `
|
|
111
|
+
+ 'draws "Please adopt containerBackground API" in its place. Its view gives its background with .containerBackground(.background, for: .widget).');
|
|
112
|
+
});
|
|
113
|
+
const made = targets.map((target, place) => targetObjects(target, place, writeTargetFiles(to, target, options), options));
|
|
114
|
+
return {
|
|
115
|
+
objects: Object.assign({}, ...made.map(({ objects }) => objects)),
|
|
116
|
+
groups: made.map(({ group }) => group),
|
|
117
|
+
products: made.map(({ product }) => product),
|
|
118
|
+
dependencies: made.map(({ dependency }) => dependency),
|
|
119
|
+
targets: made.map(({ native }) => native),
|
|
120
|
+
embedded: made.map(({ embedded }) => embedded),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { EXEC_MAX_BUFFER } from './run.js';
|
|
4
|
+
|
|
5
|
+
const DEVELOPER_ACCOUNT = 'https://developer.apple.com/account';
|
|
6
|
+
// The lines of a failed xcodebuild's output that say why; its log has the rest.
|
|
7
|
+
const REASON_LINES = 15;
|
|
8
|
+
|
|
9
|
+
// What a failed build, archive or upload means where xcodebuild's own words leave it unclear: what the user mends in
|
|
10
|
+
// Xcode or on Apple's sites, and the page where. A cause may need another build, as one in what was built does.
|
|
11
|
+
const CAUSES = [
|
|
12
|
+
{
|
|
13
|
+
pattern: /login details for account .* were rejected|No Accounts?\b|No Account for Team/i,
|
|
14
|
+
hint: ({ team }) => `Xcode is signed in to no account of team ${team}, or its sign-in has lapsed: sign in again in Xcode, under Settings › Accounts.`,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
pattern: /PLA Update available|Program License Agreement/i,
|
|
18
|
+
hint: () => `Apple has a new Program License Agreement, which the team's Account Holder accepts at ${DEVELOPER_ACCOUNT}.`,
|
|
19
|
+
page: DEVELOPER_ACCOUNT,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
pattern: /Cloud signing permission error/i,
|
|
23
|
+
hint: () => "the API key may not sign in the cloud, which takes its Admin role: use an Admin key, make an Apple Distribution certificate once in Xcode, under Settings › Accounts › Manage Certificates, or unset CROSSBIND_APPLE_API_KEY to upload with Xcode's account.",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
pattern: /cannot be registered to your development team because it is not available/i,
|
|
27
|
+
hint: ({ id }) => `another team registered the bundle id ${id} with Apple first: set app.id in crossbind.config.js to an id of your own, as one under a domain you own.`,
|
|
28
|
+
// The id is in what was built, so the same step tried again fails the same way.
|
|
29
|
+
needsRebuild: true,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Runs xcodebuild with its output in `log`, which it returns. Its failure names the lines that say why, and what to do
|
|
34
|
+
// where those leave it unclear, from crossbind's causes first, then those the caller adds: the error says whether the
|
|
35
|
+
// user can mend it and try again, the page where, and the cause it found.
|
|
36
|
+
export function xcodebuild(what, args, { log, facts = {}, causes = [] }) {
|
|
37
|
+
const out = fs.openSync(log, 'w');
|
|
38
|
+
let result;
|
|
39
|
+
try {
|
|
40
|
+
result = spawnSync('xcodebuild', args, { stdio: ['ignore', out, out] });
|
|
41
|
+
} finally {
|
|
42
|
+
fs.closeSync(out);
|
|
43
|
+
}
|
|
44
|
+
if (result.error) throw new Error(`crossbind: cannot run xcodebuild, which comes with Xcode: ${result.error.message}`, { cause: result.error });
|
|
45
|
+
if (result.signal) throw new Error(`crossbind: ${what} stopped on ${result.signal}.`);
|
|
46
|
+
const output = fs.readFileSync(log, 'utf8');
|
|
47
|
+
if (result.status === 0) return output;
|
|
48
|
+
const reasons = [...new Set(output.split('\n').map((line) => line.trim()).filter((line) => /\berror\b|ITMS-/i.test(line)))].slice(-REASON_LINES);
|
|
49
|
+
const cause = [...CAUSES, ...causes].find(({ pattern }) => pattern.test(output));
|
|
50
|
+
const lines = [`crossbind: ${what} failed; xcodebuild's log is ${log}.`, ...reasons.map((line) => ` ${line}`), ...(cause ? [`crossbind: ${cause.hint(facts)}`] : [])];
|
|
51
|
+
throw Object.assign(new Error(lines.join('\n')), { isMendable: Boolean(cause) && !cause.needsRebuild, page: cause?.page ?? null, reason: cause ?? null });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The teams of the accounts signed in to Xcode that upload to App Store Connect, which a free personal team does not,
|
|
55
|
+
// from Xcode's settings; `withPersonal` adds those free teams, which sign for their own devices.
|
|
56
|
+
export function xcodeTeams({ withPersonal = false } = {}) {
|
|
57
|
+
const settings = spawnSync('defaults', ['export', 'com.apple.dt.Xcode', '-'], { maxBuffer: EXEC_MAX_BUFFER });
|
|
58
|
+
if (settings.status !== 0) return [];
|
|
59
|
+
const teams = spawnSync('plutil', ['-extract', 'IDEProvisioningTeamByIdentifier', 'json', '-o', '-', '-'], {
|
|
60
|
+
input: settings.stdout, encoding: 'utf8', maxBuffer: EXEC_MAX_BUFFER,
|
|
61
|
+
});
|
|
62
|
+
if (teams.status !== 0) return [];
|
|
63
|
+
const signing = Object.values(JSON.parse(teams.stdout)).flat().filter((team) => withPersonal || !team.isFreeProvisioningTeam);
|
|
64
|
+
return [...new Map(signing.map((team) => [team.teamID, { id: team.teamID, name: team.teamName }])).values()];
|
|
65
|
+
}
|
package/src/app/index.d.ts
CHANGED
|
@@ -1,171 +1,183 @@
|
|
|
1
|
-
// What an app's host does for its page (target.runtimeEnv: 'app')
|
|
1
|
+
// What an app's host does for its page (target.runtimeEnv: 'app'): crossbind/app/core alone, which crossbind/app has with
|
|
2
|
+
// what crossbind's own extensions give the page. A TypeScript app has their types with
|
|
3
|
+
// /// <reference types="@crossbind/core-app-host/app" />.
|
|
2
4
|
|
|
3
|
-
/**
|
|
4
|
-
export interface FileFilter {
|
|
5
|
-
name: string;
|
|
6
|
-
extensions: string[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface OpenOptions {
|
|
10
|
-
title?: string;
|
|
11
|
-
/** The folder the dialog shows first. */
|
|
12
|
-
defaultPath?: string;
|
|
13
|
-
filters?: FileFilter[];
|
|
14
|
-
/** Folders instead of files. */
|
|
15
|
-
directory?: boolean;
|
|
16
|
-
multiple?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface SaveOptions {
|
|
20
|
-
title?: string;
|
|
21
|
-
/** The folder the dialog shows first. */
|
|
22
|
-
defaultPath?: string;
|
|
23
|
-
defaultName?: string;
|
|
24
|
-
filters?: FileFilter[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface MessageOptions {
|
|
28
|
-
message: string;
|
|
29
|
-
/** Smaller text under the message. */
|
|
30
|
-
detail?: string;
|
|
31
|
-
/** info by default. Windows shows its icon for each, and macOS a caution sign for error; Linux, iOS and Android show none. */
|
|
32
|
-
kind?: 'info' | 'warning' | 'error';
|
|
33
|
-
/** One to three labels, the first the default; without them OK. */
|
|
34
|
-
buttons?: string[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** The app's own folders, made when first asked for: what it keeps, what it can lose, and its logs. */
|
|
5
|
+
/** The app's own folders, made when first asked for: what it keeps, what it can lose, and its logs; and the read-only one of the files app.files lists, each in the place it had in the project. */
|
|
38
6
|
export interface AppPaths {
|
|
39
7
|
data: string;
|
|
40
8
|
cache: string;
|
|
41
9
|
logs: string;
|
|
10
|
+
resources: string;
|
|
11
|
+
/** An iOS app's App Group's folder, which the targets of its extensions' that ask for it with appGroup: true share; '' elsewhere. */
|
|
12
|
+
group: string;
|
|
42
13
|
}
|
|
43
14
|
|
|
44
|
-
export declare const dialog: {
|
|
45
|
-
/** The paths of the files or folders the user picked, or null when they cancelled. */
|
|
46
|
-
open(options?: OpenOptions): Promise<string[] | null>;
|
|
47
|
-
/** The path the user chose to save to, or null when they cancelled. */
|
|
48
|
-
save(options?: SaveOptions): Promise<string | null>;
|
|
49
|
-
/** The index of the button the user clicked: Return gives the first, and Escape or closing the dialog the last. */
|
|
50
|
-
message(options: MessageOptions): Promise<number>;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export declare const shell: {
|
|
54
|
-
/** An http, https or mailto address in the default browser or mail app. */
|
|
55
|
-
openExternal(url: string | URL): Promise<void>;
|
|
56
|
-
/** A file in the app the system opens its kind with, where app.openPath in crossbind.config.js lets the page: nowhere without it. */
|
|
57
|
-
openPath(path: string): Promise<void>;
|
|
58
|
-
/** A file, selected, in the system's file manager: one in the app's folders, or one a dialog picked during this run. */
|
|
59
|
-
revealInFolder(path: string): Promise<void>;
|
|
60
|
-
/** A file or folder moved to the system's trash, where its user can take it back: one in the app's folders, not one of them itself, or one a dialog picked during this run. Where the system has no trash, nothing moves and it rejects. */
|
|
61
|
-
trash(path: string): Promise<void>;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
15
|
export declare const app: {
|
|
65
16
|
paths(): Promise<AppPaths>;
|
|
66
17
|
/** Ends the app as closing its window does. */
|
|
67
18
|
quit(): Promise<void>;
|
|
19
|
+
/** Asks the app's extensions, as its user's Quit does, then ends the app as quit() does, unless one keeps it running. It resolves once it has asked the host, before the answer. */
|
|
20
|
+
requestQuit(): Promise<void>;
|
|
68
21
|
/** Ends the app as quit() does, then opens it again, with no link. */
|
|
69
22
|
relaunch(): Promise<void>;
|
|
70
|
-
/** The
|
|
71
|
-
|
|
23
|
+
/** The launch that started a desktop app. A phone's has no arguments, and no folder. */
|
|
24
|
+
launchArgs(): Promise<Launch>;
|
|
72
25
|
};
|
|
73
26
|
|
|
74
|
-
/**
|
|
75
|
-
export
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
/** A menu of the bar, by its items, or a standard one by its role. */
|
|
87
|
-
export type Menu = { label: string; items: MenuItem[] } | { role: 'editMenu' | 'windowMenu'; label?: string };
|
|
88
|
-
|
|
89
|
-
/** What the event menu carries: the id of the item clicked, and for one with checked whether it now is. */
|
|
90
|
-
export interface MenuClick {
|
|
91
|
-
id: string;
|
|
92
|
-
checked?: boolean;
|
|
27
|
+
/** Where the app runs, as its host tells the page before the page's own scripts run; the same through the app's run. */
|
|
28
|
+
export interface Device {
|
|
29
|
+
/** The platform the app is built for. */
|
|
30
|
+
readonly platform: 'macos' | 'windows' | 'linux' | 'ios' | 'android';
|
|
31
|
+
/** The arch the app's process runs as. */
|
|
32
|
+
readonly arch: 'arm64' | 'x64';
|
|
33
|
+
/** What the device is to its user as the app starts: a phone; a tablet, an iPad or an Android device whose screen's shorter side has 600dp; or a desktop, a computer, a Chromebook or a Mac that runs an iOS app. */
|
|
34
|
+
readonly formFactor: 'phone' | 'tablet' | 'desktop';
|
|
35
|
+
/** The system's name and version, as its maker numbers it: iOS or iPadOS 18.4, Android 15 with its API level, macOS 26.1, Windows 10.0.26100, or a Linux distribution's, as Ubuntu 24.04, else its kernel's release. */
|
|
36
|
+
readonly os: { readonly name: string; readonly version: string; readonly apiLevel?: number };
|
|
37
|
+
/** Whether the iOS Simulator or an Android emulator runs the app. */
|
|
38
|
+
readonly isVirtual: boolean;
|
|
93
39
|
}
|
|
94
40
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
set(menus: Menu[]): Promise<void>;
|
|
98
|
-
};
|
|
41
|
+
/** Where the app runs, read at once, as the page starts; reading it outside an app throws. */
|
|
42
|
+
export declare const device: Device;
|
|
99
43
|
|
|
100
|
-
/**
|
|
101
|
-
export
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
44
|
+
/** What windows.set() changes of an open window: sizes and places in logical pixels, a page's size without the frame, and x and y the frame's place from the top left of the main screen, given together. A limit of 0 takes it away. */
|
|
45
|
+
export interface WindowChanges {
|
|
46
|
+
title?: string;
|
|
47
|
+
width?: number;
|
|
48
|
+
height?: number;
|
|
49
|
+
x?: number;
|
|
50
|
+
y?: number;
|
|
51
|
+
/** Moves the window to the middle of its screen. */
|
|
52
|
+
center?: boolean;
|
|
53
|
+
minWidth?: number;
|
|
54
|
+
minHeight?: number;
|
|
55
|
+
maxWidth?: number;
|
|
56
|
+
maxHeight?: number;
|
|
57
|
+
resizable?: boolean;
|
|
58
|
+
/** Keeps the window above the other apps' windows. */
|
|
59
|
+
alwaysOnTop?: boolean;
|
|
60
|
+
fullscreen?: boolean;
|
|
111
61
|
}
|
|
112
62
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
63
|
+
/** What a window opens with: app.window's options, beside the page it loads and its name. Without x and y it opens in the middle of the screen. */
|
|
64
|
+
export interface WindowOptions extends Omit<WindowChanges, 'center' | 'minWidth' | 'minHeight' | 'maxWidth' | 'maxHeight'> {
|
|
65
|
+
/** The app's own page, as /settings.html?tab=2: '/' by default. */
|
|
66
|
+
path?: string;
|
|
67
|
+
/** A name of the app's own: a window of a name already open comes to the front, and no other opens. */
|
|
68
|
+
name?: string;
|
|
69
|
+
minWidth?: number;
|
|
70
|
+
minHeight?: number;
|
|
71
|
+
maxWidth?: number;
|
|
72
|
+
maxHeight?: number;
|
|
73
|
+
/** What the window shows until the page paints, as #rrggbb: app.window's by default. */
|
|
74
|
+
backgroundColor?: string;
|
|
75
|
+
/** false opens a window without a title bar and its buttons, which the page moves with data-crossbind-drag or windows.startDrag(). */
|
|
76
|
+
frame?: boolean;
|
|
77
|
+
/** 'hidden' lays a macOS window's page under its title bar, whose buttons stay at its top left, as app.window.titleBar does; Windows and Linux keep their title bar and list it in ignored. */
|
|
78
|
+
titleBar?: 'visible' | 'hidden';
|
|
79
|
+
/** The id of the window it stays above, and closes with. */
|
|
80
|
+
parent?: number;
|
|
81
|
+
/** With a parent: a sheet of the parent's on macOS, and on Windows and Linux a window the parent takes no input under until it closes. */
|
|
82
|
+
modal?: boolean;
|
|
83
|
+
}
|
|
119
84
|
|
|
120
|
-
|
|
85
|
+
/** An open window, as windows.list() tells of it. */
|
|
86
|
+
export interface WindowState {
|
|
87
|
+
id: number;
|
|
88
|
+
name: string | null;
|
|
121
89
|
title: string;
|
|
122
|
-
|
|
90
|
+
x: number;
|
|
91
|
+
y: number;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
isMaximized: boolean;
|
|
95
|
+
isMinimized: boolean;
|
|
96
|
+
isFullscreen: boolean;
|
|
97
|
+
isFocused: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** What windows.open() and windows.set() answer: the window's id, and the options the platform could not honour, which it did without, as Wayland's x and y. */
|
|
101
|
+
export interface WindowOpened {
|
|
102
|
+
id: number;
|
|
103
|
+
ignored: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** What the events window-opened and window-closed carry. */
|
|
107
|
+
export interface WindowEvent {
|
|
108
|
+
id: number;
|
|
109
|
+
name: string | null;
|
|
123
110
|
}
|
|
124
111
|
|
|
125
|
-
export declare const
|
|
126
|
-
/**
|
|
127
|
-
|
|
112
|
+
export declare const windows: {
|
|
113
|
+
/** Opens a window of the app's own page, in front, on the desktop and an iPad whose app.ios.multipleWindows is true; an iPhone and Android reject. */
|
|
114
|
+
open(options?: WindowOptions): Promise<WindowOpened>;
|
|
115
|
+
/** Changes the window of this id, or the page's own without one. */
|
|
116
|
+
set(changes: WindowChanges): Promise<{ ignored: string[] }>;
|
|
117
|
+
set(id: number, changes: WindowChanges): Promise<{ ignored: string[] }>;
|
|
118
|
+
/** Closes the window of this id, or the page's own without one, as its user does: an extension may keep it open. The app quits as its last window closes. */
|
|
119
|
+
close(id?: number): Promise<void>;
|
|
120
|
+
/** Brings the window of this id, or the page's own without one, to the front, restored if it was minimized. */
|
|
121
|
+
focus(id?: number): Promise<void>;
|
|
122
|
+
minimize(id?: number): Promise<void>;
|
|
123
|
+
toggleMaximize(id?: number): Promise<void>;
|
|
124
|
+
/** Moves the page's window with the pointer, as its title bar does, from a mousedown of the primary button. */
|
|
125
|
+
startDrag(): Promise<void>;
|
|
126
|
+
/** The open windows, the oldest first. */
|
|
127
|
+
list(): Promise<WindowState[]>;
|
|
128
|
+
/** The page's own window. */
|
|
129
|
+
current(): Promise<WindowState>;
|
|
128
130
|
};
|
|
129
131
|
|
|
130
|
-
/**
|
|
131
|
-
export interface
|
|
132
|
+
/** A launch of a desktop app: its arguments, the program's own and its links left out, which reach the owners of their schemes, the page those of app.urlSchemes as the event open-url; and the folder it was launched in. */
|
|
133
|
+
export interface Launch {
|
|
132
134
|
args: string[];
|
|
133
135
|
cwd: string;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
/** What
|
|
137
|
-
export interface
|
|
138
|
-
|
|
138
|
+
/** What each event a page's listeners get gives them, by the event's name: the host's own here, and those an extension's types add. */
|
|
139
|
+
export interface AppEvents {
|
|
140
|
+
/** Calls listener as the app becomes the active one on the desktop, or comes to a phone's foreground, until the function it returns is called. A Linux app's is its window's focus. */
|
|
141
|
+
'active': void;
|
|
142
|
+
/** Calls listener as another app takes the app's place on the desktop, or a phone's is interrupted, as by a call, a system alert or another app's picker, or goes to the background, until the function it returns is called. */
|
|
143
|
+
'inactive': void;
|
|
144
|
+
/** Calls listener as each window of the app's opens, until the function it returns is called. */
|
|
145
|
+
'window-opened': WindowEvent;
|
|
146
|
+
/** Calls listener as each window of the app's closes, until the function it returns is called. */
|
|
147
|
+
'window-closed': WindowEvent;
|
|
139
148
|
}
|
|
140
149
|
|
|
150
|
+
/** What each event the host holds for the page gives, by the event's name, which an extension's types add. */
|
|
151
|
+
export interface HeldEvents {}
|
|
152
|
+
|
|
141
153
|
export declare const events: {
|
|
142
|
-
|
|
143
|
-
on(name: 'menu', listener: (click: MenuClick) => void): () => void;
|
|
144
|
-
/** Calls listener with each link of app.urlSchemes the system opens the app with once the page has called app.launchUrls(), until the function it returns is called. */
|
|
145
|
-
on(name: 'open-url', listener: (open: OpenUrl) => void): () => void;
|
|
146
|
-
/** Calls listener with each later launch of a desktop app that app.singleInstance keeps to one instance, which brings its window to the front as far as the system lets it, until the function it returns is called. */
|
|
147
|
-
on(name: 'second-instance', listener: (launch: SecondInstance) => void): () => void;
|
|
148
|
-
/** Calls listener as the system of a desktop app goes to sleep, which it may do before the page has run, until the function it returns is called. */
|
|
149
|
-
on(name: 'suspend', listener: () => void): () => void;
|
|
150
|
-
/** Calls listener as the system of a desktop app wakes from sleep, until the function it returns is called. */
|
|
151
|
-
on(name: 'resume', listener: () => void): () => void;
|
|
152
|
-
/** Calls listener with each left or right click of the tray's icon that opens no menu, until the function it returns is called. */
|
|
153
|
-
on(name: 'tray', listener: () => void): () => void;
|
|
154
|
-
/** Calls listener with each click on a notification the app's notification.show showed while it runs, until the function it returns is called. */
|
|
155
|
-
on(name: 'notification', listener: () => void): () => void;
|
|
154
|
+
on<Name extends keyof AppEvents>(name: Name, listener: (value: AppEvents[Name]) => void): () => void;
|
|
156
155
|
/** Calls listener with the value of each event of this name the app's native code emits from now on, until the function it returns is called. */
|
|
157
156
|
on(name: string, listener: (value: unknown) => void): () => void;
|
|
157
|
+
held<Name extends keyof HeldEvents>(name: Name): Promise<HeldEvents[Name][]>;
|
|
158
|
+
/** The values of the events of this name the app's native code held for the page with holdEvent(), which came before the page asked for them since it loaded; later ones come as the events while it listens, so listening first, then asking, misses none. */
|
|
159
|
+
held(name: string): Promise<unknown[]>;
|
|
158
160
|
};
|
|
159
161
|
|
|
160
|
-
/**
|
|
161
|
-
export interface
|
|
162
|
-
|
|
162
|
+
/** What an option of a page's function is, beside what typeof tells: how it is checked, and what it is called. */
|
|
163
|
+
export interface OptionKind {
|
|
164
|
+
isFitting(value: unknown): boolean;
|
|
165
|
+
name: string;
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
/**
|
|
166
|
-
export declare
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
/** The options as the host takes them, so a misspelt or mistyped one fails here, saying which. */
|
|
169
|
+
export declare function checkedOptions<T>(name: string, options: T, kinds: Record<string, string | OptionKind>): T;
|
|
170
|
+
|
|
171
|
+
/** What invoke takes beside a function's options. */
|
|
172
|
+
export interface InvokeOptions<M = unknown> {
|
|
173
|
+
/** Gets each message the function sends before it answers, in the order it sent them, all of them before invoke resolves. */
|
|
174
|
+
onMessage?: (message: M) => void;
|
|
175
|
+
/** Cancels the call: invoke rejects with the signal's reason, and the function hears of it. A new page cancels each call of the one before. */
|
|
176
|
+
signal?: AbortSignal;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** A function of one of the app's extensions, by its name, as clipboard.readText, with options of JSON values and bytes, a typed array, a DataView or an ArrayBuffer, whose result's bytes come back as a Uint8Array; an extension's own package calls it so. */
|
|
180
|
+
export declare function invoke<T = unknown, M = unknown>(name: string, options?: Record<string, unknown>, call?: InvokeOptions<M>): Promise<T>;
|
|
181
|
+
|
|
182
|
+
/** The URL of a path an extension's route serves, as "/clips/intro.mp4" or "/thumb/42?size=256", for the page's <img>, <video> or fetch(): crossbind://app/ext/<extension><path>, and https://appassets.androidplatform.net/ext/<extension><path> on Android. The path is as a URL holds it, escaped where it must be, without a part "." or ".."; the route gets it decoded. */
|
|
183
|
+
export declare function routeUrl(extension: string, path: string): string;
|