@lakutata/builder 3.0.0-beta.0

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/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-present Lakutata
4
+
5
+ All rights reserved
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @lakutata/builder
2
+
3
+ The builder of the applications of the lakutata framework, used by the `bundle` and `executable` commands of
4
+ `@lakutata/cli`, and usable from the scripts of a project:
5
+
6
+ ```js
7
+ import {buildExecutable, buildInstaller, bundleProject} from '@lakutata/builder'
8
+
9
+ //dist/<name>.cjs, run by node, its native addons and its resources next to it
10
+ await bundleProject({project: process.cwd()})
11
+ //dist/<name>, which runs without Node.js: Node.js (by default) or Bun embedded, one file or a directory
12
+ await buildExecutable({project: process.cwd(), runtime: 'node', layout: 'directory'})
13
+ //dist/installer: the installers of the platform (deb, rpm, apk, archlinux, tar.gz on Linux; pkg, tar.gz on macOS;
14
+ //nsis, msi, zip on Windows), its service registered
15
+ await buildInstaller({project: process.cwd()})
16
+ ```
17
+
18
+ The application is compiled first (`tsc`, which emits the metadata of the decorators). The bundle is one CommonJS file
19
+ (esbuild); the native addons, which cannot be bundled, are gathered with what they load at run time into a native tree;
20
+ the resources of the application (the `lakutata.resources` of its `package.json`) are found at run time by
21
+ `resourcesPath()` of lakutata. The installers are configured by the `lakutata.installer` of the `package.json`. See the
22
+ documentation of `lakutata package bundle`, `lakutata package exe` and `lakutata package installer` in `@lakutata/cli`.
23
+
24
+ The installers of Linux are tested in containers (Docker): `npm run test:installer` makes them on Debian and Alpine,
25
+ installs, upgrades and removes them on Debian and Fedora (systemd) and Alpine (OpenRC). The ones of macOS are installed
26
+ in a virtual machine of macOS (`npm run test:installer:mac`, tart), the ones of Windows on Windows as an administrator
27
+ (`LAKUTATA_WINDOWS_INSTALL=1 npm run test:installer:windows`).
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The builder of the applications of lakutata: their bundles (one CommonJS file run by node) and their executables (the
3
+ * single executable applications of Node.js, or Bun), with their native addons and their resources
4
+ */
5
+ export { bundleProject, programName, resourceFiles } from './lib/Bundle.js';
6
+ export { buildExecutable } from './lib/Executable.js';
7
+ export { NativeCollector, packNativeTree, writeNativeTree } from './lib/Native.js';
8
+ export { buildInstaller } from './lib/Installer.js';
9
+ export { LINUX_TARGETS } from './lib/linux/Linux.js';
10
+ export { INSTALLER_HOOKS, NATIVE_HOOKS } from './lib/Scripts.js';
11
+ export { detectLibc } from './lib/Platform.js';
12
+ export { WINDOWS_TARGETS } from './lib/win/Windows.js';
13
+ export { MAC_TARGETS } from './lib/mac/Mac.js';
@@ -0,0 +1,21 @@
1
+ import { cp, rm, stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { runCommand } from './Processes.js';
4
+ /**
5
+ * The tar.gz archive of the directory of the executable (its native addons and its resources next to it), named after
6
+ * the package, its version, its platform and its architecture
7
+ * @param plan
8
+ * @param arch
9
+ * @param work
10
+ * @param outdir
11
+ */
12
+ export async function archiveDirectory(plan, arch, work, outdir) {
13
+ const base = `${plan.name}-${plan.version}-${process.platform}-${arch}`;
14
+ await rm(path.join(work, base), { recursive: true, force: true });
15
+ await cp(plan.directory, path.join(work, base), { recursive: true, verbatimSymlinks: true });
16
+ const file = path.join(outdir, `${base}.tar.gz`);
17
+ const archived = await runCommand('tar', ['-czf', file, base], work);
18
+ if (archived.code !== 0)
19
+ throw new Error(`The tar.gz archive cannot be made: ${archived.output.trim()}`);
20
+ return { target: 'tar.gz', file: file, size: (await stat(file)).size };
21
+ }
@@ -0,0 +1,162 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { chmod, cp, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { hashNativeTree, NativeCollector, writeNativeTree } from './Native.js';
5
+ import { EMBEDDED_RESOURCES, nativeBanner, packagedBanner } from './NativeRuntime.js';
6
+ /**
7
+ * The name of the program of a package: its first bin, or the name of the package without its scope
8
+ * @param manifest
9
+ */
10
+ export function programName(manifest) {
11
+ if (manifest.bin && typeof manifest.bin === 'object') {
12
+ const [name] = Object.keys(manifest.bin);
13
+ if (name)
14
+ return name;
15
+ }
16
+ return String(manifest.name ?? 'app').replace(/^@[^/]+\//, '');
17
+ }
18
+ /**
19
+ * The ESM sources use import.meta.url, which the CommonJS bundle gives from its file
20
+ */
21
+ const IMPORT_META_URL = '__lakutataImportMetaUrl';
22
+ /**
23
+ * The first code of a bundle embedded in an executable of Bun, which bundles it again into a function without the
24
+ * arguments of the CommonJS modules: its file is the executable (as in the executables of Node.js), its require loads
25
+ * the modules of node and the ones of a node_modules next to the executable (the ones left out of the bundle)
26
+ */
27
+ const BUN_PRELUDE = 'var __filename=process.execPath,__dirname=process.getBuiltinModule(\'node:path\').dirname(__filename),require=typeof require===\'function\'?require:process.getBuiltinModule(\'node:module\').createRequire(__filename);';
28
+ /**
29
+ * The files of the resources of an application, declared by the "lakutata.resources" of its package.json: their paths in
30
+ * the resources ("/" separated) and their sources
31
+ * @param project
32
+ * @param manifest
33
+ */
34
+ export function resourceFiles(project, manifest) {
35
+ const declarations = manifest.lakutata?.resources;
36
+ if (declarations === undefined)
37
+ return { declared: [], files: [] };
38
+ const valid = (declaration) => typeof declaration?.from === 'string' && typeof declaration?.to === 'string' &&
39
+ !!declaration.to && !path.isAbsolute(declaration.to) && !declaration.to.split(/[\\/]/).includes('..');
40
+ if (!Array.isArray(declarations) || !declarations.every(valid))
41
+ throw new Error('The resources of the application (lakutata.resources of its package.json) must be [{"from": "<file or directory of the project>", "to": "<its path in the resources>"}]');
42
+ const files = [];
43
+ for (const { from, to } of declarations) {
44
+ const source = path.resolve(project, from);
45
+ if (!existsSync(source))
46
+ throw new Error(`The resource ${from} does not exist: build it first`);
47
+ const target = to.split(/[\\/]/).filter(Boolean).join('/');
48
+ const walk = (file, at) => {
49
+ const stats = statSync(file);
50
+ if (stats.isDirectory())
51
+ for (const entry of readdirSync(file))
52
+ walk(path.join(file, entry), `${at}/${entry}`);
53
+ else if (stats.isFile())
54
+ files.push({ path: at, source: file, mode: stats.mode & 0o777 });
55
+ };
56
+ walk(source, target);
57
+ }
58
+ return { declared: declarations.map(({ to }) => to), files: files };
59
+ }
60
+ /**
61
+ * The code with a code before it, after its hashbang
62
+ * @param code
63
+ * @param first
64
+ */
65
+ function prepend(code, first) {
66
+ const hashbang = /^#![^\n]*\n/.exec(code);
67
+ return hashbang ? `${hashbang[0]}${first}${code.slice(hashbang[0].length)}` : `${first}${code}`;
68
+ }
69
+ /**
70
+ * Bundle the compiled application and its dependencies into one CommonJS file, run by node and embedded in the executables.
71
+ * The sources are compiled by tsc first: the metadata of the decorators (the injections, the DTOs) are emitted by it.
72
+ * The native addons cannot be bundled: their packages (and what they load) are gathered into a native tree, which the
73
+ * require of the bundle loads them from
74
+ * @param options
75
+ */
76
+ export async function bundleProject(options) {
77
+ const project = path.resolve(options.project);
78
+ const manifestFile = path.join(project, 'package.json');
79
+ if (!existsSync(manifestFile))
80
+ throw new Error(`${project} has no package.json`);
81
+ const manifest = JSON.parse(await readFile(manifestFile, 'utf8'));
82
+ const name = programName(manifest);
83
+ const entry = path.resolve(project, options.entry ?? manifest.main ?? 'build/App.js');
84
+ if (!existsSync(entry))
85
+ throw new Error(`The entry ${path.relative(process.cwd(), entry) || entry} does not exist: build the project first`);
86
+ const outfile = path.resolve(project, options.outfile ?? path.join('dist', `${name}.cjs`));
87
+ const collector = new NativeCollector(project, options.runtime);
88
+ //esbuild is loaded by the commands which need it
89
+ const { build } = await import('esbuild');
90
+ const result = await build({
91
+ entryPoints: [entry],
92
+ outfile: outfile,
93
+ write: false,
94
+ bundle: true,
95
+ platform: 'node',
96
+ target: `node${process.versions.node.split('.')[0]}`,
97
+ format: 'cjs',
98
+ minify: options.minify ?? true,
99
+ //The names of the classes are the ones of the objects of the container and of the errors
100
+ keepNames: true,
101
+ external: options.external ?? [],
102
+ //The dynamic imports of the modules left out are requires (the require of the bundle loads the native ones)
103
+ supported: { 'dynamic-import': false },
104
+ banner: { js: `var ${IMPORT_META_URL}=require('node:url').pathToFileURL(__filename).href;` },
105
+ define: {
106
+ 'import.meta.url': IMPORT_META_URL,
107
+ //Bun replaces process.env.NODE_ENV with the mode of its build: the one of the run is read
108
+ ...options.runtime === 'bun' ? { 'process.env.NODE_ENV': 'globalThis.process.env.NODE_ENV' } : {}
109
+ },
110
+ plugins: [collector.plugin()],
111
+ logLevel: 'silent'
112
+ }).catch((error) => {
113
+ const messages = (error.errors ?? []).map(({ text, location }) => location ? `${location.file}:${location.line}: ${text}` : text);
114
+ //The bundles are CommonJS, which the executables of Node.js run
115
+ const hint = messages.some((message) => message.includes('Top-level await')) ? '\nThe bundles are CommonJS modules, without top-level await: await in an async function' : '';
116
+ throw new Error(`The bundle failed:\n ${messages.length ? messages.join('\n ') : error.message}${hint}`);
117
+ });
118
+ let code = result.outputFiles[0].text;
119
+ const embedded = options.native === 'embedded';
120
+ let native = await collector.collect();
121
+ const resources = resourceFiles(project, manifest);
122
+ const base = path.basename(outfile, path.extname(outfile));
123
+ const nativeDirectory = path.resolve(path.dirname(outfile), options.directories?.native ?? `${base}.native`);
124
+ const resourcesDirectory = path.resolve(path.dirname(outfile), options.directories?.resources ?? `${base}.resources`);
125
+ await rm(nativeDirectory, { recursive: true, force: true });
126
+ await rm(resourcesDirectory, { recursive: true, force: true });
127
+ //The resources of an executable are extracted with its native tree
128
+ if (embedded && resources.files.length) {
129
+ const files = [...native?.files ?? [], ...resources.files.map((file) => ({ ...file, path: `${EMBEDDED_RESOURCES}/${file.path}` }))];
130
+ native = { modules: native?.modules ?? [], files: files, links: native?.links ?? [], hash: hashNativeTree(files, native?.links ?? []) };
131
+ }
132
+ if (native) {
133
+ //The require loading the native packages is the first code, after the hashbang
134
+ code = prepend(code, nativeBanner({ name: name, hash: native.hash, directory: path.basename(nativeDirectory), modules: native.modules, embedded: embedded }));
135
+ if (!embedded)
136
+ await writeNativeTree(native, nativeDirectory);
137
+ }
138
+ if (!embedded) {
139
+ for (const file of resources.files) {
140
+ const target = path.join(resourcesDirectory, ...file.path.split('/'));
141
+ await mkdir(path.dirname(target), { recursive: true });
142
+ await cp(file.source, target);
143
+ }
144
+ }
145
+ code = prepend(code, packagedBanner(path.basename(resourcesDirectory)));
146
+ //Before the require of the native tree, which loads the modules of node by it
147
+ if (options.runtime === 'bun')
148
+ code = prepend(code, BUN_PRELUDE);
149
+ await mkdir(path.dirname(outfile), { recursive: true });
150
+ await writeFile(outfile, code);
151
+ await chmod(outfile, 0o755);
152
+ return {
153
+ name: name,
154
+ entry: entry,
155
+ outfile: outfile,
156
+ size: (await stat(outfile)).size,
157
+ native: native,
158
+ nativeDirectory: native && !embedded ? nativeDirectory : undefined,
159
+ resources: resources.declared,
160
+ resourcesDirectory: resources.files.length && !embedded ? resourcesDirectory : undefined
161
+ };
162
+ }
@@ -0,0 +1,221 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { chmod, copyFile, mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { detectLibc } from './Platform.js';
7
+ import { runCommand } from './Processes.js';
8
+ import { bundleProject, programName } from './Bundle.js';
9
+ import { applicationIcon, icoFile, setExecutableIcon } from './Icons.js';
10
+ import { packNativeTree } from './Native.js';
11
+ import { BUN_NATIVE_ASSETS, NATIVE_DATA_ASSET, NATIVE_INDEX_ASSET } from './NativeRuntime.js';
12
+ /**
13
+ * The fuse of the Node.js binaries telling that a single executable application is injected
14
+ */
15
+ const SEA_FUSE = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2';
16
+ /**
17
+ * Run a command which must succeed
18
+ * @param command
19
+ * @param args
20
+ * @param cwd
21
+ */
22
+ async function must(command, args, cwd) {
23
+ const result = await runCommand(command, args, cwd);
24
+ if (result.code !== 0)
25
+ throw new Error(`${command} ${args.join(' ')} failed: ${result.output.trim()}`);
26
+ }
27
+ /**
28
+ * Run a function without the output of the console: postject prints the warnings of LIEF, which it binds when it is called
29
+ * ("Can't find string offset for section name", the signature of node.exe changed by the injection)
30
+ * @param run
31
+ */
32
+ async function quiet(run) {
33
+ const { log, warn } = console;
34
+ console.log = console.warn = () => undefined;
35
+ try {
36
+ return await run();
37
+ }
38
+ finally {
39
+ console.log = log;
40
+ console.warn = warn;
41
+ }
42
+ }
43
+ /**
44
+ * The bun of the project (the bun package of npm, in its node_modules or in the ones of its workspace), else the one of
45
+ * the system
46
+ * @param project
47
+ * @return the command, and the version of Bun
48
+ */
49
+ async function findBun(project) {
50
+ const candidates = [];
51
+ try {
52
+ const root = path.dirname(createRequire(path.join(project, 'package.json')).resolve('bun/package.json'));
53
+ candidates.push(path.join(root, 'bin', 'bun.exe'));
54
+ }
55
+ catch {
56
+ //Not installed in the project
57
+ }
58
+ candidates.push('bun');
59
+ for (const command of candidates) {
60
+ if (command !== 'bun' && !existsSync(command))
61
+ continue;
62
+ const result = await runCommand(command, ['--version'], project).catch(() => undefined);
63
+ if (result?.code === 0)
64
+ return { command: command, version: result.output.trim() };
65
+ }
66
+ throw new Error('Bun is not installed: install it in the project (npm install --save-dev bun), or on the system (https://bun.sh)');
67
+ }
68
+ /**
69
+ * Write the icon of the application (ico) into the work directory
70
+ * @param work
71
+ * @param icon
72
+ */
73
+ async function writeIcon(work, icon) {
74
+ const file = path.join(work, 'icon.ico');
75
+ await writeFile(file, icon);
76
+ return file;
77
+ }
78
+ /**
79
+ * Embed a bundle in the Node.js binary running the tool (the single executable applications of Node.js)
80
+ * @param bundle
81
+ * @param work
82
+ * @param file
83
+ * @param warnings
84
+ * @param icon the icon of the application (ico, Windows)
85
+ */
86
+ async function embedInNode(bundle, work, file, warnings, icon) {
87
+ const assets = {};
88
+ if (bundle.native) {
89
+ //The files of the native packages, extracted by the executable at its first run
90
+ const { index, data } = packNativeTree(bundle.native);
91
+ assets[NATIVE_DATA_ASSET] = path.join(work, 'native.gz');
92
+ assets[NATIVE_INDEX_ASSET] = path.join(work, 'native.json');
93
+ await writeFile(assets[NATIVE_DATA_ASSET], data);
94
+ await writeFile(assets[NATIVE_INDEX_ASSET], JSON.stringify(index));
95
+ }
96
+ //The preparation blob of the bundle
97
+ const blob = path.join(work, 'main.blob');
98
+ await writeFile(path.join(work, 'sea-config.json'), JSON.stringify({
99
+ main: bundle.outfile,
100
+ output: blob,
101
+ disableExperimentalSEAWarning: true,
102
+ //The code cache of V8, made by the Node.js embedded: the bundle is not compiled again at start
103
+ useCodeCache: true,
104
+ useSnapshot: false,
105
+ assets: assets
106
+ }));
107
+ await must(process.execPath, ['--experimental-sea-config', 'sea-config.json'], work);
108
+ const { inject } = await import('postject');
109
+ await copyFile(process.execPath, file);
110
+ //The icon of the application replaces the one of node.exe, before the bundle is embedded
111
+ if (icon)
112
+ await setExecutableIcon(file, icon);
113
+ //The signature of macOS is removed before the binary is changed, then made again (ad hoc)
114
+ if (process.platform === 'darwin')
115
+ await must('codesign', ['--remove-signature', file], work);
116
+ await quiet(() => inject(file, 'NODE_SEA_BLOB', readFileSync(blob), { sentinelFuse: SEA_FUSE, machoSegmentName: process.platform === 'darwin' ? 'NODE_SEA' : undefined }));
117
+ if (process.platform === 'darwin')
118
+ await must('codesign', ['--sign', '-', '--force', file], work);
119
+ //The Authenticode signature of node.exe does not match the executable anymore
120
+ if (process.platform === 'win32')
121
+ warnings.push(`${path.basename(file)} has the signature of Node.js, which is not valid anymore: sign it (signtool) before distributing it`);
122
+ return `Node.js ${process.version}`;
123
+ }
124
+ /**
125
+ * Compile a bundle into an executable of Bun, in bytecode: the files of the native tree are embedded beside it, the
126
+ * executable gives their paths to the require of the bundle (BUN_NATIVE_ASSETS). As the executables of Node.js, it loads neither the .env
127
+ * nor the bunfig.toml of the directory it runs in; it reads the package.json of the packages it loads from the disk (the
128
+ * native tree, the modules left out)
129
+ * @param bun
130
+ * @param bundle
131
+ * @param work
132
+ * @param file
133
+ * @param icon the icon of the application (ico, Windows)
134
+ */
135
+ async function compileWithBun(bun, bundle, work, file, icon) {
136
+ let entry = bundle.outfile;
137
+ if (bundle.native) {
138
+ const { index, data } = packNativeTree(bundle.native);
139
+ await writeFile(path.join(work, 'native.gz'), data);
140
+ await writeFile(path.join(work, 'native.json'), JSON.stringify(index));
141
+ entry = path.join(work, 'main.mjs');
142
+ await writeFile(entry, [
143
+ `import data from './native.gz' with {type: 'file'}`,
144
+ `import index from './native.json' with {type: 'file'}`,
145
+ `globalThis[Symbol.for(${JSON.stringify(BUN_NATIVE_ASSETS)})] = {data, index}`,
146
+ //Not imported: the bytecode of Bun is the one of CommonJS modules, without top-level await
147
+ `require(${JSON.stringify(`./${path.basename(bundle.outfile)}`)})`
148
+ ].join('\n'));
149
+ }
150
+ await must(bun.command, [
151
+ 'build', entry, '--compile', '--outfile', file,
152
+ //Compiled to the bytecode of JavaScriptCore: it starts without parsing the bundle
153
+ '--bytecode',
154
+ '--compile-autoload-package-json', '--no-compile-autoload-dotenv', '--no-compile-autoload-bunfig',
155
+ //The icon of the application (Windows)
156
+ ...icon ? [`--windows-icon=${await writeIcon(work, icon)}`] : []
157
+ ], work);
158
+ return `Bun ${bun.version}`;
159
+ }
160
+ /**
161
+ * Make the executable of the application for the platform it is made on, which runs without Node.js installed: its bundle
162
+ * embedded in the Node.js binary running the tool (the single executable applications of Node.js), or compiled by Bun.
163
+ * The executables of the other platforms are made on them (their native addons are built there), as the applications are
164
+ * built there
165
+ * @param options
166
+ */
167
+ export async function buildExecutable(options) {
168
+ const project = path.resolve(options.project);
169
+ const outdir = path.resolve(project, options.outdir ?? 'dist');
170
+ //Bun is found before the application is bundled
171
+ const bun = options.runtime === 'bun' ? await findBun(project) : undefined;
172
+ const work = await mkdtemp(path.join(os.tmpdir(), 'lakutata-executable-'));
173
+ try {
174
+ const directory = options.layout === 'directory';
175
+ //The directory of the executable: dist/<name>, its native tree and its resources written there (the links of
176
+ //the tree are the ones of their directory), the one of the executable alone otherwise
177
+ const manifest = JSON.parse(await readFile(path.join(project, 'package.json'), 'utf8'));
178
+ const target = directory ? path.join(outdir, programName(manifest)) : outdir;
179
+ if (directory)
180
+ await rm(target, { recursive: true, force: true });
181
+ await mkdir(target, { recursive: true });
182
+ const bundle = await bundleProject({
183
+ ...options,
184
+ outfile: path.join(work, 'main.cjs'),
185
+ native: directory ? 'directory' : 'embedded',
186
+ directories: { native: path.join(target, 'native'), resources: path.join(target, 'resources') }
187
+ });
188
+ //The executables of Node.js do not load the native addons on musl (postject rewrites their symbols): the ones of Bun do
189
+ if (!bun && detectLibc() === 'musl' && bundle.native?.files.some((file) => file.path.endsWith('.node')))
190
+ throw new Error('The executables of Node.js do not load native addons on musl (Alpine): make the executable with Bun (--runtime bun, or "runtime": "bun" of lakutata.installer)');
191
+ const file = path.join(target, process.platform === 'win32' ? `${bundle.name}.exe` : bundle.name);
192
+ await rm(file, { force: true });
193
+ //The native tree and the resources of the layout directory are not embedded
194
+ const embedded = directory ? { ...bundle, native: undefined } : bundle;
195
+ //Made and signed aside: codesign takes a directory holding "resources" for a bundle of macOS (its file system
196
+ //ignores the case), whose signature it writes beside the executable (_CodeSignature)
197
+ const made = path.join(work, 'executable', path.basename(file));
198
+ await mkdir(path.dirname(made), { recursive: true });
199
+ const warnings = [];
200
+ //The icon of the application: the one of the executables of Windows (the ones of macOS and Linux have none)
201
+ const application = applicationIcon(project, manifest);
202
+ const icon = application && process.platform === 'win32' ? icoFile(application.image) : undefined;
203
+ const runtime = bun ? await compileWithBun(bun, embedded, work, made, icon) : await embedInNode(embedded, work, made, warnings, icon);
204
+ await copyFile(made, file);
205
+ await chmod(file, 0o755);
206
+ return {
207
+ name: bundle.name,
208
+ file: file,
209
+ platform: `${process.platform === 'win32' ? 'win' : process.platform}-${process.arch}`,
210
+ size: (await stat(file)).size,
211
+ native: bundle.native?.modules ?? [],
212
+ resources: bundle.resources,
213
+ directory: directory ? target : undefined,
214
+ runtime: runtime,
215
+ warnings: warnings
216
+ };
217
+ }
218
+ finally {
219
+ await rm(work, { recursive: true, force: true });
220
+ }
221
+ }