@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 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 scripts run on their own by their paths, which the bundler does not follow (the workers, the pools of workers,
21
- `fork()`, `require(path.join(__dirname, 'x.js'))`: the paths given from their modules are found, the scripts bundled,
22
- the paths rewritten; the workers whose scripts cannot be followed are in the `warnings` of the result);
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`.
@@ -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 { WORKERS_DIRECTORY, WorkerCollector } from './Workers.js';
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 workers = new WorkerCollector(project);
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) => workers.transform(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 = [...workers.scripts]; pending.length; pending = [...workers.scripts].filter(([at]) => !scripts.has(at))) {
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
- workers.warnBundled(application.inputs);
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 files = [{ path: `${WORKERS_DIRECTORY}/package.json`, source: manifestFile, mode: 0o644, content: Buffer.from('{"type": "commonjs"}\n') }];
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
- files.push({ path: at, source: workers.scripts.get(at), mode: 0o644, content: Buffer.from(prepend(script, banner)) });
167
+ tree.push({ path: at, source: files.scripts.get(at), mode: 0o644, content: Buffer.from(prepend(script, banner)) });
156
168
  }
157
- const all = [...native?.files ?? [], ...files];
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 files = [...native?.files ?? [], ...resources.files.map((file) => ({ ...file, path: `${EMBEDDED_RESOURCES}/${file.path}` }))];
165
- native = { modules: native?.modules ?? [], files: files, links: native?.links ?? [], hash: hashNativeTree(files, native?.links ?? []) };
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) => workers.nameOf(workers.scripts.get(at))))].sort(),
202
- warnings: workers.warnings
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 scripts run on their own in the native tree
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 WORKERS_DIRECTORY = '.lakutata/workers';
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 (WORKERS_DIRECTORY, the directories mirrored), and the expression rewritten to it. A path known at
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 WorkerCollector {
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
- * The scripts a path gives: the file, or the ones its pattern matches; their directory and the parts of their paths
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
- scriptsOf(found, file) {
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 script = path.join(directory, prefix.slice(cut + 1));
319
- return { directory: directory, rest: rest, files: SCRIPT.test(script) && existsSync(script) && statSync(script).isFile() ? [script] : [] };
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.isDirectory() && entry.name !== 'node_modules')
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
- return { directory: directory, rest: rest, files: files.sort() };
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 scripts, wherever they are written
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 { directory, rest, files } = this.scriptsOf(found, file);
428
- //A path to another file (a directory, a data file, a file not found): left as it is
429
- if (!files.length)
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
- for (const script of files)
433
- this.scripts.set(`${WORKERS_DIRECTORY}/${key}/${path.relative(directory, script).split(path.sep).join('/')}`, script);
434
- const relative = `\`${rest.map((part) => 'text' in part ? templateText(part.text) : `\${${part.expression}}`).join('')}\``;
435
- const text = found.kind === 'url' ? `new URL(${relative}, require.lakutataWorkerURL(${JSON.stringify(key)}))` :
436
- found.kind === 'url-text' ? `(require.lakutataWorkerURL(${JSON.stringify(key)}) + ${relative})` :
437
- `require("node:path").join(require.lakutataWorker(${JSON.stringify(key)}), ${relative})`;
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;
@@ -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
- return info.addons || this.dependencies(root).some(({ root: dependency }) => !!this.package(dependency)?.addons);
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 scripts of the workers of the application, bundled into the tree (WORKERS_DIRECTORY): the directory of a key, its URL
141
- const workers = (key) => path.join(locate(), '.lakutata', 'workers', ...key.split('/'));
142
- require.lakutataWorker = workers;
143
- require.lakutataWorkerURL = (key) => nodeRequire('node:url').pathToFileURL(workers(key) + path.sep).href;
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', 'workers') + path.sep;
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
- * What the bundle cannot follow: the scripts of the workers given by paths known at run time only
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 scripts run on their own in the native tree
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 WORKERS_DIRECTORY: string;
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 (WORKERS_DIRECTORY, the directories mirrored), and the expression rewritten to it. A path known at
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 WorkerCollector {
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
- * The scripts a path gives: the file, or the ones its pattern matches; their directory and the parts of their paths
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 scriptsOf(found: ScriptPath, file: string): {
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.4",
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",