@lakutata/builder 3.0.0-beta.4 → 3.0.0-beta.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.
- package/README.md +4 -3
- package/dist/esm/lib/Bundle.js +31 -14
- package/dist/esm/lib/Executable.js +1 -0
- package/dist/esm/lib/{Workers.js → Files.js} +109 -28
- package/dist/esm/lib/Native.js +32 -2
- package/dist/esm/lib/NativeRuntime.js +5 -5
- package/dist/esm/lib/electron/Electron.js +1 -0
- package/dist/types/lib/Bundle.d.ts +7 -1
- package/dist/types/lib/Executable.d.ts +5 -0
- package/dist/types/lib/{Workers.d.ts → Files.d.ts} +35 -11
- package/dist/types/lib/Native.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,9 +17,10 @@ await buildInstaller({project: process.cwd()})
|
|
|
17
17
|
|
|
18
18
|
The application is compiled first (`tsc`, which emits the metadata of the decorators). The bundle is one CommonJS file
|
|
19
19
|
(esbuild); the native addons, which cannot be bundled, are gathered with what they load at run time into a native tree,
|
|
20
|
-
with the
|
|
21
|
-
`fork()`, `require(path.join(__dirname, 'x.js'))
|
|
22
|
-
the paths
|
|
20
|
+
with the files the application reads by their paths, which the bundler does not follow (the scripts it runs on their
|
|
21
|
+
own: the workers, the pools of workers, `fork()`, `require(path.join(__dirname, 'x.js'))`, bundled; its data files and
|
|
22
|
+
directories, copied: the paths given from their modules are found and rewritten; what cannot be shipped is in the
|
|
23
|
+
`warnings` of the result);
|
|
23
24
|
the resources of the application (the `lakutata.resources` of its `package.json`) are found at run time by
|
|
24
25
|
`resourcesPath()` of lakutata. The installers are configured by the `lakutata.installer` of the `package.json`. See the
|
|
25
26
|
documentation of `lakutata package bundle`, `lakutata package exe` and `lakutata package installer` in `@lakutata/cli`.
|
package/dist/esm/lib/Bundle.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
2
2
|
import { chmod, cp, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { hashNativeTree, NativeCollector, writeNativeTree } from './Native.js';
|
|
5
5
|
import { EMBEDDED_RESOURCES, nativeBanner, packagedBanner } from './NativeRuntime.js';
|
|
6
|
-
import {
|
|
6
|
+
import { FILES_DIRECTORY, FileCollector } from './Files.js';
|
|
7
7
|
/**
|
|
8
8
|
* The name of the program of a package: its first bin, or the name of the package without its scope
|
|
9
9
|
* @param manifest
|
|
@@ -58,6 +58,18 @@ export function resourceFiles(project, manifest) {
|
|
|
58
58
|
}
|
|
59
59
|
return { declared: declarations.map(({ to }) => to), files: files };
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* A package.json copied into the tree, its type CommonJS; undefined when it is not JSON (copied as it is)
|
|
63
|
+
* @param file
|
|
64
|
+
*/
|
|
65
|
+
function commonJSManifest(file) {
|
|
66
|
+
try {
|
|
67
|
+
return Buffer.from(`${JSON.stringify({ ...JSON.parse(readFileSync(file, 'utf8')), type: 'commonjs' }, null, 2)}\n`);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
61
73
|
/**
|
|
62
74
|
* The code with a code before it, after its hashbang
|
|
63
75
|
* @param code
|
|
@@ -86,7 +98,7 @@ export async function bundleProject(options) {
|
|
|
86
98
|
throw new Error(`The entry ${path.relative(process.cwd(), entry) || entry} does not exist: build the project first`);
|
|
87
99
|
const outfile = path.resolve(project, options.outfile ?? path.join('dist', `${name}.cjs`));
|
|
88
100
|
const collector = new NativeCollector(project, options.runtime);
|
|
89
|
-
const
|
|
101
|
+
const files = new FileCollector(project);
|
|
90
102
|
//esbuild is loaded by the commands which need it
|
|
91
103
|
const { build } = await import('esbuild');
|
|
92
104
|
//The application, and each script of its workers (the bundler does not follow new Worker()): their native packages
|
|
@@ -113,7 +125,7 @@ export async function bundleProject(options) {
|
|
|
113
125
|
//Bun replaces process.env.NODE_ENV with the mode of its build: the one of the run is read
|
|
114
126
|
...options.runtime === 'bun' ? { 'process.env.NODE_ENV': 'globalThis.process.env.NODE_ENV' } : {}
|
|
115
127
|
},
|
|
116
|
-
plugins: [collector.plugin([(source, file) =>
|
|
128
|
+
plugins: [collector.plugin([(source, file) => files.transform(source, file)])],
|
|
117
129
|
logLevel: 'silent'
|
|
118
130
|
}).catch((error) => {
|
|
119
131
|
const messages = (error.errors ?? []).map(({ text, location }) => location ? `${location.file}:${location.line}: ${text}` : text);
|
|
@@ -135,34 +147,38 @@ export async function bundleProject(options) {
|
|
|
135
147
|
const resourcesDirectory = path.resolve(path.dirname(outfile), options.directories?.resources ?? `${base}.resources`);
|
|
136
148
|
//The scripts of the workers, until the ones they start are bundled too
|
|
137
149
|
const scripts = new Map();
|
|
138
|
-
for (let pending = [...
|
|
150
|
+
for (let pending = [...files.scripts]; pending.length; pending = [...files.scripts].filter(([at]) => !scripts.has(at))) {
|
|
139
151
|
for (const [at, script] of pending)
|
|
140
152
|
scripts.set(at, (await bundle(script, path.join(nativeDirectory, ...at.split('/')))).code);
|
|
141
153
|
}
|
|
142
|
-
|
|
154
|
+
files.warnBundled(application.inputs);
|
|
143
155
|
const embedded = options.native === 'embedded';
|
|
144
156
|
let native = await collector.collect();
|
|
145
157
|
const resources = resourceFiles(project, manifest);
|
|
146
|
-
if (scripts.size) {
|
|
158
|
+
if (scripts.size || files.files.size) {
|
|
147
159
|
//The scripts run from the tree: their require loads the native packages from it, their resources are the ones of
|
|
148
160
|
//the application (an executable gives them from the tree it extracted)
|
|
149
161
|
const modules = native?.modules ?? [];
|
|
150
|
-
const
|
|
162
|
+
const tree = [{ path: `${FILES_DIRECTORY}/package.json`, source: manifestFile, mode: 0o644, content: Buffer.from('{"type": "commonjs"}\n') }];
|
|
151
163
|
for (const [at, script] of [...scripts].sort(([a], [b]) => a.localeCompare(b))) {
|
|
152
164
|
const directory = path.dirname(path.join(nativeDirectory, ...at.split('/')));
|
|
153
165
|
const banner = packagedBanner(path.relative(directory, resourcesDirectory).split(path.sep).join('/')) +
|
|
154
166
|
nativeBanner({ name: name, hash: '', directory: '', modules: modules, embedded: embedded, root: path.relative(directory, nativeDirectory).split(path.sep).join('/') });
|
|
155
|
-
|
|
167
|
+
tree.push({ path: at, source: files.scripts.get(at), mode: 0o644, content: Buffer.from(prepend(script, banner)) });
|
|
156
168
|
}
|
|
157
|
-
|
|
169
|
+
//The other files the application reads by their paths, as they are; a package.json read by its package (its
|
|
170
|
+
//version) is of the CommonJS scripts of the tree next to it (its type would make them ES modules)
|
|
171
|
+
for (const [at, data] of files.files)
|
|
172
|
+
tree.push({ path: at, source: data, mode: statSync(data).mode & 0o777, content: path.basename(data) === 'package.json' ? commonJSManifest(data) : undefined });
|
|
173
|
+
const all = [...native?.files ?? [], ...tree];
|
|
158
174
|
native = { modules: modules, files: all, links: native?.links ?? [], hash: hashNativeTree(all, native?.links ?? []) };
|
|
159
175
|
}
|
|
160
176
|
await rm(nativeDirectory, { recursive: true, force: true });
|
|
161
177
|
await rm(resourcesDirectory, { recursive: true, force: true });
|
|
162
178
|
//The resources of an executable are extracted with its native tree
|
|
163
179
|
if (embedded && resources.files.length) {
|
|
164
|
-
const
|
|
165
|
-
native = { modules: native?.modules ?? [], files:
|
|
180
|
+
const embeddedFiles = [...native?.files ?? [], ...resources.files.map((file) => ({ ...file, path: `${EMBEDDED_RESOURCES}/${file.path}` }))];
|
|
181
|
+
native = { modules: native?.modules ?? [], files: embeddedFiles, links: native?.links ?? [], hash: hashNativeTree(embeddedFiles, native?.links ?? []) };
|
|
166
182
|
}
|
|
167
183
|
if (native && embedded) {
|
|
168
184
|
//An executable runs the scripts of its tree it is started for (fork()) instead of the application
|
|
@@ -198,7 +214,8 @@ export async function bundleProject(options) {
|
|
|
198
214
|
nativeDirectory: native && !embedded ? nativeDirectory : undefined,
|
|
199
215
|
resources: resources.declared,
|
|
200
216
|
resourcesDirectory: resources.files.length && !embedded ? resourcesDirectory : undefined,
|
|
201
|
-
workers: [...new Set([...scripts.keys()].map((at) =>
|
|
202
|
-
|
|
217
|
+
workers: [...new Set([...scripts.keys()].map((at) => files.nameOf(files.scripts.get(at))))].sort(),
|
|
218
|
+
files: [...files.roots].filter((root) => !files.scriptPaths().has(root)).map((root) => files.nameOf(root)).sort(),
|
|
219
|
+
warnings: files.warnings
|
|
203
220
|
};
|
|
204
221
|
}
|
|
@@ -210,6 +210,7 @@ export async function buildExecutable(options) {
|
|
|
210
210
|
size: (await stat(file)).size,
|
|
211
211
|
native: bundle.native?.modules ?? [],
|
|
212
212
|
workers: bundle.workers,
|
|
213
|
+
files: bundle.files,
|
|
213
214
|
resources: bundle.resources,
|
|
214
215
|
directory: directory ? target : undefined,
|
|
215
216
|
runtime: runtime,
|
|
@@ -4,13 +4,40 @@ import { createRequire } from 'node:module';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { parse } from 'acorn';
|
|
6
6
|
/**
|
|
7
|
-
* The directory of the
|
|
7
|
+
* The directory of the files the application reads by their paths in the native tree: the scripts it runs on their own
|
|
8
|
+
* (bundled), the other files and the directories of data (copied), their directories as in the project
|
|
8
9
|
*/
|
|
9
|
-
export const
|
|
10
|
+
export const FILES_DIRECTORY = '.lakutata/files';
|
|
10
11
|
/**
|
|
11
12
|
* The scripts run on their own: the CommonJS scripts, which they are bundled as
|
|
12
13
|
*/
|
|
13
14
|
const SCRIPT = /\.c?js$/;
|
|
15
|
+
/**
|
|
16
|
+
* The code a directory of data must not hold: the scripts, which need their packages, and the packages
|
|
17
|
+
*/
|
|
18
|
+
const CODE = /\.(c|m)?js$|^package\.json$|^node_modules$/;
|
|
19
|
+
/**
|
|
20
|
+
* The files of a directory, deep
|
|
21
|
+
* @param directory
|
|
22
|
+
*/
|
|
23
|
+
function filesOf(directory) {
|
|
24
|
+
const files = [];
|
|
25
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
26
|
+
const at = path.join(directory, entry.name);
|
|
27
|
+
if (entry.isDirectory())
|
|
28
|
+
files.push(...filesOf(at));
|
|
29
|
+
else if (entry.isFile())
|
|
30
|
+
files.push(at);
|
|
31
|
+
}
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether a directory holds code: scripts, packages
|
|
36
|
+
* @param directory
|
|
37
|
+
*/
|
|
38
|
+
function holdsCode(directory) {
|
|
39
|
+
return readdirSync(directory, { withFileTypes: true }).some((entry) => CODE.test(entry.name) || (entry.isDirectory() && holdsCode(path.join(directory, entry.name))));
|
|
40
|
+
}
|
|
14
41
|
/**
|
|
15
42
|
* The modules of the worker threads
|
|
16
43
|
*/
|
|
@@ -254,13 +281,13 @@ function templateText(text) {
|
|
|
254
281
|
* workers (new Worker(), the pools of workers given a file), of its child processes (fork()), the modules it loads by
|
|
255
282
|
* their paths (require(path.join(__dirname, 'x.js'))), in its modules and in the ones of its packages. A path to a script
|
|
256
283
|
* given from its module, wherever it is written, is found when the module is bundled: the script is bundled on its own
|
|
257
|
-
* into the native tree (
|
|
284
|
+
* into the native tree (FILES_DIRECTORY, the directories mirrored), and the expression rewritten to it. A path known at
|
|
258
285
|
* run time only in part (a template, a concatenation, an argument of path.join: `${name}.js`) gives every script of its
|
|
259
286
|
* directory its pattern matches, the expression of the part kept. The scripts of the workers of node (worker_threads)
|
|
260
287
|
* whose paths cannot be followed are warned of, in the modules of the application (the packages start workers from the
|
|
261
288
|
* paths their users give)
|
|
262
289
|
*/
|
|
263
|
-
export class
|
|
290
|
+
export class FileCollector {
|
|
264
291
|
/**
|
|
265
292
|
* @param project The directory of the project: the scripts are named from it (as the modules the bundler gives, its
|
|
266
293
|
* real path)
|
|
@@ -270,6 +297,14 @@ export class WorkerCollector {
|
|
|
270
297
|
* The scripts: their paths in the native tree, and their files
|
|
271
298
|
*/
|
|
272
299
|
this.scripts = new Map();
|
|
300
|
+
/**
|
|
301
|
+
* The other files: their paths in the native tree, and their files
|
|
302
|
+
*/
|
|
303
|
+
this.files = new Map();
|
|
304
|
+
/**
|
|
305
|
+
* The files and the directories of data the paths name
|
|
306
|
+
*/
|
|
307
|
+
this.roots = new Set();
|
|
273
308
|
this.warnings = [];
|
|
274
309
|
this.project = realpathSync(project);
|
|
275
310
|
}
|
|
@@ -281,14 +316,18 @@ export class WorkerCollector {
|
|
|
281
316
|
*/
|
|
282
317
|
keyOf(directory) {
|
|
283
318
|
const relative = path.relative(this.project, directory);
|
|
284
|
-
if (!relative)
|
|
285
|
-
return '_';
|
|
286
319
|
if (!relative.startsWith('..'))
|
|
287
320
|
return relative.split(path.sep).join('/');
|
|
288
321
|
const segments = directory.split(path.sep);
|
|
289
322
|
const modules = segments.indexOf('node_modules');
|
|
290
323
|
return modules >= 0 ? segments.slice(modules).join('/') : `_outside/${createHash('sha256').update(directory).digest('hex').slice(0, 16)}`;
|
|
291
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* The files of the scripts
|
|
327
|
+
*/
|
|
328
|
+
scriptPaths() {
|
|
329
|
+
return new Set(this.scripts.values());
|
|
330
|
+
}
|
|
292
331
|
/**
|
|
293
332
|
* The name of a script: its path from the project ("/" separated), or from the node_modules holding it outside of the
|
|
294
333
|
* project (the packages of a workspace)
|
|
@@ -301,12 +340,12 @@ export class WorkerCollector {
|
|
|
301
340
|
return relative.startsWith('..') && modules >= 0 ? segments.slice(modules).join('/') : relative.split(path.sep).join('/');
|
|
302
341
|
}
|
|
303
342
|
/**
|
|
304
|
-
*
|
|
305
|
-
* from it
|
|
343
|
+
* What a path gives: the file or the directory, or the ones its pattern matches (an expression matches a part of a
|
|
344
|
+
* name); the directory they are named from, the parts of their paths from it
|
|
306
345
|
* @param found
|
|
307
346
|
* @param file the module
|
|
308
347
|
*/
|
|
309
|
-
|
|
348
|
+
targetsOf(found, file) {
|
|
310
349
|
//The directory: the texts before the first expression, up to their last separator
|
|
311
350
|
const firstExpression = found.parts.findIndex((part) => 'expression' in part);
|
|
312
351
|
const leading = firstExpression < 0 ? found.parts : found.parts.slice(0, firstExpression);
|
|
@@ -314,27 +353,55 @@ export class WorkerCollector {
|
|
|
314
353
|
const cut = prefix.lastIndexOf('/');
|
|
315
354
|
const directory = path.resolve(path.join(found.base ?? path.dirname(file), prefix.slice(0, cut + 1)));
|
|
316
355
|
const rest = [...prefix.slice(cut + 1) ? [{ text: prefix.slice(cut + 1) }] : [], ...found.parts.slice(leading.length)];
|
|
356
|
+
const targets = { directory: directory, rest: rest, scripts: [], files: [], roots: [] };
|
|
357
|
+
//A file: bundled when it is a script, copied otherwise; a directory of data: its files copied
|
|
358
|
+
const take = (at, strict) => {
|
|
359
|
+
const stats = statSync(at);
|
|
360
|
+
if (stats.isFile()) {
|
|
361
|
+
(SCRIPT.test(at) ? targets.scripts : targets.files).push(at);
|
|
362
|
+
targets.roots.push(at);
|
|
363
|
+
}
|
|
364
|
+
else if (stats.isDirectory()) {
|
|
365
|
+
//A directory holding the module (the application, its packages), or holding code: not shipped
|
|
366
|
+
if ((file + path.sep).startsWith(at + path.sep)) {
|
|
367
|
+
if (strict)
|
|
368
|
+
targets.refused = 'ancestor';
|
|
369
|
+
}
|
|
370
|
+
else if (holdsCode(at)) {
|
|
371
|
+
if (strict)
|
|
372
|
+
targets.refused = 'code';
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
targets.files.push(...filesOf(at));
|
|
376
|
+
targets.roots.push(at);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
317
380
|
if (firstExpression < 0) {
|
|
318
|
-
const
|
|
319
|
-
|
|
381
|
+
const target = path.join(directory, prefix.slice(cut + 1));
|
|
382
|
+
if (existsSync(target))
|
|
383
|
+
take(target, true);
|
|
384
|
+
return targets;
|
|
320
385
|
}
|
|
321
|
-
//The pattern: an expression matches a part of a segment
|
|
322
386
|
const pattern = new RegExp(`^${rest.map((part) => 'text' in part ? part.text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '[^/]*').join('')}$`);
|
|
323
387
|
const depth = rest.map((part) => 'text' in part ? part.text : '').join('').split('/').length;
|
|
324
|
-
const files = [];
|
|
325
388
|
const search = (at, relative, level) => {
|
|
326
389
|
if (level > depth || !existsSync(at))
|
|
327
390
|
return;
|
|
328
391
|
for (const entry of readdirSync(at, { withFileTypes: true })) {
|
|
329
392
|
const name = relative ? `${relative}/${entry.name}` : entry.name;
|
|
330
|
-
if (entry.
|
|
393
|
+
if (entry.name === 'node_modules')
|
|
394
|
+
continue;
|
|
395
|
+
if (pattern.test(name) && (entry.isFile() || entry.isDirectory()))
|
|
396
|
+
take(path.join(at, entry.name), false);
|
|
397
|
+
else if (entry.isDirectory())
|
|
331
398
|
search(path.join(at, entry.name), name, level + 1);
|
|
332
|
-
else if (entry.isFile() && SCRIPT.test(entry.name) && pattern.test(name))
|
|
333
|
-
files.push(path.join(at, entry.name));
|
|
334
399
|
}
|
|
335
400
|
};
|
|
336
401
|
search(directory, '', 1);
|
|
337
|
-
|
|
402
|
+
targets.scripts.sort();
|
|
403
|
+
targets.files.sort();
|
|
404
|
+
return targets;
|
|
338
405
|
}
|
|
339
406
|
/**
|
|
340
407
|
* Warn of a worker in a module of the application
|
|
@@ -417,24 +484,38 @@ export class WorkerCollector {
|
|
|
417
484
|
if (node.type === 'AssignmentExpression' && node.operator === '=' && node.left.type === 'MemberExpression' && node.left.object.type === 'ThisExpression' && !node.left.computed)
|
|
418
485
|
remember(members, node.left.property.name, node.right);
|
|
419
486
|
});
|
|
420
|
-
//The paths to
|
|
487
|
+
//The paths to files, wherever they are written
|
|
421
488
|
const edits = [];
|
|
422
489
|
const followed = new Set();
|
|
423
490
|
walk(program, (node) => {
|
|
424
491
|
const found = scriptPathOf(node, source, file);
|
|
425
492
|
if (!found)
|
|
426
493
|
return true;
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
if (
|
|
494
|
+
const targets = this.targetsOf(found, file);
|
|
495
|
+
const written = source.slice(node.start, node.end);
|
|
496
|
+
if (targets.refused === 'ancestor') {
|
|
497
|
+
this.warn(file, node.loc.start.line, `the path (${written}) is a directory holding the module, which is not where it is once bundled (the bundle is elsewhere, the code and the packages of the application are not shipped): declare the files it reads as resources (lakutata.resources) and read them from resourcesPath()`);
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
if (targets.refused === 'code') {
|
|
501
|
+
this.warn(file, node.loc.start.line, `the path (${written}) is a directory of scripts or of packages, which need their packages at run time: it is not shipped with the bundle — import its scripts, or declare it as a resource (lakutata.resources) with what it loads`);
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
//A path to nothing yet (a directory the application creates): left as it is
|
|
505
|
+
if (!targets.scripts.length && !targets.files.length)
|
|
430
506
|
return true;
|
|
431
|
-
const key = this.keyOf(directory);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
507
|
+
const key = this.keyOf(targets.directory);
|
|
508
|
+
const at = (target) => [FILES_DIRECTORY, key, path.relative(targets.directory, target).split(path.sep).join('/')].filter(Boolean).join('/');
|
|
509
|
+
for (const script of targets.scripts)
|
|
510
|
+
this.scripts.set(at(script), script);
|
|
511
|
+
for (const data of targets.files)
|
|
512
|
+
this.files.set(at(data), data);
|
|
513
|
+
for (const root of targets.roots)
|
|
514
|
+
this.roots.add(root);
|
|
515
|
+
const relative = `\`${targets.rest.map((part) => 'text' in part ? templateText(part.text) : `\${${part.expression}}`).join('')}\``;
|
|
516
|
+
const text = found.kind === 'url' ? `new URL(${relative}, require.lakutataFilesURL(${JSON.stringify(key)}))` :
|
|
517
|
+
found.kind === 'url-text' ? `(require.lakutataFilesURL(${JSON.stringify(key)}) + ${relative})` :
|
|
518
|
+
`require("node:path").join(require.lakutataFiles(${JSON.stringify(key)}), ${relative})`;
|
|
438
519
|
followed.add(node);
|
|
439
520
|
edits.push({ start: node.start, end: node.end, text: text });
|
|
440
521
|
return false;
|
package/dist/esm/lib/Native.js
CHANGED
|
@@ -11,6 +11,16 @@ import { gzipSync } from 'node:zlib';
|
|
|
11
11
|
function contentOf(file) {
|
|
12
12
|
return file.content ?? readFileSync(file.source);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether a package is the framework, or built on it (it depends on it)
|
|
16
|
+
* @param manifest
|
|
17
|
+
*/
|
|
18
|
+
function sharesFramework(manifest) {
|
|
19
|
+
const name = String(manifest.name ?? '');
|
|
20
|
+
if (name === 'lakutata' || name.startsWith('@lakutata/'))
|
|
21
|
+
return true;
|
|
22
|
+
return ['dependencies', 'peerDependencies', 'optionalDependencies'].some((field) => !!manifest[field] && ('lakutata' in manifest[field] || '@lakutata/core' in manifest[field]));
|
|
23
|
+
}
|
|
14
24
|
/**
|
|
15
25
|
* The shared libraries: the addons, and the libraries they are linked with (sharp and libvips)
|
|
16
26
|
*/
|
|
@@ -221,7 +231,13 @@ export class NativeCollector {
|
|
|
221
231
|
const info = this.package(root);
|
|
222
232
|
if (!info)
|
|
223
233
|
return false;
|
|
224
|
-
|
|
234
|
+
if (info.addons)
|
|
235
|
+
return true;
|
|
236
|
+
//A package of the framework, or built on it, shares it with the application: bundled with it (one copy of its
|
|
237
|
+
//classes, its container), its native dependencies loaded by their names, which the bundler follows
|
|
238
|
+
if (sharesFramework(info.manifest))
|
|
239
|
+
return false;
|
|
240
|
+
return this.dependencies(root).some(({ root: dependency }) => !!this.package(dependency)?.addons);
|
|
225
241
|
}
|
|
226
242
|
/**
|
|
227
243
|
* Resolve an import as esbuild does
|
|
@@ -232,6 +248,18 @@ export class NativeCollector {
|
|
|
232
248
|
const result = await build.resolve(args.path, { kind: args.kind, resolveDir: args.resolveDir, importer: args.importer, pluginData: { [RESOLVING]: true } });
|
|
233
249
|
return result.errors.length || result.external || !result.path ? undefined : realpathSync(result.path);
|
|
234
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Whether a package is an optional dependency of the package of a module: an optional peer dependency
|
|
253
|
+
* (peerDependenciesMeta) or an optional dependency
|
|
254
|
+
* @param specifier
|
|
255
|
+
* @param importer
|
|
256
|
+
*/
|
|
257
|
+
isOptionalOf(specifier, importer) {
|
|
258
|
+
const name = specifierPackage(specifier);
|
|
259
|
+
const root = name && importer ? this.packageRoot(existsSync(importer) ? realpathSync(importer) : importer) : undefined;
|
|
260
|
+
const manifest = root ? this.package(root)?.manifest : undefined;
|
|
261
|
+
return !!name && !!manifest && (!!manifest.peerDependenciesMeta?.[name]?.optional || !!manifest.optionalDependencies?.[name]);
|
|
262
|
+
}
|
|
235
263
|
/**
|
|
236
264
|
* The plugin of the bundle: the native packages are external, the addons of the application are loaded from the tree
|
|
237
265
|
* @param transforms the transformations of the sources of the modules (the paths of the workers), giving their new
|
|
@@ -245,8 +273,10 @@ export class NativeCollector {
|
|
|
245
273
|
if (args.pluginData?.[RESOLVING] || isBuiltin(args.path) || args.kind === 'entry-point')
|
|
246
274
|
return undefined;
|
|
247
275
|
const file = await this.resolve(build, args);
|
|
276
|
+
//An optional dependency of the package importing it, not installed: left out, it throws when it is
|
|
277
|
+
//loaded at run time, as without the bundle (the drivers of the stores the application does not use)
|
|
248
278
|
if (!file)
|
|
249
|
-
return undefined;
|
|
279
|
+
return this.isOptionalOf(args.path, args.importer) ? { path: args.path, external: true } : undefined;
|
|
250
280
|
const root = this.packageRoot(file);
|
|
251
281
|
if (this.isNative(root)) {
|
|
252
282
|
const name = specifierPackage(args.path);
|
|
@@ -137,16 +137,16 @@ export function lakutataNativeRequire(nodeRequire, config) {
|
|
|
137
137
|
process.dlopen(addon, path.join(locate(), ...key.split('/')));
|
|
138
138
|
return addon.exports;
|
|
139
139
|
};
|
|
140
|
-
//The
|
|
141
|
-
const
|
|
142
|
-
require.
|
|
143
|
-
require.
|
|
140
|
+
//The files the application reads by their paths, in the tree (FILES_DIRECTORY): the directory of a key, its URL
|
|
141
|
+
const files = (key) => path.join(locate(), '.lakutata', 'files', ...key.split('/'));
|
|
142
|
+
require.lakutataFiles = files;
|
|
143
|
+
require.lakutataFilesURL = (key) => nodeRequire('node:url').pathToFileURL(files(key) + path.sep).href;
|
|
144
144
|
//An executable started to run a script of its tree (fork(), spawn(process.execPath, [script]): process.execPath is the
|
|
145
145
|
//executable): it runs the script as node runs it, the application not started
|
|
146
146
|
require.lakutataScript = () => {
|
|
147
147
|
if (!config.embedded || config.root !== undefined)
|
|
148
148
|
return false;
|
|
149
|
-
const scripts = path.join(locate(), '.lakutata', '
|
|
149
|
+
const scripts = path.join(locate(), '.lakutata', 'files') + path.sep;
|
|
150
150
|
const at = [1, 2].find((index) => !!process.argv[index] && path.resolve(process.argv[index]).startsWith(scripts)) ?? -1;
|
|
151
151
|
if (at < 0)
|
|
152
152
|
return false;
|
|
@@ -356,6 +356,7 @@ export async function buildElectronInstaller(project, manifest, config, options)
|
|
|
356
356
|
size: bundle.size,
|
|
357
357
|
native: bundle.native?.modules ?? [],
|
|
358
358
|
workers: bundle.workers,
|
|
359
|
+
files: bundle.files,
|
|
359
360
|
resources: bundle.resources,
|
|
360
361
|
runtime: `Electron v${electronVersion}`,
|
|
361
362
|
warnings: []
|
|
@@ -75,7 +75,13 @@ export type BundleResult = {
|
|
|
75
75
|
*/
|
|
76
76
|
workers: string[];
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* The other files and the directories of data the application reads by their paths (from the project), copied into
|
|
79
|
+
* the native tree
|
|
80
|
+
*/
|
|
81
|
+
files: string[];
|
|
82
|
+
/**
|
|
83
|
+
* What the bundle cannot follow: the scripts of the workers given by paths known at run time only, the directories
|
|
84
|
+
* which cannot be shipped (holding the modules, holding code), the scripts bundled twice
|
|
79
85
|
*/
|
|
80
86
|
warnings: string[];
|
|
81
87
|
};
|
|
@@ -27,6 +27,11 @@ export type ExecutableResult = {
|
|
|
27
27
|
* The scripts of the workers of the application (their files, from the project), with the native addons
|
|
28
28
|
*/
|
|
29
29
|
workers: string[];
|
|
30
|
+
/**
|
|
31
|
+
* The other files and the directories of data the application reads by their paths (from the project), with the
|
|
32
|
+
* native addons
|
|
33
|
+
*/
|
|
34
|
+
files: string[];
|
|
30
35
|
/**
|
|
31
36
|
* The resources of the application (their paths in the resources)
|
|
32
37
|
*/
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The directory of the
|
|
2
|
+
* The directory of the files the application reads by their paths in the native tree: the scripts it runs on their own
|
|
3
|
+
* (bundled), the other files and the directories of data (copied), their directories as in the project
|
|
3
4
|
*/
|
|
4
|
-
export declare const
|
|
5
|
+
export declare const FILES_DIRECTORY: string;
|
|
5
6
|
/**
|
|
6
7
|
* A piece of a path to a script: a text, or an expression known at run time only (the source of the expression)
|
|
7
8
|
*/
|
|
@@ -26,22 +27,45 @@ type ScriptPath = {
|
|
|
26
27
|
*/
|
|
27
28
|
base?: string;
|
|
28
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* What a path from a module gives: the directory of its pattern, the parts from it, the scripts to bundle and the files
|
|
32
|
+
* to copy (the files of its directories of data), or why it cannot be shipped
|
|
33
|
+
*/
|
|
34
|
+
type Targets = {
|
|
35
|
+
directory: string;
|
|
36
|
+
rest: Part[];
|
|
37
|
+
scripts: string[];
|
|
38
|
+
files: string[];
|
|
39
|
+
/**
|
|
40
|
+
* The files and the directories it names (the roots of the files)
|
|
41
|
+
*/
|
|
42
|
+
roots: string[];
|
|
43
|
+
refused?: 'ancestor' | 'code';
|
|
44
|
+
};
|
|
29
45
|
/**
|
|
30
46
|
* The scripts an application runs on its own, by their paths, which the bundler does not follow: the scripts of its
|
|
31
47
|
* workers (new Worker(), the pools of workers given a file), of its child processes (fork()), the modules it loads by
|
|
32
48
|
* their paths (require(path.join(__dirname, 'x.js'))), in its modules and in the ones of its packages. A path to a script
|
|
33
49
|
* given from its module, wherever it is written, is found when the module is bundled: the script is bundled on its own
|
|
34
|
-
* into the native tree (
|
|
50
|
+
* into the native tree (FILES_DIRECTORY, the directories mirrored), and the expression rewritten to it. A path known at
|
|
35
51
|
* run time only in part (a template, a concatenation, an argument of path.join: `${name}.js`) gives every script of its
|
|
36
52
|
* directory its pattern matches, the expression of the part kept. The scripts of the workers of node (worker_threads)
|
|
37
53
|
* whose paths cannot be followed are warned of, in the modules of the application (the packages start workers from the
|
|
38
54
|
* paths their users give)
|
|
39
55
|
*/
|
|
40
|
-
export declare class
|
|
56
|
+
export declare class FileCollector {
|
|
41
57
|
/**
|
|
42
58
|
* The scripts: their paths in the native tree, and their files
|
|
43
59
|
*/
|
|
44
60
|
readonly scripts: Map<string, string>;
|
|
61
|
+
/**
|
|
62
|
+
* The other files: their paths in the native tree, and their files
|
|
63
|
+
*/
|
|
64
|
+
readonly files: Map<string, string>;
|
|
65
|
+
/**
|
|
66
|
+
* The files and the directories of data the paths name
|
|
67
|
+
*/
|
|
68
|
+
readonly roots: Set<string>;
|
|
45
69
|
readonly warnings: string[];
|
|
46
70
|
protected readonly project: string;
|
|
47
71
|
/**
|
|
@@ -56,6 +80,10 @@ export declare class WorkerCollector {
|
|
|
56
80
|
* @param directory
|
|
57
81
|
*/
|
|
58
82
|
protected keyOf(directory: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* The files of the scripts
|
|
85
|
+
*/
|
|
86
|
+
scriptPaths(): Set<string>;
|
|
59
87
|
/**
|
|
60
88
|
* The name of a script: its path from the project ("/" separated), or from the node_modules holding it outside of the
|
|
61
89
|
* project (the packages of a workspace)
|
|
@@ -63,16 +91,12 @@ export declare class WorkerCollector {
|
|
|
63
91
|
*/
|
|
64
92
|
nameOf(script: string): string;
|
|
65
93
|
/**
|
|
66
|
-
*
|
|
67
|
-
* from it
|
|
94
|
+
* What a path gives: the file or the directory, or the ones its pattern matches (an expression matches a part of a
|
|
95
|
+
* name); the directory they are named from, the parts of their paths from it
|
|
68
96
|
* @param found
|
|
69
97
|
* @param file the module
|
|
70
98
|
*/
|
|
71
|
-
protected
|
|
72
|
-
directory: string;
|
|
73
|
-
rest: Part[];
|
|
74
|
-
files: string[];
|
|
75
|
-
};
|
|
99
|
+
protected targetsOf(found: ScriptPath, file: string): Targets;
|
|
76
100
|
/**
|
|
77
101
|
* Warn of a worker in a module of the application
|
|
78
102
|
* @param file
|
|
@@ -136,6 +136,13 @@ export declare class NativeCollector {
|
|
|
136
136
|
resolveDir: string;
|
|
137
137
|
importer: string;
|
|
138
138
|
}): Promise<string | undefined>;
|
|
139
|
+
/**
|
|
140
|
+
* Whether a package is an optional dependency of the package of a module: an optional peer dependency
|
|
141
|
+
* (peerDependenciesMeta) or an optional dependency
|
|
142
|
+
* @param specifier
|
|
143
|
+
* @param importer
|
|
144
|
+
*/
|
|
145
|
+
protected isOptionalOf(specifier: string, importer: string): boolean;
|
|
139
146
|
/**
|
|
140
147
|
* The plugin of the bundle: the native packages are external, the addons of the application are loaded from the tree
|
|
141
148
|
* @param transforms the transformations of the sources of the modules (the paths of the workers), giving their new
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lakutata/builder",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.6",
|
|
4
4
|
"description": "The builder of the applications of the lakutata framework: their bundles, their executables (Node.js, Bun) with their native addons and their resources.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/esm/index.js",
|