@lakutata/builder 3.1.3 → 3.1.6

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 (39) hide show
  1. package/README.md +24 -1
  2. package/README.zh-CN.md +7 -1
  3. package/dist/esm/index.js +5 -0
  4. package/dist/esm/lib/Archive.js +3 -2
  5. package/dist/esm/lib/Bundle.js +2 -1
  6. package/dist/esm/lib/Directories.js +18 -0
  7. package/dist/esm/lib/Executable.js +27 -6
  8. package/dist/esm/lib/Installer.js +28 -14
  9. package/dist/esm/lib/NativeRuntime.js +27 -3
  10. package/dist/esm/lib/Processes.js +3 -2
  11. package/dist/esm/lib/Tools.js +98 -19
  12. package/dist/esm/lib/linux/Linux.js +60 -10
  13. package/dist/esm/lib/mac/Mac.js +39 -11
  14. package/dist/esm/lib/mobile/Addons.js +675 -0
  15. package/dist/esm/lib/mobile/Node.js +190 -0
  16. package/dist/esm/lib/mobile/Stage.js +180 -0
  17. package/dist/esm/lib/nwjs/Addons.js +310 -0
  18. package/dist/esm/lib/nwjs/Package.js +149 -0
  19. package/dist/esm/lib/nwjs/Runtime.js +188 -0
  20. package/dist/esm/lib/nwjs/Stage.js +92 -0
  21. package/dist/esm/lib/win/Windows.js +68 -45
  22. package/dist/types/index.d.ts +7 -0
  23. package/dist/types/lib/Bundle.d.ts +5 -0
  24. package/dist/types/lib/Directories.d.ts +8 -0
  25. package/dist/types/lib/Executable.d.ts +7 -0
  26. package/dist/types/lib/Installer.d.ts +37 -4
  27. package/dist/types/lib/Processes.d.ts +2 -1
  28. package/dist/types/lib/Tools.d.ts +47 -1
  29. package/dist/types/lib/linux/Linux.d.ts +2 -1
  30. package/dist/types/lib/mac/Mac.d.ts +1 -1
  31. package/dist/types/lib/mobile/Addons.d.ts +154 -0
  32. package/dist/types/lib/mobile/Node.d.ts +89 -0
  33. package/dist/types/lib/mobile/Stage.d.ts +111 -0
  34. package/dist/types/lib/nwjs/Addons.d.ts +69 -0
  35. package/dist/types/lib/nwjs/Package.d.ts +56 -0
  36. package/dist/types/lib/nwjs/Runtime.d.ts +120 -0
  37. package/dist/types/lib/nwjs/Stage.d.ts +104 -0
  38. package/dist/types/lib/win/Windows.d.ts +30 -1
  39. package/package.json +2 -2
@@ -0,0 +1,190 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { cp, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { runCommand } from '../Processes.js';
5
+ import { cacheDirectory, downloadFile, moveIntoCache } from '../Tools.js';
6
+ /**
7
+ * The version of Node.js of the mobile applications when their libnode does not tell it: the one of the release of
8
+ * lakutata/libnode they download (MOBILE_NODE_RELEASE)
9
+ */
10
+ export const MOBILE_NODE_VERSION = '24.20.0';
11
+ /**
12
+ * The architectures of Android of a libnode built for them (the arch of Node.js names the native tree of the
13
+ * architecture, the target names the compilers of the NDK)
14
+ */
15
+ export const ANDROID_ABIS = {
16
+ 'arm64-v8a': { arch: 'arm64', target: 'aarch64-linux-android' },
17
+ 'armeabi-v7a': { arch: 'arm', target: 'armv7a-linux-androideabi' },
18
+ 'x86_64': { arch: 'x64', target: 'x86_64-linux-android' }
19
+ };
20
+ /**
21
+ * The version of Node.js of a libnode: the one of its headers (include/node/node_version.h)
22
+ * @param directory the export of the libnode
23
+ * @internal
24
+ */
25
+ export function mobileNodeVersion(directory) {
26
+ const header = readFileSync(path.join(directory, 'include', 'node', 'node_version.h'), 'utf8');
27
+ const part = (name) => new RegExp(`#define NODE_${name}_VERSION (\\d+)`).exec(header)?.[1];
28
+ const [major, minor, patch] = [part('MAJOR'), part('MINOR'), part('PATCH')];
29
+ if (!major || !minor || !patch)
30
+ throw new Error(`${directory} holds no version of Node.js (include/node/node_version.h)`);
31
+ return `${major}.${minor}.${patch}`;
32
+ }
33
+ /**
34
+ * The architectures of Android a libnode is built for (libs/<abi>/libnode.so)
35
+ * @param directory the export of the libnode of Android
36
+ * @internal
37
+ */
38
+ export function mobileNodeAbis(directory) {
39
+ return Object.keys(ANDROID_ABIS).filter((abi) => existsSync(path.join(directory, 'libs', abi, 'libnode.so')));
40
+ }
41
+ /**
42
+ * Check the export of a libnode of a system: its headers, and its libraries (Android: libs/<abi>/libnode.so, HarmonyOS:
43
+ * libs/arm64-v8a/libnode.so) or its framework (iOS: NodeMobile.xcframework)
44
+ * @param platform
45
+ * @param directory
46
+ * @internal
47
+ */
48
+ export function checkMobileNode(platform, directory) {
49
+ const missing = (what) => new Error(`${directory} is not the libnode of ${platform} (${what} missing): download it (lakutata mobile node ${platform})`);
50
+ if (!existsSync(path.join(directory, 'include', 'node', 'node_api.h')))
51
+ throw missing('include/node/node_api.h');
52
+ if (platform === 'ios' && !existsSync(path.join(directory, 'NodeMobile.xcframework', 'Info.plist')))
53
+ throw missing('NodeMobile.xcframework');
54
+ if (platform === 'android' && !mobileNodeAbis(directory).length)
55
+ throw missing('libs/<abi>/libnode.so');
56
+ if (platform === 'harmony' && !existsSync(path.join(directory, 'libs', 'arm64-v8a', 'libnode.so')))
57
+ throw missing('libs/arm64-v8a/libnode.so');
58
+ }
59
+ /**
60
+ * The release of lakutata/libnode the mobile applications download their libnode from: its tag is v<release> (the
61
+ * version of Node.js, then -<revision> for a release of the same version built again)
62
+ */
63
+ export const MOBILE_NODE_RELEASE = '24.20.0';
64
+ /**
65
+ * The architectures of HarmonyOS the mobile applications ship (the libs/arm64-v8a of their HAP)
66
+ */
67
+ const HARMONY_ABIS = { 'arm64-v8a': { arch: 'arm64' } };
68
+ /**
69
+ * Where the releases of lakutata/libnode are downloaded from: LAKUTATA_LIBNODE_MIRROR (a mirror of them, its files laid
70
+ * out as the ones of GitHub: v<release>/<archive>), else the releases of the repository on GitHub
71
+ */
72
+ function libnodeMirror() {
73
+ return (process.env.LAKUTATA_LIBNODE_MIRROR ?? 'https://github.com/lakutata/libnode/releases/download').replace(/\/+$/, '');
74
+ }
75
+ /**
76
+ * An archive of the release, downloaded once into the cache (~/.cache/lakutata/mobile/libnode, LAKUTATA_CACHE_DIR),
77
+ * checked with the SHA256SUMS of the release and extracted
78
+ * @param name the archive without its extension: libnode_<platform>_<arch>
79
+ * @param sums the sha256 of the archives of the release, by their names
80
+ * @param progress
81
+ * @returns the directory extracted: lib/libnode.so or NodeMobile.xcframework, include/node, LICENSE, libnode.json
82
+ */
83
+ async function libnodeArchive(name, sums, progress) {
84
+ const directory = path.join(cacheDirectory(), 'mobile', 'libnode', MOBILE_NODE_RELEASE, name);
85
+ if (existsSync(path.join(directory, '.complete')))
86
+ return directory;
87
+ const archive = `${name}.tar.gz`;
88
+ const url = `${libnodeMirror()}/v${MOBILE_NODE_RELEASE}/${archive}`;
89
+ const sum = (await sums()).get(archive);
90
+ if (!sum)
91
+ throw new Error(`The release ${MOBILE_NODE_RELEASE} of libnode has no ${archive} in its SHA256SUMS`);
92
+ progress?.(`${archive} (libnode ${MOBILE_NODE_RELEASE})`);
93
+ //Extracted aside, then moved into the cache: the builds running together do not see it partly written
94
+ await mkdir(path.dirname(directory), { recursive: true });
95
+ const temporary = await mkdtemp(`${directory}.`);
96
+ try {
97
+ const file = path.join(temporary, archive);
98
+ const downloaded = await downloadFile(url, file, { progress: progress }).catch((error) => {
99
+ throw new Error(`${error.message}: a mirror of the releases of lakutata/libnode is given by LAKUTATA_LIBNODE_MIRROR, a libnode of your own by LAKUTATA_MOBILE_NODE_DIR`);
100
+ });
101
+ if (downloaded !== sum)
102
+ throw new Error(`The ${archive} downloaded is not the one of the release ${MOBILE_NODE_RELEASE} of libnode (sha256 ${downloaded}, ${sum} expected)`);
103
+ const extracted = await runCommand('tar', ['-xf', archive], temporary);
104
+ if (extracted.code !== 0)
105
+ throw new Error(`${archive} cannot be extracted: ${extracted.output.trim()}`);
106
+ await rm(file);
107
+ const root = path.join(temporary, name);
108
+ if (!existsSync(path.join(root, 'include', 'node', 'node_api.h')))
109
+ throw new Error(`${archive} has no ${name}/include/node`);
110
+ await writeFile(path.join(root, '.complete'), '');
111
+ await moveIntoCache(root, directory, () => existsSync(path.join(directory, '.complete')));
112
+ }
113
+ finally {
114
+ await rm(temporary, { recursive: true, force: true });
115
+ }
116
+ return directory;
117
+ }
118
+ /**
119
+ * Download the libnode of a mobile system from the releases of lakutata/libnode (LAKUTATA_LIBNODE_MIRROR: a mirror of
120
+ * them), once: its archives are checked with the SHA256SUMS of the release and kept in the cache
121
+ * (~/.cache/lakutata/mobile, LAKUTATA_CACHE_DIR). LAKUTATA_MOBILE_NODE_DIR gives exports of a libnode of your own instead
122
+ * (a directory per system: android, ios, harmony; built with the repository of lakutata/libnode)
123
+ * @param options
124
+ * @returns the directory of the export: its headers (include/node) and its libraries (libs/<abi>/libnode.so) or its
125
+ * framework (NodeMobile.xcframework)
126
+ * @example
127
+ * import {downloadMobileNode} from '@lakutata/builder'
128
+ *
129
+ * const directory = await downloadMobileNode({platform: 'android', abis: ['arm64-v8a'], progress: console.log})
130
+ * console.log(directory)
131
+ */
132
+ export async function downloadMobileNode(options) {
133
+ const given = process.env.LAKUTATA_MOBILE_NODE_DIR;
134
+ if (given) {
135
+ const directory = path.resolve(given, options.platform);
136
+ checkMobileNode(options.platform, directory);
137
+ return directory;
138
+ }
139
+ //The archives by architecture: the ABI of their library in the export
140
+ const archives = options.platform === 'ios'
141
+ ? [['libnode_ios_arm64', undefined]]
142
+ : options.platform === 'harmony'
143
+ ? Object.entries(HARMONY_ABIS).map(([abi, { arch }]) => [`libnode_harmony_${arch}`, abi])
144
+ : [...new Set(options.abis ?? ['arm64-v8a', 'x86_64'])].map((abi) => {
145
+ if (!ANDROID_ABIS[abi])
146
+ throw new Error(`${abi} is not an architecture of Android (${Object.keys(ANDROID_ABIS).join(', ')})`);
147
+ return [`libnode_android_${ANDROID_ABIS[abi].arch}`, abi];
148
+ });
149
+ const target = path.join(cacheDirectory(), 'mobile', 'node', MOBILE_NODE_RELEASE, [options.platform, ...archives.map(([, abi]) => abi ?? '')].filter(Boolean).join('-'));
150
+ if (existsSync(path.join(target, '.complete')))
151
+ return target;
152
+ //The checksums of the release, read once and only when an archive is downloaded
153
+ let sums;
154
+ const checksums = () => sums ??= (async () => {
155
+ const response = await fetch(`${libnodeMirror()}/v${MOBILE_NODE_RELEASE}/SHA256SUMS`).catch((error) => {
156
+ throw new Error(`The checksums of the release ${MOBILE_NODE_RELEASE} of libnode cannot be downloaded (${error.message}): a mirror of the releases of lakutata/libnode is given by LAKUTATA_LIBNODE_MIRROR`);
157
+ });
158
+ if (!response.ok)
159
+ throw new Error(`The checksums of the release ${MOBILE_NODE_RELEASE} of libnode cannot be downloaded (${response.url}: ${response.status})`);
160
+ return new Map((await response.text()).split('\n').map((line) => /^([0-9a-f]{64})\s+\*?(\S+)$/.exec(line.trim())).filter((match) => !!match).map((match) => [match[2], match[1]]));
161
+ })();
162
+ const extracted = [];
163
+ for (const [name, abi] of archives)
164
+ extracted.push([await libnodeArchive(name, checksums, options.progress), abi]);
165
+ //The export: the headers and the description of the first archive, the library of each architecture
166
+ await mkdir(path.dirname(target), { recursive: true });
167
+ const temporary = await mkdtemp(`${target}.`);
168
+ try {
169
+ const [first] = extracted[0];
170
+ await cp(path.join(first, 'include'), path.join(temporary, 'include'), { recursive: true });
171
+ for (const file of ['LICENSE', 'libnode.json'])
172
+ if (existsSync(path.join(first, file)))
173
+ await cp(path.join(first, file), path.join(temporary, file));
174
+ for (const [directory, abi] of extracted) {
175
+ if (abi) {
176
+ await mkdir(path.join(temporary, 'libs', abi), { recursive: true });
177
+ await cp(path.join(directory, 'lib', 'libnode.so'), path.join(temporary, 'libs', abi, 'libnode.so'));
178
+ }
179
+ else
180
+ await cp(path.join(directory, 'NodeMobile.xcframework'), path.join(temporary, 'NodeMobile.xcframework'), { recursive: true, verbatimSymlinks: true });
181
+ }
182
+ checkMobileNode(options.platform, temporary);
183
+ await writeFile(path.join(temporary, '.complete'), '');
184
+ await moveIntoCache(temporary, target, () => existsSync(path.join(target, '.complete')));
185
+ }
186
+ finally {
187
+ await rm(temporary, { recursive: true, force: true });
188
+ }
189
+ return target;
190
+ }
@@ -0,0 +1,180 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { lstat, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { bundleProject } from '../Bundle.js';
5
+ import { packNativeTree, writeNativeTree } from '../Native.js';
6
+ import { BUN_NATIVE_ASSETS } from '../NativeRuntime.js';
7
+ import { MOBILE_ADDONS_FILE, prepareAndroidAddons, prepareHarmonyAddons, prepareIosAddons } from './Addons.js';
8
+ import { checkMobileNode, MOBILE_NODE_VERSION, mobileNodeVersion } from './Node.js';
9
+ /**
10
+ * The package of a file of the native tree: the last one of the node_modules in its path
11
+ * @param file
12
+ */
13
+ function packageOf(file) {
14
+ const parts = file.split('/');
15
+ const index = parts.lastIndexOf('node_modules');
16
+ if (index < 0)
17
+ return file;
18
+ return parts.slice(index + 1, parts[index + 1]?.startsWith('@') ? index + 3 : index + 2).join('/');
19
+ }
20
+ /**
21
+ * The files of the embedded native tree of HarmonyOS, next to the entry (names without a leading dot: the resources of
22
+ * the HAP leave those out)
23
+ */
24
+ const NATIVE_DATA = 'native.gz';
25
+ const NATIVE_INDEX = 'native.json';
26
+ /**
27
+ * The entry of the Node.js project: the fetch() of Node.js put away without WebAssembly (V8 runs --jitless on
28
+ * HarmonyOS: its first call would throw an uncaught exception). The native tree embedded in the project (HarmonyOS) is
29
+ * given to the bundle, which extracts it at its first run into the directory LAKUTATA_NATIVE_DIR gives (the native
30
+ * module sets it). The addons of iOS and HarmonyOS are loaded from where the system keeps them (MOBILE_ADDONS_FILE: the
31
+ * frameworks of the application, the libraries of the HAP), in place of their placeholders in the tree. Then the
32
+ * application, bundled
33
+ */
34
+ const ENTRY_SCRIPT = `//The entry of the application in the Node.js of the mobile application: fetch() put away without WebAssembly, the native tree embedded (HarmonyOS) given to the bundle, the native addons of iOS and HarmonyOS loaded from where the system keeps them, then the application, bundled
35
+ const fs = require('fs')
36
+ const path = require('path')
37
+ if (typeof WebAssembly === 'undefined') delete globalThis.fetch
38
+ const tree = path.join(__dirname, '${NATIVE_INDEX}')
39
+ if (fs.existsSync(tree)) globalThis[Symbol.for('${BUN_NATIVE_ASSETS}')] = {data: path.join(__dirname, '${NATIVE_DATA}'), index: tree}
40
+ const addons = path.join(__dirname, '${MOBILE_ADDONS_FILE}')
41
+ if (fs.existsSync(addons)) {
42
+ //The placeholders are keyed by their paths in the tree, wherever the tree is (next to the bundle, extracted)
43
+ const {directory, files} = JSON.parse(fs.readFileSync(addons, 'utf8'))
44
+ const base = path.resolve(fs.realpathSync(__dirname), directory)
45
+ const keys = Object.keys(files)
46
+ const dlopen = process.dlopen
47
+ process.dlopen = function (module, filename, ...rest) {
48
+ let real
49
+ try {
50
+ real = fs.realpathSync(filename).split(path.sep).join('/')
51
+ } catch {
52
+ real = undefined
53
+ }
54
+ const key = real && keys.find((key) => real.endsWith('/' + key))
55
+ return dlopen.call(this, module, key ? path.join(base, files[key]) : filename, ...rest)
56
+ }
57
+ }
58
+ require('./app.cjs')
59
+ `;
60
+ /**
61
+ * The files of a native tree written in a directory, its links left out (they stay the links of the tree)
62
+ * @param directory
63
+ * @param base
64
+ */
65
+ async function treeFiles(directory, base = directory) {
66
+ const files = [];
67
+ for (const entry of (await readdir(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
68
+ const file = path.join(directory, entry.name);
69
+ if (entry.isSymbolicLink())
70
+ continue;
71
+ if (entry.isDirectory())
72
+ files.push(...await treeFiles(file, base));
73
+ else if (entry.isFile())
74
+ files.push({ path: path.relative(base, file).split(path.sep).join('/'), source: file, mode: (await lstat(file)).mode & 0o777 });
75
+ }
76
+ return files;
77
+ }
78
+ /**
79
+ * Make the Node.js project of a mobile application of React Native (Android, iOS, HarmonyOS), which the native module
80
+ * of the application runs in its libnode: the bundle of the application (CommonJS, for the version of Node.js of the
81
+ * libnode) and its resources (lakutata.resources), its manifest and its entry. The bundle holds the dependencies: the
82
+ * project is packed into the application as it is. The native addons are built for the system from the sources of their
83
+ * packages, against the headers and the libraries of the libnode: on Android, a native tree per architecture (node-gyp
84
+ * and the NDK of Android); on iOS, a framework per addon for each SDK (node-gyp and Xcode), in the directory of the
85
+ * frameworks the project of iOS embeds; on HarmonyOS, a library per addon (node-gyp and the native SDK of HarmonyOS),
86
+ * in the libraries of the HAP
87
+ * @param options
88
+ * @example
89
+ * import {stageMobileApp} from '@lakutata/builder'
90
+ *
91
+ * //In packages/app of a project of the mobile-react template, before the application of Android is built
92
+ * const stage = await stageMobileApp({
93
+ * platform: 'android',
94
+ * outdir: '../../android/app/src/main/assets/nodejs',
95
+ * node: '../ipc/native/node/out/android'
96
+ * })
97
+ * console.log(stage.directory, stage.node)
98
+ */
99
+ export async function stageMobileApp(options) {
100
+ const project = path.resolve(options.project ?? process.cwd());
101
+ const manifestFile = path.join(project, 'package.json');
102
+ if (!existsSync(manifestFile))
103
+ throw new Error(`${project} has no package.json`);
104
+ const manifest = JSON.parse(await readFile(manifestFile, 'utf8'));
105
+ const directory = path.resolve(project, options.outdir);
106
+ //The directory is made again: never the project or a directory holding it
107
+ if (!path.relative(directory, project).startsWith('..'))
108
+ throw new Error(`The Node.js project of the mobile application is made in a directory of its own, not ${directory}`);
109
+ const node = options.node ? path.resolve(project, options.node) : undefined;
110
+ if (node)
111
+ checkMobileNode(options.platform, node);
112
+ const version = node ? mobileNodeVersion(node) : MOBILE_NODE_VERSION;
113
+ await rm(directory, { recursive: true, force: true });
114
+ await mkdir(directory, { recursive: true });
115
+ //The frameworks of the addons of iOS made before for the SDKs built: made again, or none (the ones of the other SDK
116
+ //kept: the development builds for the simulators only); the libraries of the addons of HarmonyOS made before
117
+ const frameworks = options.platform === 'ios' && options.frameworks ? path.resolve(project, options.frameworks) : undefined;
118
+ if (frameworks)
119
+ for (const sdk of options.sdks?.length ? options.sdks : ['iphoneos', 'iphonesimulator'])
120
+ await rm(path.join(frameworks, sdk), { recursive: true, force: true });
121
+ const libraries = options.platform === 'harmony' && options.libraries ? path.resolve(project, options.libraries) : undefined;
122
+ if (libraries && existsSync(libraries)) {
123
+ for (const name of await readdir(libraries))
124
+ if (/^libaddon-.*\.so$/.test(name))
125
+ await rm(path.join(libraries, name), { force: true });
126
+ }
127
+ //The resources of the HAP of HarmonyOS leave out the files whose names start with a dot (the .lakutata of the native
128
+ //tree): there the tree is embedded, packed into the project, extracted at run time
129
+ const harmony = options.platform === 'harmony';
130
+ const bundle = await bundleProject({
131
+ project: project,
132
+ entry: options.entry,
133
+ outfile: path.join(directory, 'app.cjs'),
134
+ node: version,
135
+ minify: options.minify ?? true,
136
+ external: ['@lakutata/netif', ...options.external ?? []],
137
+ native: harmony ? 'embedded' : 'directory',
138
+ directories: { native: 'app.native', resources: 'resources' }
139
+ });
140
+ //The tree its addons are built in: next to the bundle, or written there to be packed (HarmonyOS)
141
+ const tree = harmony && bundle.native ? path.join(directory, 'app.native') : bundle.nativeDirectory;
142
+ if (harmony && bundle.native)
143
+ await writeNativeTree(bundle.native, tree);
144
+ //The native tree holds the scripts of the workers too: its addons only are built
145
+ const files = bundle.native?.files ?? [];
146
+ const addonFiles = files.map((file) => file.path).filter((file) => file.endsWith('.node'));
147
+ let addons;
148
+ if (addonFiles.length) {
149
+ const packages = [...new Set(addonFiles.map(packageOf))].join(', ');
150
+ try {
151
+ if (!node)
152
+ throw new Error(`The native addons of the application are built against the libnode of ${options.platform}: give the directory of its export to the stage (node; ${packages})`);
153
+ if (options.platform === 'ios' && !frameworks)
154
+ throw new Error(`The native addons of the application are built for iOS into the frameworks the project of iOS embeds: give their directory (frameworks) to the stage (${packages})`);
155
+ if (options.platform === 'harmony' && !libraries)
156
+ throw new Error(`The native addons of the application are built for HarmonyOS into the libraries of the HAP: give their directory (libraries) to the stage (${packages})`);
157
+ if (options.platform === 'ios')
158
+ addons = await prepareIosAddons(project, directory, tree, files, frameworks, node, { sdks: options.sdks, progress: options.progress });
159
+ else if (options.platform === 'harmony')
160
+ addons = await prepareHarmonyAddons(project, directory, tree, files, libraries, node, { progress: options.progress });
161
+ else
162
+ addons = await prepareAndroidAddons(project, tree, files, node, { abis: options.abis, progress: options.progress });
163
+ }
164
+ catch (error) {
165
+ await rm(directory, { recursive: true, force: true });
166
+ throw error;
167
+ }
168
+ }
169
+ if (harmony && bundle.native) {
170
+ //Packed as the tree of an executable of Bun: the bundle extracts it as it is (its hash names the directory)
171
+ const links = bundle.native.links;
172
+ const packed = packNativeTree({ ...bundle.native, files: await treeFiles(tree), links: links });
173
+ await writeFile(path.join(directory, NATIVE_DATA), packed.data);
174
+ await writeFile(path.join(directory, NATIVE_INDEX), JSON.stringify(packed.index));
175
+ await rm(tree, { recursive: true, force: true });
176
+ }
177
+ await writeFile(path.join(directory, 'package.json'), `${JSON.stringify({ name: manifest.name ?? 'app', version: manifest.version ?? '0.0.0', private: true, main: 'main.js' }, null, 2)}\n`);
178
+ await writeFile(path.join(directory, 'main.js'), ENTRY_SCRIPT);
179
+ return { directory: directory, platform: options.platform, node: version, bundle: bundle, addons: addons };
180
+ }