@lakutata/builder 3.0.0-beta.3 → 3.0.0-beta.4

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
@@ -16,7 +16,10 @@ await buildInstaller({project: process.cwd()})
16
16
  ```
17
17
 
18
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;
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
23
  the resources of the application (the `lakutata.resources` of its `package.json`) are found at run time by
21
24
  `resourcesPath()` of lakutata. The installers are configured by the `lakutata.installer` of the `package.json`. See the
22
25
  documentation of `lakutata package bundle`, `lakutata package exe` and `lakutata package installer` in `@lakutata/cli`.
@@ -1,8 +1,9 @@
1
- import { existsSync, readdirSync, statSync } from 'node:fs';
1
+ import { existsSync, readdirSync, 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
7
  /**
7
8
  * The name of the program of a package: its first bin, or the name of the package without its scope
8
9
  * @param manifest
@@ -85,43 +86,77 @@ export async function bundleProject(options) {
85
86
  throw new Error(`The entry ${path.relative(process.cwd(), entry) || entry} does not exist: build the project first`);
86
87
  const outfile = path.resolve(project, options.outfile ?? path.join('dist', `${name}.cjs`));
87
88
  const collector = new NativeCollector(project, options.runtime);
89
+ const workers = new WorkerCollector(project);
88
90
  //esbuild is loaded by the commands which need it
89
91
  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);
92
+ //The application, and each script of its workers (the bundler does not follow new Worker()): their native packages
93
+ //gathered into one tree, the workers they start found too
94
+ const bundle = async (input, output) => {
95
+ const result = await build({
96
+ entryPoints: [input],
97
+ outfile: output,
98
+ write: false,
99
+ metafile: true,
100
+ bundle: true,
101
+ platform: 'node',
102
+ target: `node${process.versions.node.split('.')[0]}`,
103
+ format: 'cjs',
104
+ minify: options.minify ?? true,
105
+ //The names of the classes are the ones of the objects of the container and of the errors
106
+ keepNames: true,
107
+ external: options.external ?? [],
108
+ //The dynamic imports of the modules left out are requires (the require of the bundle loads the native ones)
109
+ supported: { 'dynamic-import': false },
110
+ banner: { js: `var ${IMPORT_META_URL}=require('node:url').pathToFileURL(__filename).href;` },
111
+ define: {
112
+ 'import.meta.url': IMPORT_META_URL,
113
+ //Bun replaces process.env.NODE_ENV with the mode of its build: the one of the run is read
114
+ ...options.runtime === 'bun' ? { 'process.env.NODE_ENV': 'globalThis.process.env.NODE_ENV' } : {}
115
+ },
116
+ plugins: [collector.plugin([(source, file) => workers.transform(source, file)])],
117
+ logLevel: 'silent'
118
+ }).catch((error) => {
119
+ const messages = (error.errors ?? []).map(({ text, location }) => location ? `${location.file}:${location.line}: ${text}` : text);
120
+ //The bundles are CommonJS, which the executables of Node.js run
121
+ const hint = messages.some((message) => message.includes('Top-level await')) ? '\nThe bundles are CommonJS modules, without top-level await: await in an async function' : '';
122
+ throw new Error(`The bundle failed:\n ${messages.length ? messages.join('\n ') : error.message}${hint}`);
123
+ });
124
+ //The files bundled (their real paths; the modules of the plugins are in their namespaces)
125
+ const inputs = new Set(Object.keys(result.metafile.inputs).filter((input) => !/^[a-z-]+:/.test(input)).map((input) => {
126
+ const absolute = path.resolve(input);
127
+ return existsSync(absolute) ? realpathSync(absolute) : absolute;
128
+ }));
129
+ return { code: result.outputFiles[0].text, inputs: inputs };
130
+ };
131
+ const application = await bundle(entry, outfile);
132
+ let code = application.code;
122
133
  const base = path.basename(outfile, path.extname(outfile));
123
134
  const nativeDirectory = path.resolve(path.dirname(outfile), options.directories?.native ?? `${base}.native`);
124
135
  const resourcesDirectory = path.resolve(path.dirname(outfile), options.directories?.resources ?? `${base}.resources`);
136
+ //The scripts of the workers, until the ones they start are bundled too
137
+ const scripts = new Map();
138
+ for (let pending = [...workers.scripts]; pending.length; pending = [...workers.scripts].filter(([at]) => !scripts.has(at))) {
139
+ for (const [at, script] of pending)
140
+ scripts.set(at, (await bundle(script, path.join(nativeDirectory, ...at.split('/')))).code);
141
+ }
142
+ workers.warnBundled(application.inputs);
143
+ const embedded = options.native === 'embedded';
144
+ let native = await collector.collect();
145
+ const resources = resourceFiles(project, manifest);
146
+ if (scripts.size) {
147
+ //The scripts run from the tree: their require loads the native packages from it, their resources are the ones of
148
+ //the application (an executable gives them from the tree it extracted)
149
+ const modules = native?.modules ?? [];
150
+ const files = [{ path: `${WORKERS_DIRECTORY}/package.json`, source: manifestFile, mode: 0o644, content: Buffer.from('{"type": "commonjs"}\n') }];
151
+ for (const [at, script] of [...scripts].sort(([a], [b]) => a.localeCompare(b))) {
152
+ const directory = path.dirname(path.join(nativeDirectory, ...at.split('/')));
153
+ const banner = packagedBanner(path.relative(directory, resourcesDirectory).split(path.sep).join('/')) +
154
+ 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)) });
156
+ }
157
+ const all = [...native?.files ?? [], ...files];
158
+ native = { modules: modules, files: all, links: native?.links ?? [], hash: hashNativeTree(all, native?.links ?? []) };
159
+ }
125
160
  await rm(nativeDirectory, { recursive: true, force: true });
126
161
  await rm(resourcesDirectory, { recursive: true, force: true });
127
162
  //The resources of an executable are extracted with its native tree
@@ -129,6 +164,11 @@ export async function bundleProject(options) {
129
164
  const files = [...native?.files ?? [], ...resources.files.map((file) => ({ ...file, path: `${EMBEDDED_RESOURCES}/${file.path}` }))];
130
165
  native = { modules: native?.modules ?? [], files: files, links: native?.links ?? [], hash: hashNativeTree(files, native?.links ?? []) };
131
166
  }
167
+ if (native && embedded) {
168
+ //An executable runs the scripts of its tree it is started for (fork()) instead of the application
169
+ const hashbang = /^#![^\n]*\n/.exec(code)?.[0] ?? '';
170
+ code = `${hashbang}if (!require.lakutataScript()) {\n${code.slice(hashbang.length)}\n}\n`;
171
+ }
132
172
  if (native) {
133
173
  //The require loading the native packages is the first code, after the hashbang
134
174
  code = prepend(code, nativeBanner({ name: name, hash: native.hash, directory: path.basename(nativeDirectory), modules: native.modules, embedded: embedded }));
@@ -157,6 +197,8 @@ export async function bundleProject(options) {
157
197
  native: native,
158
198
  nativeDirectory: native && !embedded ? nativeDirectory : undefined,
159
199
  resources: resources.declared,
160
- resourcesDirectory: resources.files.length && !embedded ? resourcesDirectory : undefined
200
+ 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
161
203
  };
162
204
  }
@@ -196,7 +196,7 @@ export async function buildExecutable(options) {
196
196
  //ignores the case), whose signature it writes beside the executable (_CodeSignature)
197
197
  const made = path.join(work, 'executable', path.basename(file));
198
198
  await mkdir(path.dirname(made), { recursive: true });
199
- const warnings = [];
199
+ const warnings = [...bundle.warnings];
200
200
  //The icon of the application: the one of the executables of Windows (the ones of macOS and Linux have none)
201
201
  const application = applicationIcon(project, manifest);
202
202
  const icon = application && process.platform === 'win32' ? icoFile(application.image) : undefined;
@@ -209,6 +209,7 @@ export async function buildExecutable(options) {
209
209
  platform: `${process.platform === 'win32' ? 'win' : process.platform}-${process.arch}`,
210
210
  size: (await stat(file)).size,
211
211
  native: bundle.native?.modules ?? [],
212
+ workers: bundle.workers,
212
213
  resources: bundle.resources,
213
214
  directory: directory ? target : undefined,
214
215
  runtime: runtime,
@@ -4,6 +4,13 @@ import { mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
4
4
  import { createRequire, isBuiltin } from 'node:module';
5
5
  import path from 'node:path';
6
6
  import { gzipSync } from 'node:zlib';
7
+ /**
8
+ * The content of a file of the native tree
9
+ * @param file
10
+ */
11
+ function contentOf(file) {
12
+ return file.content ?? readFileSync(file.source);
13
+ }
7
14
  /**
8
15
  * The shared libraries: the addons, and the libraries they are linked with (sharp and libvips)
9
16
  */
@@ -227,8 +234,10 @@ export class NativeCollector {
227
234
  }
228
235
  /**
229
236
  * The plugin of the bundle: the native packages are external, the addons of the application are loaded from the tree
237
+ * @param transforms the transformations of the sources of the modules (the paths of the workers), giving their new
238
+ * source or undefined
230
239
  */
231
- plugin() {
240
+ plugin(transforms = []) {
232
241
  return {
233
242
  name: 'lakutata-native',
234
243
  setup: (build) => {
@@ -263,11 +272,21 @@ export class NativeCollector {
263
272
  });
264
273
  //The requires of the ES modules are the one of the bundle: esbuild follows what they load (the addons)
265
274
  build.onLoad({ filter: /\.(c|m)?js$/ }, async (args) => {
266
- const source = await readFile(args.path, 'utf8');
267
- if (!MODULE_REQUIRE.test(source))
268
- return undefined;
269
- MODULE_REQUIRE.lastIndex = 0;
270
- return { contents: source.replace(MODULE_REQUIRE, 'require'), loader: 'js', resolveDir: path.dirname(args.path) };
275
+ let source = await readFile(args.path, 'utf8');
276
+ let changed = false;
277
+ if (MODULE_REQUIRE.test(source)) {
278
+ MODULE_REQUIRE.lastIndex = 0;
279
+ source = source.replace(MODULE_REQUIRE, 'require');
280
+ changed = true;
281
+ }
282
+ for (const transform of transforms) {
283
+ const transformed = transform(source, args.path);
284
+ if (transformed === undefined)
285
+ continue;
286
+ source = transformed;
287
+ changed = true;
288
+ }
289
+ return changed ? { contents: source, loader: 'js', resolveDir: path.dirname(args.path) } : undefined;
271
290
  });
272
291
  //The addons of the application are loaded by the require of the bundle, from the extracted tree
273
292
  build.onLoad({ filter: /.*/, namespace: 'lakutata-native' }, (args) => ({
@@ -487,7 +506,7 @@ export class NativeCollector {
487
506
  export function hashNativeTree(files, links) {
488
507
  const hash = createHash('sha256');
489
508
  for (const file of files)
490
- hash.update(`${file.path}\0${file.mode}\0`).update(readFileSync(file.source));
509
+ hash.update(`${file.path}\0${file.mode}\0`).update(contentOf(file));
491
510
  for (const link of links)
492
511
  hash.update(`${link.path}\0${link.target}\0`);
493
512
  return hash.digest('hex').slice(0, 16);
@@ -502,7 +521,7 @@ export async function writeNativeTree(tree, directory) {
502
521
  for (const file of tree.files) {
503
522
  const target = path.join(directory, ...file.path.split('/'));
504
523
  await mkdir(path.dirname(target), { recursive: true });
505
- await writeFile(target, readFileSync(file.source), { mode: file.mode });
524
+ await writeFile(target, contentOf(file), { mode: file.mode });
506
525
  }
507
526
  for (const link of tree.links) {
508
527
  const at = path.join(directory, ...link.path.split('/'));
@@ -524,7 +543,7 @@ export function packNativeTree(tree) {
524
543
  const files = [];
525
544
  let offset = 0;
526
545
  for (const file of tree.files) {
527
- const content = readFileSync(file.source);
546
+ const content = contentOf(file);
528
547
  files.push({ path: file.path, offset: offset, size: content.length, mode: file.mode });
529
548
  chunks.push(content);
530
549
  offset += content.length;
@@ -97,7 +97,10 @@ export function lakutataNativeRequire(nodeRequire, config) {
97
97
  const locate = () => {
98
98
  if (root)
99
99
  return root;
100
- if (config.embedded) {
100
+ if (config.root !== undefined) {
101
+ root = path.resolve(__dirname, config.root);
102
+ }
103
+ else if (config.embedded) {
101
104
  try {
102
105
  root = extract(cacheDirectory());
103
106
  }
@@ -121,7 +124,9 @@ export function lakutataNativeRequire(nodeRequire, config) {
121
124
  return modules.has(id.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]);
122
125
  };
123
126
  const tree = () => treeRequire ??= createRequire(path.join(locate(), 'index.js'));
124
- const require = ((id) => fromTree(id) ? tree()(id) : nodeRequire(id));
127
+ //The files loaded by their paths (the scripts of the tree): the require of an executable of Node.js loads the modules of
128
+ //node only
129
+ const require = ((id) => fromTree(id) ? tree()(id) : path.isAbsolute(id) ? createRequire(id)(id) : nodeRequire(id));
125
130
  require.resolve = ((id, options) => fromTree(id) ? tree().resolve(id, options) : nodeRequire.resolve(id, options));
126
131
  require.cache = nodeRequire.cache;
127
132
  require.main = nodeRequire.main;
@@ -132,6 +137,28 @@ export function lakutataNativeRequire(nodeRequire, config) {
132
137
  process.dlopen(addon, path.join(locate(), ...key.split('/')));
133
138
  return addon.exports;
134
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;
144
+ //An executable started to run a script of its tree (fork(), spawn(process.execPath, [script]): process.execPath is the
145
+ //executable): it runs the script as node runs it, the application not started
146
+ require.lakutataScript = () => {
147
+ if (!config.embedded || config.root !== undefined)
148
+ return false;
149
+ const scripts = path.join(locate(), '.lakutata', 'workers') + path.sep;
150
+ const at = [1, 2].find((index) => !!process.argv[index] && path.resolve(process.argv[index]).startsWith(scripts)) ?? -1;
151
+ if (at < 0)
152
+ return false;
153
+ const script = path.resolve(process.argv[at]);
154
+ process.argv.splice(1, at - 1);
155
+ //The main module of node (require.main); Bun loads it by a require (its Module._load does nothing)
156
+ if (process.versions.bun)
157
+ createRequire(script)(script);
158
+ else
159
+ nodeRequire('node:module')._load(script, null, true);
160
+ return true;
161
+ };
135
162
  //The resources of an executable are extracted with the tree (PACKAGED, EMBEDDED_RESOURCES)
136
163
  if (config.embedded)
137
164
  globalThis[Symbol.for('lakutata.packaged')] = { resources: () => path.join(locate(), '.lakutata', 'resources') };
@@ -0,0 +1,476 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+ import { parse } from 'acorn';
6
+ /**
7
+ * The directory of the scripts run on their own in the native tree
8
+ */
9
+ export const WORKERS_DIRECTORY = '.lakutata/workers';
10
+ /**
11
+ * The scripts run on their own: the CommonJS scripts, which they are bundled as
12
+ */
13
+ const SCRIPT = /\.c?js$/;
14
+ /**
15
+ * The modules of the worker threads
16
+ */
17
+ const WORKER_THREADS = new Set(['worker_threads', 'node:worker_threads']);
18
+ /**
19
+ * The name of a function called: the identifier, the property (path.join, node_path_1.join), the last expression of a
20
+ * sequence (the calls of tsc: (0, node_path_1.join)(...))
21
+ * @param node
22
+ */
23
+ function calleeName(node) {
24
+ if (node?.type === 'Identifier')
25
+ return node.name;
26
+ if (node?.type === 'MemberExpression')
27
+ return node.computed ? (typeof node.property.value === 'string' ? node.property.value : undefined) : node.property.name;
28
+ if (node?.type === 'SequenceExpression')
29
+ return calleeName(node.expressions[node.expressions.length - 1]);
30
+ return undefined;
31
+ }
32
+ /**
33
+ * Whether a node is import.meta.<name>
34
+ * @param node
35
+ * @param name
36
+ */
37
+ function isImportMeta(node, name) {
38
+ return node?.type === 'MemberExpression' && !node.computed && node.object.type === 'MetaProperty' && node.object.meta.name === 'import' && node.property.name === name;
39
+ }
40
+ /**
41
+ * Whether a node is the URL of the file of the module: import.meta.url, pathToFileURL(__filename) (and its href)
42
+ * @param node
43
+ */
44
+ function isFileURL(node) {
45
+ if (isImportMeta(node, 'url'))
46
+ return true;
47
+ if (node?.type === 'CallExpression' && calleeName(node.callee) === 'pathToFileURL')
48
+ return isFilename(node.arguments[0]);
49
+ if (node?.type === 'MemberExpression' && !node.computed && node.property.name === 'href')
50
+ return isFileURL(node.object);
51
+ return false;
52
+ }
53
+ /**
54
+ * Whether a node is the file of the module: __filename, import.meta.filename, fileURLToPath(import.meta.url)
55
+ * @param node
56
+ */
57
+ function isFilename(node) {
58
+ if (node?.type === 'Identifier')
59
+ return node.name === '__filename';
60
+ if (isImportMeta(node, 'filename'))
61
+ return true;
62
+ return node?.type === 'CallExpression' && calleeName(node.callee) === 'fileURLToPath' && isImportMeta(node.arguments[0], 'url');
63
+ }
64
+ /**
65
+ * Whether a node is the directory of the module: __dirname, import.meta.dirname, dirname(<its file>),
66
+ * fileURLToPath(new URL('.', <its URL>))
67
+ * @param node
68
+ */
69
+ function isDirname(node) {
70
+ if (node?.type === 'Identifier')
71
+ return node.name === '__dirname';
72
+ if (isImportMeta(node, 'dirname'))
73
+ return true;
74
+ if (node?.type !== 'CallExpression')
75
+ return false;
76
+ const name = calleeName(node.callee);
77
+ if (name === 'dirname')
78
+ return isFilename(node.arguments[0]);
79
+ if (name !== 'fileURLToPath')
80
+ return false;
81
+ const url = node.arguments[0];
82
+ return url?.type === 'NewExpression' && calleeName(url.callee) === 'URL' && url.arguments[0]?.type === 'Literal' && ['.', './'].includes(url.arguments[0].value) && isFileURL(url.arguments[1]);
83
+ }
84
+ /**
85
+ * Whether a node is require('<module>') of one of the modules
86
+ * @param node
87
+ * @param modules
88
+ */
89
+ function isRequireOf(node, modules) {
90
+ return node?.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'require' && node.arguments[0]?.type === 'Literal' && modules.has(node.arguments[0].value);
91
+ }
92
+ /**
93
+ * The pieces of a string expression: its literal texts, the expressions known at run time only
94
+ * @param node
95
+ * @param source
96
+ */
97
+ function partsOf(node, source) {
98
+ if (node?.type === 'Literal')
99
+ return typeof node.value === 'string' ? [{ text: node.value }] : undefined;
100
+ if (node?.type === 'TemplateLiteral') {
101
+ const parts = [];
102
+ node.quasis.forEach((quasi, index) => {
103
+ if (quasi.value.cooked)
104
+ parts.push({ text: quasi.value.cooked });
105
+ if (index < node.expressions.length)
106
+ parts.push({ expression: source.slice(node.expressions[index].start, node.expressions[index].end) });
107
+ });
108
+ return parts;
109
+ }
110
+ if (node?.type === 'BinaryExpression' && node.operator === '+') {
111
+ const left = partsOf(node.left, source);
112
+ const right = partsOf(node.right, source);
113
+ return left && right ? [...left, ...right] : undefined;
114
+ }
115
+ if (node?.type === 'SpreadElement')
116
+ return undefined;
117
+ return [{ expression: source.slice(node.start, node.end) }];
118
+ }
119
+ /**
120
+ * The operands of a chain of concatenations, from the first one
121
+ * @param node
122
+ */
123
+ function operandsOf(node) {
124
+ return node?.type === 'BinaryExpression' && node.operator === '+' ? [...operandsOf(node.left), node.right] : [node];
125
+ }
126
+ /**
127
+ * The parts of the operands following a base
128
+ * @param operands
129
+ * @param source
130
+ */
131
+ function partsOfOperands(operands, source) {
132
+ const parts = [];
133
+ for (const operand of operands) {
134
+ const pieces = partsOf(operand, source);
135
+ if (!pieces)
136
+ return undefined;
137
+ parts.push(...pieces);
138
+ }
139
+ return parts;
140
+ }
141
+ /**
142
+ * The text of parts known when the application is bundled, undefined when one is known at run time only
143
+ * @param parts
144
+ */
145
+ function textOf(parts) {
146
+ return parts.every((part) => 'text' in part) ? parts.map((part) => part.text).join('') : undefined;
147
+ }
148
+ /**
149
+ * The path to a script an expression gives from its module, undefined when it gives none
150
+ * @param node
151
+ * @param source
152
+ * @param file the module
153
+ */
154
+ function scriptPathOf(node, source, file) {
155
+ //new URL('./worker.js', import.meta.url), new URL('./worker.js', pathToFileURL(__filename))
156
+ if (node?.type === 'NewExpression' && calleeName(node.callee) === 'URL' && node.arguments.length === 2 && isFileURL(node.arguments[1])) {
157
+ const parts = partsOf(node.arguments[0], source);
158
+ const first = parts?.[0];
159
+ //A path relative to the module
160
+ if (!parts || !first || !('text' in first) || /^([a-z][a-z0-9+.-]*:|\/)/i.test(first.text))
161
+ return undefined;
162
+ return { kind: 'url', parts: parts };
163
+ }
164
+ if (node?.type === 'CallExpression') {
165
+ const name = calleeName(node.callee);
166
+ //path.join(__dirname, 'worker.js'), path.resolve(__dirname, 'workers', `${name}.js`)
167
+ if ((name === 'join' || name === 'resolve') && isDirname(node.arguments[0])) {
168
+ const parts = [];
169
+ for (const argument of node.arguments.slice(1)) {
170
+ const pieces = partsOf(argument, source);
171
+ if (!pieces)
172
+ return undefined;
173
+ parts.push({ text: '/' }, ...pieces);
174
+ }
175
+ return { kind: 'path', parts: parts };
176
+ }
177
+ //require.resolve('./worker'), require.resolve(path.resolve(__dirname, '../lib/worker')): the extension of node found
178
+ if (name === 'resolve' && node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'require' && node.arguments.length === 1) {
179
+ const argument = node.arguments[0];
180
+ let request;
181
+ if (argument.type === 'Literal' && typeof argument.value === 'string' && /^\.\.?\//.test(argument.value))
182
+ request = argument.value;
183
+ const inner = request === undefined ? scriptPathOf(argument, source, file) : undefined;
184
+ const text = inner?.kind === 'path' ? textOf(inner.parts) : undefined;
185
+ if (text !== undefined)
186
+ request = path.join(inner.base ?? path.dirname(file), text);
187
+ if (request === undefined)
188
+ return undefined;
189
+ try {
190
+ const resolved = createRequire(file).resolve(request);
191
+ return { kind: 'path', parts: [{ text: path.basename(resolved) }], base: path.dirname(resolved) };
192
+ }
193
+ catch {
194
+ return undefined;
195
+ }
196
+ }
197
+ }
198
+ if (node?.type === 'BinaryExpression' && node.operator === '+') {
199
+ const [base, ...rest] = operandsOf(node);
200
+ //__dirname + '/worker.js'
201
+ if (isDirname(base)) {
202
+ const parts = partsOfOperands(rest, source);
203
+ return parts ? { kind: 'path', parts: parts } : undefined;
204
+ }
205
+ //import.meta.url + '/../entry/worker.js': the URL of the module, as a directory
206
+ if (isFileURL(base)) {
207
+ const parts = partsOfOperands(rest, source);
208
+ const first = parts?.[0];
209
+ return parts && first && 'text' in first && first.text.startsWith('/') ? { kind: 'url-text', parts: parts, base: file } : undefined;
210
+ }
211
+ return undefined;
212
+ }
213
+ //`${__dirname}/worker.js`
214
+ if (node?.type === 'TemplateLiteral' && node.expressions.length && !node.quasis[0].value.cooked && isDirname(node.expressions[0])) {
215
+ const parts = [];
216
+ node.quasis.forEach((quasi, index) => {
217
+ if (quasi.value.cooked)
218
+ parts.push({ text: quasi.value.cooked });
219
+ if (index > 0 && index < node.expressions.length)
220
+ parts.push({ expression: source.slice(node.expressions[index].start, node.expressions[index].end) });
221
+ });
222
+ return { kind: 'path', parts: parts };
223
+ }
224
+ return undefined;
225
+ }
226
+ /**
227
+ * Visit the nodes of a syntax tree, depth first: the children of a node are not visited when the visitor returns false
228
+ * @param node
229
+ * @param visit
230
+ */
231
+ function walk(node, visit) {
232
+ if (!node || typeof node.type !== 'string')
233
+ return;
234
+ if (visit(node) === false)
235
+ return;
236
+ for (const key of Object.keys(node)) {
237
+ const value = node[key];
238
+ if (Array.isArray(value))
239
+ for (const child of value)
240
+ walk(child, visit);
241
+ else if (value && typeof value === 'object' && typeof value.type === 'string')
242
+ walk(value, visit);
243
+ }
244
+ }
245
+ /**
246
+ * A text in a template literal
247
+ * @param text
248
+ */
249
+ function templateText(text) {
250
+ return text.replace(/[`\\]|\$\{/g, (matched) => `\\${matched}`);
251
+ }
252
+ /**
253
+ * The scripts an application runs on its own, by their paths, which the bundler does not follow: the scripts of its
254
+ * workers (new Worker(), the pools of workers given a file), of its child processes (fork()), the modules it loads by
255
+ * their paths (require(path.join(__dirname, 'x.js'))), in its modules and in the ones of its packages. A path to a script
256
+ * 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
258
+ * run time only in part (a template, a concatenation, an argument of path.join: `${name}.js`) gives every script of its
259
+ * directory its pattern matches, the expression of the part kept. The scripts of the workers of node (worker_threads)
260
+ * whose paths cannot be followed are warned of, in the modules of the application (the packages start workers from the
261
+ * paths their users give)
262
+ */
263
+ export class WorkerCollector {
264
+ /**
265
+ * @param project The directory of the project: the scripts are named from it (as the modules the bundler gives, its
266
+ * real path)
267
+ */
268
+ constructor(project) {
269
+ /**
270
+ * The scripts: their paths in the native tree, and their files
271
+ */
272
+ this.scripts = new Map();
273
+ this.warnings = [];
274
+ this.project = realpathSync(project);
275
+ }
276
+ /**
277
+ * The key of a directory in the tree ("/" separated): its path from the project; outside of the project (the
278
+ * packages of a workspace, linked), its path from the node_modules holding it, else a hash of its path (the paths of
279
+ * the machine bundling it are not shipped)
280
+ * @param directory
281
+ */
282
+ keyOf(directory) {
283
+ const relative = path.relative(this.project, directory);
284
+ if (!relative)
285
+ return '_';
286
+ if (!relative.startsWith('..'))
287
+ return relative.split(path.sep).join('/');
288
+ const segments = directory.split(path.sep);
289
+ const modules = segments.indexOf('node_modules');
290
+ return modules >= 0 ? segments.slice(modules).join('/') : `_outside/${createHash('sha256').update(directory).digest('hex').slice(0, 16)}`;
291
+ }
292
+ /**
293
+ * The name of a script: its path from the project ("/" separated), or from the node_modules holding it outside of the
294
+ * project (the packages of a workspace)
295
+ * @param script
296
+ */
297
+ nameOf(script) {
298
+ const relative = path.relative(this.project, script);
299
+ const segments = script.split(path.sep);
300
+ const modules = segments.indexOf('node_modules');
301
+ return relative.startsWith('..') && modules >= 0 ? segments.slice(modules).join('/') : relative.split(path.sep).join('/');
302
+ }
303
+ /**
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
306
+ * @param found
307
+ * @param file the module
308
+ */
309
+ scriptsOf(found, file) {
310
+ //The directory: the texts before the first expression, up to their last separator
311
+ const firstExpression = found.parts.findIndex((part) => 'expression' in part);
312
+ const leading = firstExpression < 0 ? found.parts : found.parts.slice(0, firstExpression);
313
+ const prefix = leading.map((part) => part.text).join('');
314
+ const cut = prefix.lastIndexOf('/');
315
+ const directory = path.resolve(path.join(found.base ?? path.dirname(file), prefix.slice(0, cut + 1)));
316
+ const rest = [...prefix.slice(cut + 1) ? [{ text: prefix.slice(cut + 1) }] : [], ...found.parts.slice(leading.length)];
317
+ 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] : [] };
320
+ }
321
+ //The pattern: an expression matches a part of a segment
322
+ const pattern = new RegExp(`^${rest.map((part) => 'text' in part ? part.text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '[^/]*').join('')}$`);
323
+ const depth = rest.map((part) => 'text' in part ? part.text : '').join('').split('/').length;
324
+ const files = [];
325
+ const search = (at, relative, level) => {
326
+ if (level > depth || !existsSync(at))
327
+ return;
328
+ for (const entry of readdirSync(at, { withFileTypes: true })) {
329
+ const name = relative ? `${relative}/${entry.name}` : entry.name;
330
+ if (entry.isDirectory() && entry.name !== 'node_modules')
331
+ 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
+ }
335
+ };
336
+ search(directory, '', 1);
337
+ return { directory: directory, rest: rest, files: files.sort() };
338
+ }
339
+ /**
340
+ * Warn of a worker in a module of the application
341
+ * @param file
342
+ * @param line
343
+ * @param message
344
+ */
345
+ warn(file, line, message) {
346
+ if (file.split(path.sep).includes('node_modules'))
347
+ return;
348
+ this.warnings.push(`${path.relative(this.project, file)}:${line}: ${message}`);
349
+ }
350
+ /**
351
+ * Warn of the scripts bundled with the application and run on their own too: two copies of their modules
352
+ * @param bundled the files of the bundle of the application (their real paths)
353
+ */
354
+ warnBundled(bundled) {
355
+ for (const script of new Set(this.scripts.values())) {
356
+ if (!bundled.has(script) || script.split(path.sep).includes('node_modules'))
357
+ continue;
358
+ this.warnings.push(`${path.relative(this.project, script)}: is bundled with the application and run on its own by its path (a worker, a child process, a module loaded by its path): two copies of its module, their state and their classes apart — load it by its path only, or import it only`);
359
+ }
360
+ }
361
+ /**
362
+ * The source of a module with its paths to scripts rewritten to the scripts in the native tree; undefined when it
363
+ * gives none
364
+ * @param source
365
+ * @param file
366
+ */
367
+ transform(source, file) {
368
+ if (!/__dirname|__filename|import\.meta|require\.resolve|\bWorker\b/.test(source))
369
+ return undefined;
370
+ let program;
371
+ for (const sourceType of ['module', 'script']) {
372
+ try {
373
+ program = parse(source, { ecmaVersion: 'latest', sourceType: sourceType, locations: true, allowHashBang: true, allowReturnOutsideFunction: true, allowAwaitOutsideFunction: true, allowImportExportEverywhere: true });
374
+ break;
375
+ }
376
+ catch {
377
+ program = undefined;
378
+ }
379
+ }
380
+ if (!program)
381
+ return undefined;
382
+ //The values of the variables (const script = ...) and of the properties of the classes (workerPath = ..., this.workerPath
383
+ //= ...) declared once; the constructors of the workers of node and their modules (Worker, worker_threads_1)
384
+ const constants = new Map();
385
+ const members = new Map();
386
+ const constructors = new Set();
387
+ const namespaces = new Set();
388
+ const remember = (values, name, value) => values.set(name, values.has(name) ? null : value);
389
+ walk(program, (node) => {
390
+ if (node.type === 'ImportDeclaration' && WORKER_THREADS.has(node.source.value)) {
391
+ for (const specifier of node.specifiers) {
392
+ if (specifier.type === 'ImportSpecifier' && (specifier.imported.name ?? specifier.imported.value) === 'Worker')
393
+ constructors.add(specifier.local.name);
394
+ else if (specifier.type !== 'ImportSpecifier')
395
+ namespaces.add(specifier.local.name);
396
+ }
397
+ }
398
+ if (node.type === 'VariableDeclaration') {
399
+ for (const declarator of node.declarations) {
400
+ const init = declarator.init?.type === 'CallExpression' && isRequireOf(declarator.init.arguments[0], WORKER_THREADS) ? declarator.init.arguments[0] : declarator.init;
401
+ if (declarator.id.type === 'Identifier') {
402
+ remember(constants, declarator.id.name, declarator.init);
403
+ if (isRequireOf(init, WORKER_THREADS))
404
+ namespaces.add(declarator.id.name);
405
+ if (init?.type === 'MemberExpression' && isRequireOf(init.object, WORKER_THREADS) && calleeName(init) === 'Worker')
406
+ constructors.add(declarator.id.name);
407
+ }
408
+ else if (declarator.id.type === 'ObjectPattern' && isRequireOf(init, WORKER_THREADS)) {
409
+ for (const property of declarator.id.properties)
410
+ if (property.key?.name === 'Worker' && property.value?.type === 'Identifier')
411
+ constructors.add(property.value.name);
412
+ }
413
+ }
414
+ }
415
+ if (node.type === 'PropertyDefinition' && !node.computed && node.key && node.value)
416
+ remember(members, node.key.name, node.value);
417
+ if (node.type === 'AssignmentExpression' && node.operator === '=' && node.left.type === 'MemberExpression' && node.left.object.type === 'ThisExpression' && !node.left.computed)
418
+ remember(members, node.left.property.name, node.right);
419
+ });
420
+ //The paths to scripts, wherever they are written
421
+ const edits = [];
422
+ const followed = new Set();
423
+ walk(program, (node) => {
424
+ const found = scriptPathOf(node, source, file);
425
+ if (!found)
426
+ 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)
430
+ 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})`;
438
+ followed.add(node);
439
+ edits.push({ start: node.start, end: node.end, text: text });
440
+ return false;
441
+ });
442
+ //The workers of node whose scripts are not followed
443
+ walk(program, (node) => {
444
+ if (node.type !== 'NewExpression' || !node.arguments.length)
445
+ return;
446
+ const callee = node.callee;
447
+ const isWorker = callee.type === 'Identifier' ? constructors.has(callee.name) :
448
+ calleeName(callee) === 'Worker' && callee.type === 'MemberExpression' && ((callee.object.type === 'Identifier' && namespaces.has(callee.object.name)) || isRequireOf(callee.object, WORKER_THREADS));
449
+ if (!isWorker)
450
+ return;
451
+ //A worker running a source (eval: true)
452
+ const options = node.arguments[1];
453
+ if (options?.type === 'ObjectExpression' && options.properties.some((property) => property.key?.name === 'eval' && property.value?.value === true))
454
+ return;
455
+ const argument = node.arguments[0];
456
+ const value = argument.type === 'Identifier' && constants.get(argument.name) ? constants.get(argument.name) :
457
+ argument.type === 'MemberExpression' && argument.object.type === 'ThisExpression' && !argument.computed && members.get(argument.property.name) ? members.get(argument.property.name) : argument;
458
+ if (followed.has(value))
459
+ return;
460
+ //As written: the warnings name it
461
+ const written = source.slice(argument.start, argument.end);
462
+ const line = node.loc.start.line;
463
+ if (scriptPathOf(value, source, file))
464
+ return this.warn(file, line, `the script of the worker (${written}) is not found: a .js or .cjs file from the module, built before the bundle`);
465
+ if (value.type === 'Literal' && typeof value.value === 'string')
466
+ return this.warn(file, line, `the script of the worker (${value.value}) is a path from the current directory of the process, not the bundle: give it from the directory of the module, path.join(__dirname, ${JSON.stringify(value.value)}) or new URL(${JSON.stringify(value.value)}, import.meta.url)`);
467
+ this.warn(file, line, `the script of the worker cannot be followed (${written}): give its path from the directory of the module (path.join(__dirname, 'worker.js'), new URL('./worker.js', import.meta.url)), or ship it as a resource (lakutata.resources)`);
468
+ });
469
+ if (!edits.length)
470
+ return undefined;
471
+ let result = source;
472
+ for (const edit of edits.sort((a, b) => b.start - a.start))
473
+ result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
474
+ return result;
475
+ }
476
+ }
@@ -236,6 +236,7 @@ export async function buildElectronInstaller(project, manifest, config, options)
236
236
  outfile: path.join(application, 'main.cjs'),
237
237
  directories: { native: path.join(work, 'main.native'), resources: path.join(work, 'main.resources') }
238
238
  });
239
+ warnings.push(...bundle.warnings);
239
240
  const maintainer = platform === 'linux' ? packageMaintainer(author, name, warnings) : author ?? name;
240
241
  //The packages of fpm need a homepage
241
242
  let homepage = config.homepage ?? (typeof manifest.homepage === 'string' ? manifest.homepage : undefined);
@@ -354,6 +355,7 @@ export async function buildElectronInstaller(project, manifest, config, options)
354
355
  platform: `${process.platform}-${process.arch}`,
355
356
  size: bundle.size,
356
357
  native: bundle.native?.modules ?? [],
358
+ workers: bundle.workers,
357
359
  resources: bundle.resources,
358
360
  runtime: `Electron v${electronVersion}`,
359
361
  warnings: []
@@ -70,6 +70,14 @@ export type BundleResult = {
70
70
  */
71
71
  resources: string[];
72
72
  resourcesDirectory?: string;
73
+ /**
74
+ * The scripts of the workers of the application (their files, from the project), bundled into the native tree
75
+ */
76
+ workers: string[];
77
+ /**
78
+ * What the bundle cannot follow: the scripts of the workers given by paths known at run time only
79
+ */
80
+ warnings: string[];
73
81
  };
74
82
  /**
75
83
  * The name of the program of a package: its first bin, or the name of the package without its scope
@@ -23,6 +23,10 @@ export type ExecutableResult = {
23
23
  * The packages of native addons embedded, extracted at the first run, or next to the executable
24
24
  */
25
25
  native: string[];
26
+ /**
27
+ * The scripts of the workers of the application (their files, from the project), with the native addons
28
+ */
29
+ workers: string[];
26
30
  /**
27
31
  * The resources of the application (their paths in the resources)
28
32
  */
@@ -1,11 +1,13 @@
1
1
  import type { Plugin, PluginBuild } from 'esbuild';
2
2
  /**
3
- * A file of the native tree: its path in the tree ("/" separated) and where it is read
3
+ * A file of the native tree: its path in the tree ("/" separated) and where it is read, or its content (a script of a
4
+ * worker, bundled)
4
5
  */
5
6
  export type NativeFile = {
6
7
  path: string;
7
8
  source: string;
8
9
  mode: number;
10
+ content?: Buffer;
9
11
  };
10
12
  /**
11
13
  * A link of the native tree to a directory of the tree, as the links of the node_modules (pnpm, the workspaces)
@@ -136,8 +138,10 @@ export declare class NativeCollector {
136
138
  }): Promise<string | undefined>;
137
139
  /**
138
140
  * The plugin of the bundle: the native packages are external, the addons of the application are loaded from the tree
141
+ * @param transforms the transformations of the sources of the modules (the paths of the workers), giving their new
142
+ * source or undefined
139
143
  */
140
- plugin(): Plugin;
144
+ plugin(transforms?: ((source: string, file: string) => string | undefined)[]): Plugin;
141
145
  /**
142
146
  * The packages a native package loads at run time: the JavaScript it requires (followed by esbuild), the packages of
143
147
  * shared libraries it depends on (the libraries linked with its addons), and the native packages it loads
@@ -24,6 +24,11 @@ export type NativeRuntimeConfig = {
24
24
  * in its .lakutata/resources), or a directory next to the bundle
25
25
  */
26
26
  embedded: boolean;
27
+ /**
28
+ * The tree from the directory of the bundle, for the scripts of the workers, which run from the tree (its
29
+ * .lakutata/workers): the tree is not looked for (nor extracted)
30
+ */
31
+ root?: string;
27
32
  };
28
33
  /**
29
34
  * The global symbol of the packed applications giving the directory of their resources (resourcesPath of lakutata)
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The directory of the scripts run on their own in the native tree
3
+ */
4
+ export declare const WORKERS_DIRECTORY: string;
5
+ /**
6
+ * 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
+ type Part = {
9
+ text: string;
10
+ } | {
11
+ expression: string;
12
+ };
13
+ /**
14
+ * A path to a script given from a module: from its directory (path.join, path.resolve, a concatenation or a template of
15
+ * __dirname), a URL relative to its file (new URL('./x.js', import.meta.url)), a URL text from its file
16
+ * (import.meta.url + '/../x.js'), or a file resolved when the application is bundled (require.resolve)
17
+ */
18
+ type ScriptPath = {
19
+ /**
20
+ * What the expression gives: a path, a URL, the text of a URL
21
+ */
22
+ kind: 'path' | 'url' | 'url-text';
23
+ parts: Part[];
24
+ /**
25
+ * The path the parts are joined to, the directory of the module by default
26
+ */
27
+ base?: string;
28
+ };
29
+ /**
30
+ * The scripts an application runs on its own, by their paths, which the bundler does not follow: the scripts of its
31
+ * workers (new Worker(), the pools of workers given a file), of its child processes (fork()), the modules it loads by
32
+ * their paths (require(path.join(__dirname, 'x.js'))), in its modules and in the ones of its packages. A path to a script
33
+ * 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
35
+ * run time only in part (a template, a concatenation, an argument of path.join: `${name}.js`) gives every script of its
36
+ * directory its pattern matches, the expression of the part kept. The scripts of the workers of node (worker_threads)
37
+ * whose paths cannot be followed are warned of, in the modules of the application (the packages start workers from the
38
+ * paths their users give)
39
+ */
40
+ export declare class WorkerCollector {
41
+ /**
42
+ * The scripts: their paths in the native tree, and their files
43
+ */
44
+ readonly scripts: Map<string, string>;
45
+ readonly warnings: string[];
46
+ protected readonly project: string;
47
+ /**
48
+ * @param project The directory of the project: the scripts are named from it (as the modules the bundler gives, its
49
+ * real path)
50
+ */
51
+ constructor(project: string);
52
+ /**
53
+ * The key of a directory in the tree ("/" separated): its path from the project; outside of the project (the
54
+ * packages of a workspace, linked), its path from the node_modules holding it, else a hash of its path (the paths of
55
+ * the machine bundling it are not shipped)
56
+ * @param directory
57
+ */
58
+ protected keyOf(directory: string): string;
59
+ /**
60
+ * The name of a script: its path from the project ("/" separated), or from the node_modules holding it outside of the
61
+ * project (the packages of a workspace)
62
+ * @param script
63
+ */
64
+ nameOf(script: string): string;
65
+ /**
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
68
+ * @param found
69
+ * @param file the module
70
+ */
71
+ protected scriptsOf(found: ScriptPath, file: string): {
72
+ directory: string;
73
+ rest: Part[];
74
+ files: string[];
75
+ };
76
+ /**
77
+ * Warn of a worker in a module of the application
78
+ * @param file
79
+ * @param line
80
+ * @param message
81
+ */
82
+ protected warn(file: string, line: number, message: string): void;
83
+ /**
84
+ * Warn of the scripts bundled with the application and run on their own too: two copies of their modules
85
+ * @param bundled the files of the bundle of the application (their real paths)
86
+ */
87
+ warnBundled(bundled: Set<string>): void;
88
+ /**
89
+ * The source of a module with its paths to scripts rewritten to the scripts in the native tree; undefined when it
90
+ * gives none
91
+ * @param source
92
+ * @param file
93
+ */
94
+ transform(source: string, file: string): string | undefined;
95
+ }
96
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lakutata/builder",
3
- "version": "3.0.0-beta.3",
3
+ "version": "3.0.0-beta.4",
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",
@@ -23,6 +23,7 @@
23
23
  "test:installer:windows": "npm run compile && node --test dist/esm/tests/InstallerWindows.spec.js"
24
24
  },
25
25
  "dependencies": {
26
+ "acorn": "^8.19.0",
26
27
  "esbuild": "^0.28.2",
27
28
  "postject": "^1.0.0-alpha.6",
28
29
  "resedit": "^1.7.2"
@@ -47,5 +48,10 @@
47
48
  "*.md",
48
49
  "LICENSE",
49
50
  "package.json"
50
- ]
51
+ ],
52
+ "devDependencies": {
53
+ "piscina": "^5.3.2",
54
+ "tinypool": "^2.2.0",
55
+ "workerpool": "^10.0.3"
56
+ }
51
57
  }