lakutata 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -30
- package/README.zh-CN.md +130 -0
- package/dist/cjs/exports/QueueComponent.d.ts +1 -0
- package/dist/cjs/exports/QueueComponent.js +20 -0
- package/dist/esm/exports/QueueComponent.js +1 -0
- package/dist/types/exports/QueueComponent.d.ts +1 -0
- package/doc/en/Application.md +107 -0
- package/doc/en/CLI.md +274 -0
- package/doc/en/Cache.md +379 -0
- package/doc/en/Command.md +110 -0
- package/doc/en/ContainerdIntegration.md +17 -16
- package/doc/en/Controllers.md +256 -0
- package/doc/en/DBus.md +291 -0
- package/doc/en/DTOValidation.md +72 -43
- package/doc/en/Database.md +188 -0
- package/doc/en/DatabaseDrivers.md +287 -0
- package/doc/en/Decorator.md +52 -40
- package/doc/en/DockerIntegration.md +7 -6
- package/doc/en/Entities.md +240 -0
- package/doc/en/Entrypoint.md +216 -0
- package/doc/en/Exceptions.md +174 -0
- package/doc/en/GRPC.md +251 -0
- package/doc/en/GettingStarted.md +165 -0
- package/doc/en/HTTP.md +507 -0
- package/doc/en/HelperFunctions.md +11 -11
- package/doc/en/I18n.md +119 -115
- package/doc/en/Injection.md +144 -0
- package/doc/en/Installers.md +280 -0
- package/doc/en/Logger.md +206 -0
- package/doc/en/MCP.md +293 -0
- package/doc/en/Middlewares.md +213 -0
- package/doc/en/Migrations.md +202 -0
- package/doc/en/MongoEmbedded.md +149 -0
- package/doc/en/Monitor.md +164 -0
- package/doc/en/Mutex.md +27 -25
- package/doc/en/NATS.md +246 -0
- package/doc/en/NativeAddons.md +212 -0
- package/doc/en/Objects.md +143 -0
- package/doc/en/Overview.md +62 -85
- package/doc/en/Packaging.md +193 -0
- package/doc/en/ProjectStructure.md +124 -0
- package/doc/en/Queue.md +269 -0
- package/doc/en/Schedules.md +217 -0
- package/doc/en/Security.md +10 -9
- package/doc/en/ServiceCalls.md +504 -0
- package/doc/en/Time.md +19 -20
- package/doc/en/UpgradingFrom1.md +435 -0
- package/doc/en/UpgradingFrom2.md +315 -0
- package/doc/en/Workspaces.md +189 -0
- package/doc/en/examples/BackgroundJobs.md +677 -0
- package/doc/en/examples/CommandLineTool.md +664 -0
- package/doc/en/examples/DesktopApp.md +809 -0
- package/doc/en/examples/DeviceService.md +607 -0
- package/doc/en/examples/MCPServer.md +653 -0
- package/doc/en/examples/README.md +16 -0
- package/doc/en/examples/RestAPI.md +490 -0
- package/doc/en/examples/ServiceCalls.md +829 -0
- package/doc/en/examples/WebApp.md +568 -0
- package/doc/zh/Application.md +75 -0
- package/doc/zh/CLI.md +250 -0
- package/doc/zh/Cache.md +328 -0
- package/doc/zh/Command.md +78 -0
- package/doc/zh/ContainerdIntegration.md +9 -9
- package/doc/zh/Controllers.md +228 -0
- package/doc/zh/DBus.md +257 -0
- package/doc/zh/DTOValidation.md +49 -19
- package/doc/zh/Database.md +175 -0
- package/doc/zh/DatabaseDrivers.md +261 -0
- package/doc/zh/Decorator.md +25 -18
- package/doc/zh/DockerIntegration.md +6 -6
- package/doc/zh/Entities.md +224 -0
- package/doc/zh/Entrypoint.md +193 -0
- package/doc/zh/Exceptions.md +158 -0
- package/doc/zh/GRPC.md +226 -0
- package/doc/zh/GettingStarted.md +148 -0
- package/doc/zh/HTTP.md +445 -0
- package/doc/zh/HelperFunctions.md +2 -2
- package/doc/zh/I18n.md +26 -26
- package/doc/zh/Injection.md +129 -0
- package/doc/zh/Installers.md +232 -0
- package/doc/zh/Logger.md +188 -0
- package/doc/zh/MCP.md +265 -0
- package/doc/zh/Middlewares.md +198 -0
- package/doc/zh/Migrations.md +178 -0
- package/doc/zh/MongoEmbedded.md +118 -0
- package/doc/zh/Monitor.md +145 -0
- package/doc/zh/Mutex.md +10 -10
- package/doc/zh/NATS.md +207 -0
- package/doc/zh/NativeAddons.md +180 -0
- package/doc/zh/Objects.md +119 -0
- package/doc/zh/Overview.md +46 -71
- package/doc/zh/Packaging.md +154 -0
- package/doc/zh/ProjectStructure.md +102 -0
- package/doc/zh/Queue.md +214 -0
- package/doc/zh/Schedules.md +193 -0
- package/doc/zh/Security.md +6 -6
- package/doc/zh/ServiceCalls.md +459 -0
- package/doc/zh/Time.md +2 -2
- package/doc/zh/UpgradingFrom1.md +347 -0
- package/doc/zh/UpgradingFrom2.md +230 -0
- package/doc/zh/Workspaces.md +150 -0
- package/doc/zh/examples/BackgroundJobs.md +618 -0
- package/doc/zh/examples/CommandLineTool.md +618 -0
- package/doc/zh/examples/DesktopApp.md +748 -0
- package/doc/zh/examples/DeviceService.md +546 -0
- package/doc/zh/examples/MCPServer.md +604 -0
- package/doc/zh/examples/README.md +14 -0
- package/doc/zh/examples/RestAPI.md +458 -0
- package/doc/zh/examples/ServiceCalls.md +765 -0
- package/doc/zh/examples/WebApp.md +520 -0
- package/package.json +46 -17
- package/doc/en/ActionPattern.md +0 -408
- package/doc/en/Core.md +0 -312
- package/doc/en/ORMIntegration.md +0 -341
- package/doc/zh/ActionPattern.md +0 -334
- package/doc/zh/Core.md +0 -256
- package/doc/zh/ORMIntegration.md +0 -319
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Lakutata Installers
|
|
2
|
+
|
|
3
|
+
`lakutata package installer` makes the installers of an application: the packages of the system (deb, rpm, apk, pkg,
|
|
4
|
+
msi…) that install its executable, put its program in the PATH, and can register it as a service that starts with the
|
|
5
|
+
system. They are configured in the `package.json` of the application, and need `@lakutata/builder` in the project, as do
|
|
6
|
+
the other packing commands ([Packaging](Packaging.md)).
|
|
7
|
+
|
|
8
|
+
## 1. Making installers
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm run build
|
|
12
|
+
npx lakutata package installer # the installers of the configuration, in dist/installer/
|
|
13
|
+
npx lakutata package installer --target pkg # only some of them
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
› Node.js v24.11.1 embedded
|
|
18
|
+
› Resources: texts
|
|
19
|
+
✔ dist/installer/greet-1.0.0-arm64.pkg (pkg, 36.0 MB)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
An installer holds the executable of the directory layout (`lakutata package exe --layout directory`): the program,
|
|
23
|
+
its native addons (`native/`) and its resources (`resources/`). The program is made with Node.js, or with Bun when
|
|
24
|
+
`lakutata.installer.runtime` is `"bun"`.
|
|
25
|
+
|
|
26
|
+
| Platform | Installers (default) | Their files | Made with |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| Linux (glibc) | `deb`, `rpm`, `tar.gz` (also `archlinux`) | `<name>_<version>_amd64.deb`, `<name>-<version>-1.x86_64.rpm`, `<name>-<version>-linux-amd64.tar.gz` | nFPM |
|
|
29
|
+
| Linux (musl, Alpine) | `apk`, `tar.gz` | `<name>-<version>-r0.x86_64.apk` | nFPM |
|
|
30
|
+
| macOS | `pkg`, `tar.gz` | `<name>-<version>-arm64.pkg`, `<name>-<version>-darwin-arm64.tar.gz` | `pkgbuild`, `productbuild` |
|
|
31
|
+
| Windows | `nsis`, `msi`, `zip` | `<name>-<version>-x64-setup.exe`, `<name>-<version>-x64.msi`, `<name>-<version>-win-x64.zip` | NSIS, WiX, WinSW |
|
|
32
|
+
|
|
33
|
+
- **On each platform**: the installers of a platform are made on that platform, for the architecture of the machine
|
|
34
|
+
(section 7 covers the other platforms and architectures); a `pkg` package installs on the Macs of that architecture
|
|
35
|
+
only. The executables use the C library of the system they are made on, so make the Linux packages for glibc systems
|
|
36
|
+
on a glibc system, and the `apk` packages on Alpine.
|
|
37
|
+
- **The tools** nFPM, NSIS, WiX and WinSW are downloaded once into the cache of the tool (`LAKUTATA_CACHE_DIR`).
|
|
38
|
+
`LAKUTATA_NFPM`, `LAKUTATA_NSIS`, `LAKUTATA_WIX` and `LAKUTATA_WINSW` name other copies, for machines without a
|
|
39
|
+
network, or for Linux architectures other than x64 and arm64 (nFPM has releases for these two only). macOS needs no
|
|
40
|
+
download.
|
|
41
|
+
- **The version** of the installers is the `version` of the `package.json` of the application.
|
|
42
|
+
|
|
43
|
+
## 2. The configuration
|
|
44
|
+
|
|
45
|
+
The installers are configured by `lakutata.installer` in the `package.json` of the application. The one of the
|
|
46
|
+
`service` template, with its `installer` feature (`lakutata create --with installer`):
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"bin": {"shop": "build/App.js"},
|
|
51
|
+
"scripts": {"package": "lakutata package installer"},
|
|
52
|
+
"lakutata": {
|
|
53
|
+
"icon": "../resources/logo.png",
|
|
54
|
+
"installer": {
|
|
55
|
+
"name": "shop",
|
|
56
|
+
"service": {"env": {"PORT": "8080"}},
|
|
57
|
+
"linux": {"targets": ["deb", "rpm", "tar.gz"]}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Field | Default | Meaning |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `name` | The name of the program (the first `bin`, else the name of the package without its scope), in lower case | The name of the package, of its directory and of its files: lower case letters, digits, `+`, `-` and `.` |
|
|
66
|
+
| `appId` | `name` | The identifier of the application (`com.acme.shop`): on macOS, the identifier of the package and the label of the service; on Windows, the key of its uninstaller and of its upgrades |
|
|
67
|
+
| `productName` | The `productName` of the `package.json`, else `name` | The name shown to users (in the installers, and in the programs of Windows) |
|
|
68
|
+
| `description`, `homepage`, `license` | Those of the `package.json` | The information of the packages |
|
|
69
|
+
| `maintainer` | The `author` of the `package.json` | Its maintainer (`Name <email>`); without one, the tool warns |
|
|
70
|
+
| `vendor` | None | Its vendor; on Windows, the publisher is the vendor, else the maintainer |
|
|
71
|
+
| `runtime` | `node` | `node` or `bun` for the executable; `electron` for an Electron application (section 8) |
|
|
72
|
+
| `addToPath` | `true` | Put the program in the PATH: `/usr/bin/<program>` on Linux, `/usr/local/bin/<program>` on macOS, the PATH of the machine on Windows |
|
|
73
|
+
| `service` | None | `true`, or the options of the service (section 4) |
|
|
74
|
+
| `linux.targets` | `deb`, `rpm`, `tar.gz`; on Alpine `apk`, `tar.gz` | The Linux installers (`deb`, `rpm`, `archlinux`, `tar.gz`; `apk`, `tar.gz` on Alpine) |
|
|
75
|
+
| `linux.depends`, `linux.recommends` | None | The system packages it needs and recommends (the C++ runtime library is always added) |
|
|
76
|
+
| `linux.section`, `linux.priority` | None | Its section and its priority (deb) |
|
|
77
|
+
| `mac.targets` | `pkg`, `tar.gz` | The macOS installers |
|
|
78
|
+
| `win.targets` | `nsis`, `msi`, `zip` | The Windows installers |
|
|
79
|
+
| `linux.scripts`, `mac.scripts`, `win.scripts`… | None | The scripts of the installers (section 5) |
|
|
80
|
+
| `electron` | None | The configuration of electron-builder (section 8) |
|
|
81
|
+
|
|
82
|
+
`lakutata.icon` (beside `lakutata.installer`) is a square PNG image of 256 pixels or more: the icon of the Windows
|
|
83
|
+
executable and installers, and of the Electron applications.
|
|
84
|
+
|
|
85
|
+
The configuration is checked before anything is built, the options of the installers that are not made included, and an
|
|
86
|
+
error names the field:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
error: The name of the package "Bad Name" must be lower case letters, digits, "+", "-" and "." (lakutata.installer.name)
|
|
90
|
+
error: The installer msi is not one of macOS (pkg, tar.gz)
|
|
91
|
+
error: The script lakutata.installer.mac.scripts.postinstall (scripts/missing.sh) does not exist
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 3. What is installed where
|
|
95
|
+
|
|
96
|
+
| | Linux | macOS | Windows |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| The application | `/opt/<name>` | `/usr/local/lib/<name>` | `C:\Program Files\<name>` |
|
|
99
|
+
| Its program in the PATH | `/usr/bin/<program>` | `/usr/local/bin/<program>` | the PATH of the machine |
|
|
100
|
+
| Its service | `/usr/lib/systemd/system/<service>.service`; `/etc/init.d/<service>` (OpenRC, Alpine) | `/Library/LaunchDaemons/<appId>.plist` | a Windows service: `<name>-service.exe` (WinSW) next to the program |
|
|
101
|
+
| The environment of the service | `/etc/<name>/<name>.env` | `/usr/local/etc/<name>/<name>.env` | `C:\ProgramData\<name>\<name>.env` |
|
|
102
|
+
| Its working directory (its data) | `/var/lib/<name>` | `/usr/local/var/lib/<name>` | `C:\ProgramData\<name>` |
|
|
103
|
+
| Its logs | the journal of systemd (`journalctl -u <service>`); `/var/log/<name>.log` with OpenRC | `/usr/local/var/log/<name>.log` | `C:\ProgramData\<name>\logs` |
|
|
104
|
+
| Removal | by the package manager | `sudo /usr/local/lib/<name>/uninstall` | like other Windows programs |
|
|
105
|
+
|
|
106
|
+
- **macOS does not remove packages**: the `uninstall` script installed with the application removes it, its service and
|
|
107
|
+
its link in the PATH.
|
|
108
|
+
- **The removal keeps** the data, the environment file and the user of the service, so that a new installation finds
|
|
109
|
+
them again.
|
|
110
|
+
- The archives (`tar.gz`, `zip`) only hold the application: they register no service and run no script.
|
|
111
|
+
|
|
112
|
+
## 4. The service
|
|
113
|
+
|
|
114
|
+
With `service` (`true`, or its options), the application runs when the system starts: under systemd, OpenRC on Alpine,
|
|
115
|
+
launchd on macOS, or as a Windows service (WinSW). The installation enables and starts it, an upgrade restarts it, and
|
|
116
|
+
the removal stops and disables it first.
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"lakutata": {
|
|
121
|
+
"installer": {
|
|
122
|
+
"service": {
|
|
123
|
+
"args": ["serve"],
|
|
124
|
+
"env": {"PORT": "8080", "LOG_LEVEL": "info"},
|
|
125
|
+
"restart": "on-failure"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Option | Default | Meaning |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `name` | `name` | The name of the service (systemd, OpenRC, Windows); on macOS the service is named by `appId` |
|
|
135
|
+
| `description` | The description of the package | |
|
|
136
|
+
| `args` | None | The arguments of the program |
|
|
137
|
+
| `env` | `MODE=production` | Its environment, added to `MODE=production` |
|
|
138
|
+
| `user` | A system user created by the installation: `<name>` on Linux, `_<name>` (hidden) on macOS; on Windows the virtual account `NT SERVICE\<service>` | The user it runs as; `root` (Linux, macOS), or `LocalService`, `NetworkService`, `LocalSystem` on Windows |
|
|
139
|
+
| `restart` | `on-failure` | Restart it when it fails (`on-failure`), whenever it ends (`always`), or never (`no`); on Windows and with OpenRC, `on-failure` and `always` behave alike |
|
|
140
|
+
| `autoStart` | `true` | Enable and start it at the installation; otherwise the administrator enables and starts it (`sudo systemctl enable --now <service>`; on macOS, `sudo launchctl enable system/<appId>`, then `sudo launchctl bootstrap system /Library/LaunchDaemons/<appId>.plist`) |
|
|
141
|
+
|
|
142
|
+
- **The environment file** holds `env`. Administrators can change it, and upgrades and removals keep their changes. The
|
|
143
|
+
service reads it when it starts, so restart the service after changing it (`sudo systemctl restart <service>`,
|
|
144
|
+
`sudo rc-service <service> restart`, `sudo launchctl kickstart -k system/<appId>`, `Restart-Service <service>`).
|
|
145
|
+
- **The working directory** of the service belongs to its user: write the data of the application there, with
|
|
146
|
+
relative paths or `process.cwd()`.
|
|
147
|
+
- **Stopping** the service ends the application gracefully: it receives `SIGTERM`, or on Windows a Ctrl+C, which Node.js
|
|
148
|
+
receives as `SIGINT`. systemd, launchd and WinSW give it 30 seconds to end.
|
|
149
|
+
|
|
150
|
+
## 5. Installer scripts
|
|
151
|
+
|
|
152
|
+
The installers can run scripts of the application, files of the project: to create a database, migrate data, or
|
|
153
|
+
clean up. They are given for a platform (run by all its installers), for one installer (replacing the platform's
|
|
154
|
+
script of the same moment), as native scripts of a Linux package, or as an NSIS file:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"lakutata": {
|
|
159
|
+
"installer": {
|
|
160
|
+
"linux": {
|
|
161
|
+
"scripts": {"postinstall": "build/linux/postinstall.sh", "preremove": "build/linux/preremove.sh"},
|
|
162
|
+
"rpm": {"scripts": {"postinstall": "build/linux/rpm-postinstall.sh"}},
|
|
163
|
+
"deb": {"nativeScripts": {"postinstall": "build/linux/postinst.sh"}}
|
|
164
|
+
},
|
|
165
|
+
"mac": {"scripts": {"postinstall": "build/mac/postinstall.sh"}},
|
|
166
|
+
"win": {"scripts": {"postinstall": "build/win/postinstall.ps1"}, "nsis": {"include": "build/win/installer.nsh"}}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Script | When it runs |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `preinstall` | Before the files of the application are installed |
|
|
175
|
+
| `postinstall` | After they are installed, before the service starts |
|
|
176
|
+
| `preremove` | Before the removal, once the service is stopped |
|
|
177
|
+
| `postremove` | After the files are removed |
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
#!/bin/sh
|
|
181
|
+
# build/linux/postinstall.sh: $1 is install or upgrade
|
|
182
|
+
if [ "$1" = install ]; then
|
|
183
|
+
echo "Installed in $LAKUTATA_INSTALL_DIR"
|
|
184
|
+
fi
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- **Their argument** (`$1`, `$args[0]` in PowerShell) says what the installer does: `install` or `upgrade` for
|
|
188
|
+
`preinstall` and `postinstall`, `remove` for `preremove` and `postremove` (an upgrade runs no removal script). Each
|
|
189
|
+
package manager signals an upgrade in its own way (dpkg, rpm, apk, pacman): the installers translate it.
|
|
190
|
+
- **`LAKUTATA_INSTALL_DIR`** is the directory of the application.
|
|
191
|
+
- **They run as root**; on Windows, as an administrator with NSIS and as the system account with Windows Installer.
|
|
192
|
+
When a script fails, the installation (or the removal) fails.
|
|
193
|
+
- **Linux and macOS**: the scripts run with the interpreter of their first line (`#!/bin/bash`), `/bin/sh` by default.
|
|
194
|
+
`linux.<package>.nativeScripts` run as the scripts of the package itself, with the arguments of the package manager
|
|
195
|
+
(and `preupgrade`, `postupgrade` for apk and archlinux). On macOS, the `uninstall` script runs the removal scripts.
|
|
196
|
+
- **Windows**: the scripts are PowerShell files (`.ps1`), run by Windows PowerShell. Windows Installer passes each one
|
|
197
|
+
on a command line, which limits it to about 23 KB. `win.nsis.include` is an NSIS file (`.nsh`) that may define the
|
|
198
|
+
macros `customHeader`, `customInit`, `customInstall` (at the end of the installation), `customUnInit` and
|
|
199
|
+
`customUnInstall` (at the start of the removal).
|
|
200
|
+
- **The archives** run no script: the tool warns when a script would not run in one of the installers it makes
|
|
201
|
+
(`! Some scripts of the application are not run by their installers: tar.gz (postinstall)`).
|
|
202
|
+
|
|
203
|
+
## 6. Signing
|
|
204
|
+
|
|
205
|
+
- **macOS**: the `pkg` installers are not signed, and the program they install is signed ad hoc only. The Developer ID
|
|
206
|
+
signature and the notarization, which distribution outside the machines of the organization needs, are not made yet.
|
|
207
|
+
- **Windows**: the NSIS and MSI installers are not signed: sign them with signtool before distributing them. The program
|
|
208
|
+
made with Node.js keeps the signature of `node.exe`, which no longer matches, and the tool warns of it.
|
|
209
|
+
- **Linux**: the packages are not signed.
|
|
210
|
+
|
|
211
|
+
## 7. Other platforms
|
|
212
|
+
|
|
213
|
+
The installers of the other platforms are made on them: a CI with a machine per platform, or, for Linux, a container
|
|
214
|
+
of the architecture. The templates with the `installer` feature have both scripts:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"scripts": {
|
|
219
|
+
"package:host": "npm run build && npm run package -w app -- --outdir ../../dist/installer",
|
|
220
|
+
"package:linux": "docker buildx build --file Dockerfile.installer --output type=local,dest=dist/installer ."
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`npm run package:linux` makes the Linux packages in a container (on macOS and Windows too) and copies them to
|
|
226
|
+
`dist/installer`; `docker buildx build --platform linux/arm64 ...` makes those of another architecture. Its
|
|
227
|
+
`Dockerfile.installer`:
|
|
228
|
+
|
|
229
|
+
```dockerfile
|
|
230
|
+
FROM node:22-bookworm AS build
|
|
231
|
+
WORKDIR /project
|
|
232
|
+
COPY . .
|
|
233
|
+
RUN (npm ci --no-audit --no-fund || npm install --no-audit --no-fund) && npm run build && npm run package -w app -- --outdir /installer
|
|
234
|
+
|
|
235
|
+
FROM scratch
|
|
236
|
+
COPY --from=build /installer /
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The `apk` packages are made the same way on `node:22-alpine`, with Bun when the application has native addons (the
|
|
240
|
+
executables of Node.js do not load them on musl). A CI matrix makes the rest, for example with GitHub Actions:
|
|
241
|
+
|
|
242
|
+
```yaml
|
|
243
|
+
jobs:
|
|
244
|
+
installers:
|
|
245
|
+
strategy:
|
|
246
|
+
matrix:
|
|
247
|
+
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
|
|
248
|
+
runs-on: ${{ matrix.os }}
|
|
249
|
+
steps:
|
|
250
|
+
- uses: actions/checkout@v4
|
|
251
|
+
- uses: actions/setup-node@v4
|
|
252
|
+
with:
|
|
253
|
+
node-version: 22
|
|
254
|
+
- run: npm ci
|
|
255
|
+
- run: npm run package:host
|
|
256
|
+
- uses: actions/upload-artifact@v4
|
|
257
|
+
with:
|
|
258
|
+
name: installers-${{ runner.os }}-${{ runner.arch }}
|
|
259
|
+
path: dist/installer/
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 8. Electron applications
|
|
263
|
+
|
|
264
|
+
With `"runtime": "electron"`, the installers are made by electron-builder, which the project installs with `electron`.
|
|
265
|
+
The main process is bundled (Electron left out), and its native addons and resources (`lakutata.resources`: the
|
|
266
|
+
renderer and the preload script, read through `resourcesPath()`) go into the resources of Electron. The `electron-react`
|
|
267
|
+
and `electron-vue` templates are configured this way.
|
|
268
|
+
|
|
269
|
+
- **The installers** are targets of electron-builder: `deb` on Linux, `dmg` on macOS and `nsis` on Windows by default
|
|
270
|
+
(`linux.targets`, `mac.targets`, `win.targets`). The name of the files is the name of the package, without its scope.
|
|
271
|
+
- **`electron`** is merged into the configuration of electron-builder: icons, file associations, signing, updates.
|
|
272
|
+
`name`, `appId`, `productName`, the description, the maintainer, the homepage and the license are given to it too.
|
|
273
|
+
- **An Electron application is not a service**: it runs in the session of its user, and `service` is refused.
|
|
274
|
+
- **The scripts** run in the `deb` and `rpm` packages (all four), the `pkg` (`preinstall`, `postinstall`) and the
|
|
275
|
+
`nsis` installer (`preinstall`, `postinstall`, `preremove`); the other targets run none.
|
|
276
|
+
- **Native addons** are copied as they are installed: electron-builder does not rebuild them. The addons built on
|
|
277
|
+
Node-API run as they are; install the Electron builds of the others before packing (`electron-rebuild`)
|
|
278
|
+
([NativeAddons](NativeAddons.md), section 4).
|
|
279
|
+
- On Linux arm64, the deb and rpm packages need the fpm of the system (`gem install fpm`, `USE_SYSTEM_FPM=true`), as the
|
|
280
|
+
`Dockerfile.installer` of the Electron templates installs it.
|
package/doc/en/Logger.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Lakutata Logging
|
|
2
|
+
|
|
3
|
+
Every application of `lakutata` has a logger: the `log` component, preset by the package (`Logger`, from
|
|
4
|
+
`lakutata/com/logger`). It writes readable lines, or the JSON lines of pino for a log collector, to the standard output
|
|
5
|
+
or to other streams. The framework logs through it too.
|
|
6
|
+
|
|
7
|
+
## 1. The `log` component
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import {Application, Component} from 'lakutata'
|
|
11
|
+
import {Inject} from 'lakutata/decorator/di'
|
|
12
|
+
import {Logger} from 'lakutata/com/logger'
|
|
13
|
+
|
|
14
|
+
class Orders extends Component {
|
|
15
|
+
@Inject('log')
|
|
16
|
+
protected readonly log: Logger
|
|
17
|
+
|
|
18
|
+
public pay(orderId: number, amount: number): void {
|
|
19
|
+
this.log.info({orderId: orderId}, 'order %d paid: %s', orderId, amount.toFixed(2))
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Application
|
|
24
|
+
.run(() => ({
|
|
25
|
+
id: 'shop.app',
|
|
26
|
+
name: 'Shop',
|
|
27
|
+
components: {
|
|
28
|
+
//The options of the preset logger: no class needed
|
|
29
|
+
log: {level: 'info', pretty: process.env.NODE_ENV !== 'production'},
|
|
30
|
+
orders: {class: Orders}
|
|
31
|
+
}
|
|
32
|
+
}))
|
|
33
|
+
.onLaunched(async (app: Application): Promise<void> => {
|
|
34
|
+
const orders: Orders = await app.getObject<Orders>('orders')
|
|
35
|
+
orders.pay(42, 9.9)
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- Inject it by its name, `log`. Type it as `Logger`, or as `ILogger` (from `lakutata`), the interface that every
|
|
40
|
+
logger of an application implements.
|
|
41
|
+
- The handlers of the application (`onLaunched`, `onDone`, `onUncaughtException`, `onFatalException`) receive it as
|
|
42
|
+
their second argument (see [Application](Application.md), section 2).
|
|
43
|
+
- Its options go into the `log` entry of `components`, without `class` (section 4).
|
|
44
|
+
|
|
45
|
+
## 2. Writing a line
|
|
46
|
+
|
|
47
|
+
| Method | Level | For |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `fatal()` | 60 | A failure the application cannot go on after; only `Logger` has it, not `ILogger` |
|
|
50
|
+
| `error()` | 50 | A failure that someone must act on |
|
|
51
|
+
| `warn()` | 40 | An unexpected event the application recovers from |
|
|
52
|
+
| `info()` | 30 | The normal course of the application |
|
|
53
|
+
| `debug()` | 20 | The details for debugging |
|
|
54
|
+
| `trace()` | 10 | Finer details |
|
|
55
|
+
|
|
56
|
+
Each method takes an optional object, whose properties are added to the line, then a message, then the values of its
|
|
57
|
+
placeholders:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import {Logger} from 'lakutata/com/logger'
|
|
61
|
+
|
|
62
|
+
declare const log: Logger
|
|
63
|
+
|
|
64
|
+
//%s string, %d and %f number, %i integer, %o, %O and %j JSON, %% a percent sign
|
|
65
|
+
log.info('user %s signed in from %s', 'alice', '10.0.0.7')
|
|
66
|
+
//The properties of the object are added to the line
|
|
67
|
+
log.warn({orderId: 42, retries: 3}, 'payment retried %d times', 3)
|
|
68
|
+
//An error: written under "err"; its message is the message of the line when none is given
|
|
69
|
+
log.error(new Error('connection refused'), 'payment failed')
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- A value without a placeholder is dropped: `log.info('done', 42)` writes `done`.
|
|
73
|
+
- An error is written under `err`: its type, its message followed by the messages of its causes, its stack followed by
|
|
74
|
+
the stacks of its causes, and its own properties (such as the `errno` and `statusCode` of an exception).
|
|
75
|
+
- An HTTP request is written under `req` (method, URL, headers, remote address), an HTTP response under `res`. Their
|
|
76
|
+
`Authorization`, `Cookie`, `Set-Cookie` and `Proxy-Authorization` headers are written as `[Redacted]`.
|
|
77
|
+
- Nothing else is redacted, and there are no child loggers: log the fields you need, not tokens or whole objects that
|
|
78
|
+
hold them, and give the context (a request id) in the object of each call.
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
[13:44:12.447] ERROR (Shop/24236): payment failed
|
|
82
|
+
err: {
|
|
83
|
+
"type": "PaymentFailedException",
|
|
84
|
+
"message": "Payment of order 7 failed: socket hang up",
|
|
85
|
+
"stack":
|
|
86
|
+
PaymentFailedException: Payment of order 7 failed
|
|
87
|
+
at pay (file:///app/build/Orders.js:48:17)
|
|
88
|
+
caused by: Error: socket hang up
|
|
89
|
+
at charge (file:///app/build/Gateway.js:12:9)
|
|
90
|
+
"appId": "shop.app",
|
|
91
|
+
"appName": "Shop",
|
|
92
|
+
"statusCode": 502,
|
|
93
|
+
"errMsg": "Payment of order 7 failed",
|
|
94
|
+
"err": "PaymentFailedException",
|
|
95
|
+
"errno": "E_PAYMENT_FAILED"
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 3. Readable lines or JSON
|
|
100
|
+
|
|
101
|
+
By default (`pretty: true`), the lines are readable, with the time in the local time zone, the level, the name of the
|
|
102
|
+
application, its process id, the message, then one indented line per property:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
[13:45:04.246] INFO (Shop/24297): order 42 paid: 9.90
|
|
106
|
+
orderId: 42
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
With `pretty: false`, each line is a JSON object, in the format of pino: `level`, `time` (milliseconds since the
|
|
110
|
+
epoch), `pid`, `hostname`, `name` (the name of the application), the properties of the object, and `msg`. A log
|
|
111
|
+
collector reads these lines; use them in production.
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
{"level":30,"time":1792215904479,"pid":24300,"hostname":"api-1","name":"Shop","orderId":42,"msg":"order 42 paid: 9.90"}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 4. Options
|
|
118
|
+
|
|
119
|
+
| Option | Default | Meaning |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `level` | `'trace'` | The lowest level written: `'trace'`, `'debug'`, `'info'`, `'warn'`, `'error'`, `'fatal'` (only the lines of `fatal()`) or `'silent'` (nothing) |
|
|
122
|
+
| `pretty` | `true` | Readable lines; `false` writes the JSON lines |
|
|
123
|
+
| `colorize` | on a terminal | Colors the readable lines written to the standard output |
|
|
124
|
+
| `destinations` | `[process.stdout]` | The streams that receive the lines (section 5) |
|
|
125
|
+
| `sync` | `false` | Writes each line at once, instead of in batches (section 6) |
|
|
126
|
+
| `redactedHeaders` | `['authorization', 'cookie', 'set-cookie', 'proxy-authorization']` | The headers written as `[Redacted]`; the list replaces the default one, and `[]` redacts none |
|
|
127
|
+
|
|
128
|
+
By default, the readable lines are colored when the standard output is a terminal and the `NO_COLOR` environment
|
|
129
|
+
variable is not set: a standard output redirected to a file or read by a collector gets no escape codes.
|
|
130
|
+
`colorize: true` colors it in any case.
|
|
131
|
+
|
|
132
|
+
## 5. Destinations
|
|
133
|
+
|
|
134
|
+
Each stream of `destinations` receives every line:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import {createWriteStream} from 'node:fs'
|
|
138
|
+
import {Application} from 'lakutata'
|
|
139
|
+
|
|
140
|
+
Application.run(() => ({
|
|
141
|
+
id: 'shop.app',
|
|
142
|
+
name: 'Shop',
|
|
143
|
+
components: {
|
|
144
|
+
log: {
|
|
145
|
+
pretty: false,
|
|
146
|
+
//The standard output, and a file the lines are appended to
|
|
147
|
+
destinations: [process.stdout, createWriteStream('shop.log', {flags: 'a'})]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}))
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- The logger does not close the streams, and does not rotate the files: leave that to the system (logrotate, the
|
|
154
|
+
collector of the containers).
|
|
155
|
+
- An application whose standard output belongs to a protocol (JSON-RPC over stdio, MCP over stdio) writes its logs to
|
|
156
|
+
the standard error: `log: {class: Logger, destinations: [process.stderr]}` (see [ServiceCalls](ServiceCalls.md),
|
|
157
|
+
section 4).
|
|
158
|
+
|
|
159
|
+
## 6. Buffering
|
|
160
|
+
|
|
161
|
+
The lines are written in batches: at the next turn of the event loop, or once 16 KB are waiting. The waiting lines are
|
|
162
|
+
also written when the process exits and when the logger is destroyed.
|
|
163
|
+
|
|
164
|
+
- `sync: true` writes each line at once.
|
|
165
|
+
- `flush()` writes the waiting lines now, such as before a `process.exit()`.
|
|
166
|
+
- `fatal()` writes its line and the waiting ones at once.
|
|
167
|
+
- The standard output, the standard error and the files are written synchronously, so the lines keep their order.
|
|
168
|
+
|
|
169
|
+
## 7. Another logger
|
|
170
|
+
|
|
171
|
+
The `log` component can be replaced by a class of your own: a component that implements `ILogger`, declared with
|
|
172
|
+
`class`. The framework and the handlers of the application then log through it.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import {Application, Component, type ILogger} from 'lakutata'
|
|
176
|
+
|
|
177
|
+
class ConsoleOnlyLogger extends Component implements ILogger {
|
|
178
|
+
public error(...args: unknown[]): void {
|
|
179
|
+
console.error(...args)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public warn(...args: unknown[]): void {
|
|
183
|
+
console.warn(...args)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public info(...args: unknown[]): void {
|
|
187
|
+
console.info(...args)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public debug(...args: unknown[]): void {
|
|
191
|
+
console.debug(...args)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public trace(...args: unknown[]): void {
|
|
195
|
+
console.debug(...args)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
Application.run(() => ({
|
|
200
|
+
id: 'tool.app',
|
|
201
|
+
name: 'Tool',
|
|
202
|
+
components: {
|
|
203
|
+
log: {class: ConsoleOnlyLogger}
|
|
204
|
+
}
|
|
205
|
+
}))
|
|
206
|
+
```
|