@lakutata/builder 3.1.3 → 3.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +24 -1
  2. package/README.zh-CN.md +7 -1
  3. package/dist/esm/index.js +5 -0
  4. package/dist/esm/lib/Archive.js +3 -2
  5. package/dist/esm/lib/Bundle.js +2 -1
  6. package/dist/esm/lib/Directories.js +18 -0
  7. package/dist/esm/lib/Executable.js +27 -6
  8. package/dist/esm/lib/Installer.js +28 -14
  9. package/dist/esm/lib/NativeRuntime.js +27 -3
  10. package/dist/esm/lib/Processes.js +3 -2
  11. package/dist/esm/lib/Tools.js +98 -19
  12. package/dist/esm/lib/linux/Linux.js +60 -10
  13. package/dist/esm/lib/mac/Mac.js +39 -11
  14. package/dist/esm/lib/mobile/Addons.js +675 -0
  15. package/dist/esm/lib/mobile/Node.js +190 -0
  16. package/dist/esm/lib/mobile/Stage.js +180 -0
  17. package/dist/esm/lib/nwjs/Addons.js +310 -0
  18. package/dist/esm/lib/nwjs/Package.js +149 -0
  19. package/dist/esm/lib/nwjs/Runtime.js +188 -0
  20. package/dist/esm/lib/nwjs/Stage.js +92 -0
  21. package/dist/esm/lib/win/Windows.js +68 -45
  22. package/dist/types/index.d.ts +7 -0
  23. package/dist/types/lib/Bundle.d.ts +5 -0
  24. package/dist/types/lib/Directories.d.ts +8 -0
  25. package/dist/types/lib/Executable.d.ts +7 -0
  26. package/dist/types/lib/Installer.d.ts +37 -4
  27. package/dist/types/lib/Processes.d.ts +2 -1
  28. package/dist/types/lib/Tools.d.ts +47 -1
  29. package/dist/types/lib/linux/Linux.d.ts +2 -1
  30. package/dist/types/lib/mac/Mac.d.ts +1 -1
  31. package/dist/types/lib/mobile/Addons.d.ts +154 -0
  32. package/dist/types/lib/mobile/Node.d.ts +89 -0
  33. package/dist/types/lib/mobile/Stage.d.ts +111 -0
  34. package/dist/types/lib/nwjs/Addons.d.ts +69 -0
  35. package/dist/types/lib/nwjs/Package.d.ts +56 -0
  36. package/dist/types/lib/nwjs/Runtime.d.ts +120 -0
  37. package/dist/types/lib/nwjs/Stage.d.ts +104 -0
  38. package/dist/types/lib/win/Windows.d.ts +30 -1
  39. package/package.json +2 -2
@@ -1,7 +1,8 @@
1
1
  import { copyFileSync, lstatSync, mkdirSync, readdirSync, readlinkSync } from 'node:fs';
2
- import { stat, mkdir, writeFile, rm } from 'node:fs/promises';
2
+ import { stat, mkdir, readFile, writeFile, rm } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { archiveDirectory } from '../Archive.js';
5
+ import { decodePng, writeLinuxIcons } from '../Icons.js';
5
6
  import { runCommand } from '../Processes.js';
6
7
  import { ensureTool } from '../Tools.js';
7
8
  import { embeddedShell, INSTALLER_HOOKS, NATIVE_HOOKS } from '../Scripts.js';
@@ -18,11 +19,47 @@ const RUNTIME_DEPENDS = {
18
19
  apk: ['libstdc++', 'libgcc'],
19
20
  archlinux: ['gcc-libs']
20
21
  };
22
+ /**
23
+ * The libraries of the system a desktop application of Chromium (NW.js) loads, by package: the deb ones of Electron, and
24
+ * the atomic operations the Node.js of NW.js loads (libnode.so needs libatomic.so.1, which only the compilers install)
25
+ */
26
+ const DESKTOP_DEPENDS = {
27
+ deb: ['libgtk-3-0', 'libnotify4', 'libnss3', 'libxss1', 'libxtst6', 'xdg-utils', 'libatspi2.0-0', 'libuuid1', 'libsecret-1-0', 'libasound2', 'libgbm1', 'libatomic1'],
28
+ rpm: ['gtk3', 'libnotify', 'nss', 'libXScrnSaver', 'libXtst', 'xdg-utils', 'at-spi2-core', 'libuuid', 'libsecret', 'alsa-lib', 'mesa-libgbm', 'libatomic'],
29
+ apk: [],
30
+ archlinux: []
31
+ };
32
+ /**
33
+ * The desktop entry of a desktop application, which the menus of the desktops list
34
+ * @param plan
35
+ * @param executable
36
+ */
37
+ function desktopEntry(plan, executable) {
38
+ return [
39
+ '[Desktop Entry]',
40
+ 'Type=Application',
41
+ `Name=${plan.productName}`,
42
+ `Comment=${plan.description.replace(/\s+/g, ' ')}`,
43
+ `Exec="${plan.prefix}/${executable}" %U`,
44
+ `Icon=${plan.name}`,
45
+ 'Terminal=false',
46
+ 'Categories=Utility;',
47
+ ''
48
+ ].join('\n');
49
+ }
50
+ /**
51
+ * The lines refreshing the menus and the icons of the desktops after the installation and the removal of a desktop
52
+ * application, where their tools are installed
53
+ */
54
+ const DESKTOP_REFRESH = [
55
+ 'command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q /usr/share/applications || true',
56
+ 'command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true'
57
+ ];
21
58
  /**
22
59
  * The architecture of the packages (the one of Go, which nFPM translates for each package)
23
60
  */
24
- function packageArch() {
25
- return { x64: 'amd64', arm64: 'arm64', ia32: '386', arm: 'arm7' }[process.arch] ?? process.arch;
61
+ function packageArch(plan) {
62
+ return { x64: 'amd64', arm64: 'arm64', ia32: '386', arm: 'arm7' }[plan.arch] ?? plan.arch;
26
63
  }
27
64
  /**
28
65
  * The characters of the globs, which nFPM reads in the sources of the contents (the chunks of Turbopack are named
@@ -78,7 +115,7 @@ function packageFile(plan, target) {
78
115
  const arch = { x64: 'x86_64', arm64: 'aarch64' }[process.arch] ?? process.arch;
79
116
  switch (target) {
80
117
  case 'deb':
81
- return `${plan.name}_${plan.version}_${packageArch()}.deb`;
118
+ return `${plan.name}_${plan.version}_${packageArch(plan)}.deb`;
82
119
  case 'rpm':
83
120
  return `${plan.name}-${plan.version}-1.${arch}.rpm`;
84
121
  case 'apk':
@@ -276,7 +313,8 @@ function packageScripts(plan, target) {
276
313
  //Not indented: the lines of the script are the ones of its file
277
314
  ...application ? [scriptAction(target, hook), 'if [ -n "$lakutata_action" ]; then', ...embeddedShell(application, plan.prefix, '"$lakutata_action"'), 'fi'] : [],
278
315
  ...native ? embeddedShell(native, plan.prefix, '"$@"') : [],
279
- ...service.after[hook] ?? []
316
+ ...service.after[hook] ?? [],
317
+ ...plan.desktop && (hook === 'postinstall' || hook === 'postremove' || hook === 'postupgrade') ? DESKTOP_REFRESH : []
280
318
  ];
281
319
  if (lines.length)
282
320
  scripts[hook] = ['#!/bin/sh', 'set -e', ...lines, ''].join('\n');
@@ -290,14 +328,15 @@ function packageScripts(plan, target) {
290
328
  * @param targets
291
329
  * @param work
292
330
  * @param outdir
331
+ * @param progress Called with what is being downloaded (nFPM)
293
332
  */
294
- export async function buildLinuxPackages(plan, targets, work, outdir) {
333
+ export async function buildLinuxPackages(plan, targets, work, outdir, progress) {
295
334
  await mkdir(work, { recursive: true });
296
335
  const files = [];
297
336
  const service = plan.service;
298
337
  for (const target of targets) {
299
338
  if (target === 'tar.gz') {
300
- files.push(await archiveDirectory(plan, packageArch(), work, outdir));
339
+ files.push(await archiveDirectory(plan, packageArch(plan), work, outdir));
301
340
  continue;
302
341
  }
303
342
  const directory = path.join(work, target);
@@ -305,6 +344,17 @@ export async function buildLinuxPackages(plan, targets, work, outdir) {
305
344
  const contents = packageContents(plan.directory, plan.prefix, directory);
306
345
  if (plan.addToPath)
307
346
  contents.push({ src: `${plan.prefix}/${plan.program}`, dst: `/usr/bin/${plan.program}`, type: 'symlink' });
347
+ if (plan.desktop) {
348
+ //Its entry in the menus of the desktops, its icons in the theme hicolor
349
+ await writeFile(path.join(directory, 'desktop'), desktopEntry(plan, plan.desktop.executable));
350
+ contents.push({ src: path.join(directory, 'desktop'), dst: `/usr/share/applications/${plan.name}.desktop`, file_info: { mode: 0o644 } });
351
+ if (plan.desktop.icon) {
352
+ for (const icon of await writeLinuxIcons(decodePng(await readFile(plan.desktop.icon), plan.desktop.icon), path.join(directory, 'icons'))) {
353
+ const size = path.basename(icon, '.png');
354
+ contents.push({ src: icon, dst: `/usr/share/icons/hicolor/${size}/apps/${plan.name}.png`, file_info: { mode: 0o644 } });
355
+ }
356
+ }
357
+ }
308
358
  if (service) {
309
359
  await writeFile(path.join(directory, 'environment'), environmentFile(service));
310
360
  contents.push({ src: path.join(directory, 'environment'), dst: `/etc/${plan.name}/${plan.name}.env`, type: 'config|noreplace', file_info: { mode: 0o640 } });
@@ -331,7 +381,7 @@ export async function buildLinuxPackages(plan, targets, work, outdir) {
331
381
  }
332
382
  const config = {
333
383
  name: plan.name,
334
- arch: packageArch(),
384
+ arch: packageArch(plan),
335
385
  platform: 'linux',
336
386
  version: plan.version,
337
387
  maintainer: plan.maintainer,
@@ -341,7 +391,7 @@ export async function buildLinuxPackages(plan, targets, work, outdir) {
341
391
  ...plan.license ? { license: plan.license } : {},
342
392
  ...plan.section ? { section: plan.section } : {},
343
393
  ...plan.priority ? { priority: plan.priority } : {},
344
- depends: [...new Set([...RUNTIME_DEPENDS[target], ...plan.depends])],
394
+ depends: [...new Set([...RUNTIME_DEPENDS[target], ...plan.desktop ? DESKTOP_DEPENDS[target] : [], ...plan.depends])],
345
395
  ...plan.recommends.length ? { recommends: plan.recommends } : {},
346
396
  contents: contents,
347
397
  scripts: common,
@@ -349,7 +399,7 @@ export async function buildLinuxPackages(plan, targets, work, outdir) {
349
399
  };
350
400
  //JSON is YAML
351
401
  await writeFile(path.join(directory, 'nfpm.yaml'), JSON.stringify(config, null, 2));
352
- const nfpm = await ensureTool('nfpm');
402
+ const nfpm = await ensureTool('nfpm', progress);
353
403
  const file = path.join(outdir, packageFile(plan, target));
354
404
  await rm(file, { force: true });
355
405
  const packaged = await runCommand(nfpm, ['package', '--config', path.join(directory, 'nfpm.yaml'), '--packager', target, '--target', file], directory);
@@ -1,17 +1,18 @@
1
- import { chmod, cp, mkdir, readFile, rm, stat, symlink, writeFile } from 'node:fs/promises';
1
+ import { chmod, mkdir, readFile, rm, stat, symlink, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { archiveDirectory } from '../Archive.js';
4
+ import { copyDirectory } from '../Directories.js';
4
5
  import { runCommand } from '../Processes.js';
5
6
  import { embeddedShell } from '../Scripts.js';
6
7
  /**
7
8
  * The installers of macOS: the package of its installer, and the archive of the directory of the executable
8
9
  */
9
- export const MAC_TARGETS = ['pkg', 'tar.gz'];
10
+ export const MAC_TARGETS = ['pkg', 'dmg', 'tar.gz'];
10
11
  /**
11
12
  * The architecture of the packages
12
13
  */
13
- function packageArch() {
14
- return process.arch;
14
+ function packageArch(plan) {
15
+ return plan.arch;
15
16
  }
16
17
  /**
17
18
  * A text of a property list
@@ -103,7 +104,7 @@ function preInstall(plan) {
103
104
  return [
104
105
  '#!/bin/sh',
105
106
  'set -e',
106
- `if [ -e ${doubleQuoted(`${plan.prefix}/${plan.program}`)} ]; then lakutata_action=upgrade; else lakutata_action=install; fi`,
107
+ `if [ -e ${doubleQuoted(plan.desktop ? plan.prefix : `${plan.prefix}/${plan.program}`)} ]; then lakutata_action=upgrade; else lakutata_action=install; fi`,
107
108
  `if [ -n "$INSTALLER_TEMP" ]; then echo "$lakutata_action" > ${ACTION_FILE}; fi`,
108
109
  ...hooks.preinstall ? embeddedShell(hooks.preinstall, plan.prefix, '"$lakutata_action"') : [],
109
110
  ''
@@ -223,6 +224,26 @@ function uninstall(plan) {
223
224
  ''
224
225
  ].join('\n');
225
226
  }
227
+ /**
228
+ * The disk image of a desktop application (dmg): its bundle and a link to /Applications, where its user drags it
229
+ * @param plan
230
+ * @param work
231
+ * @param outdir
232
+ */
233
+ async function diskImage(plan, work, outdir) {
234
+ if (!plan.desktop)
235
+ throw new Error('The disk images (dmg) are made for the desktop applications (NW.js): the other applications are installed by pkg');
236
+ const volume = path.join(work, 'dmg');
237
+ await rm(volume, { recursive: true, force: true });
238
+ await copyDirectory(path.join(plan.directory, plan.desktop.executable), path.join(volume, plan.desktop.executable));
239
+ await symlink('/Applications', path.join(volume, 'Applications'));
240
+ const file = path.join(outdir, `${plan.name}-${plan.version}-${packageArch(plan)}.dmg`);
241
+ await rm(file, { force: true });
242
+ const created = await runCommand('hdiutil', ['create', '-volname', plan.productName, '-srcfolder', volume, '-ov', '-format', 'UDZO', file], work);
243
+ if (created.code !== 0)
244
+ throw new Error(`The disk image cannot be made: ${created.output.trim()}`);
245
+ return { target: 'dmg', file: file, size: (await stat(file)).size };
246
+ }
226
247
  /**
227
248
  * Make the installers of macOS of an application: the package installed by its installer (pkgbuild, productbuild), the
228
249
  * tar.gz archive of the directory of the executable
@@ -236,7 +257,11 @@ export async function buildMacPackages(plan, targets, work, outdir) {
236
257
  const files = [];
237
258
  for (const target of targets) {
238
259
  if (target === 'tar.gz') {
239
- files.push(await archiveDirectory(plan, packageArch(), work, outdir));
260
+ files.push(await archiveDirectory(plan, packageArch(plan), work, outdir));
261
+ continue;
262
+ }
263
+ if (target === 'dmg') {
264
+ files.push(await diskImage(plan, work, outdir));
240
265
  continue;
241
266
  }
242
267
  //The files installed, from the root of the system
@@ -246,9 +271,11 @@ export async function buildMacPackages(plan, targets, work, outdir) {
246
271
  await rm(scripts, { recursive: true, force: true });
247
272
  const prefix = path.join(root, ...plan.prefix.split('/'));
248
273
  await mkdir(path.dirname(prefix), { recursive: true });
249
- await cp(plan.directory, prefix, { recursive: true, verbatimSymlinks: true });
250
- await writeFile(path.join(prefix, 'uninstall'), uninstall(plan), { mode: 0o755 });
251
- if (plan.addToPath) {
274
+ //A desktop application: its bundle into /Applications (removed by its user, as the other applications)
275
+ await copyDirectory(plan.desktop ? path.join(plan.directory, plan.desktop.executable) : plan.directory, prefix);
276
+ if (!plan.desktop)
277
+ await writeFile(path.join(prefix, 'uninstall'), uninstall(plan), { mode: 0o755 });
278
+ if (plan.addToPath && !plan.desktop) {
252
279
  await mkdir(path.join(root, 'usr', 'local', 'bin'), { recursive: true });
253
280
  await symlink(path.posix.relative('/usr/local/bin', `${plan.prefix}/${plan.program}`), path.join(root, 'usr', 'local', 'bin', plan.program));
254
281
  }
@@ -270,14 +297,15 @@ export async function buildMacPackages(plan, targets, work, outdir) {
270
297
  const built = await runCommand('pkgbuild', ['--root', root, '--identifier', plan.appId, '--version', plan.version, '--install-location', '/', '--scripts', scripts, '--ownership', 'recommended', component], work);
271
298
  if (built.code !== 0)
272
299
  throw new Error(`The pkg package cannot be made: ${built.output.trim()}`);
273
- const file = path.join(outdir, `${plan.name}-${plan.version}-${packageArch()}.pkg`);
300
+ const file = path.join(outdir, `${plan.name}-${plan.version}-${packageArch(plan)}.pkg`);
274
301
  await rm(file, { force: true });
275
302
  //Installed on the Macs of the architecture of the program only (productbuild gives both)
276
303
  const distribution = path.join(work, 'distribution.xml');
277
304
  const synthesized = await runCommand('productbuild', ['--synthesize', '--package', component, distribution], work);
278
305
  if (synthesized.code !== 0)
279
306
  throw new Error(`The pkg package cannot be made: ${synthesized.output.trim()}`);
280
- const architectures = `hostArchitectures="${packageArch() === 'arm64' ? 'arm64' : 'x86_64'}"`;
307
+ //A desktop application of Intel runs on the Macs of Apple silicon too (Rosetta)
308
+ const architectures = `hostArchitectures="${packageArch(plan) === 'arm64' ? 'arm64' : plan.desktop ? 'x86_64,arm64' : 'x86_64'}"`;
281
309
  const options = await readFile(distribution, 'utf8');
282
310
  await writeFile(distribution, /hostArchitectures="[^"]*"/.test(options) ? options.replace(/hostArchitectures="[^"]*"/, architectures) : options.replace('<options ', `<options ${architectures} `));
283
311
  const product = await runCommand('productbuild', ['--distribution', distribution, '--package-path', work, '--identifier', plan.appId, '--version', plan.version, file], work);