Compare commits

...
11 Commits
Author SHA1 Message Date
github-actions[bot] 029ecb6b39 Bump versions for v12.97 2026-11-06 02:01:17 +00:00
Lauri Ojansivu 49c309b324 Prepare v12.97 release 2026-11-06 04:00:13 +02:00
Lauri Ojansivu e09267cc90 CHANGELOG: BSD smoke test cleanup and snap Launchpad retries.
Thanks to xet7 !
2026-11-06 03:52:17 +02:00
Lauri Ojansivu b46f155fa3 Fix v12.96 release builds: BSD smoke test cleanup and snap Launchpad retries.
openbsd-arm64 bundle: releases/bsd/smoke.sh returned OK with WeKan and
FerretDB still running, because start-wekan.sh runs its TERM trap only
after the foreground node exits. FerretDB then removed wekan.sqlite-wal
and -shm while the VM copyback rsync walked the workspace, and rsync exit
24 failed a bundle that had built and started. smoke.sh now stops node
and FerretDB, waits until both are gone (SIGKILL after 30 s) and removes
its work directory before returning.

s390x snap: all three attempts met Launchpad "HTTP Error 503: Service
Unavailable" / "Error: Timeout", but snapcraft printed only its last
exception, "Could not find repository at path ...", and the 503 was only
in snapcraft's execution log, which infra_failure() did not read. The
attempts were counted as WeKan failures instead of earning the extra
retries. infra_failure() now reads this attempt's execution logs, and
also counts the push refusal "You do not have permission to create
refs/..." from the repository those timed-out requests half-created.

Tests reproduce both from the v12.96 logs, with negatives: an earlier
attempt's execution log and a log with no outage do not count, and no
process or smoke-test file is left behind when smoke.sh returns.

Thanks to xet7 !
2026-11-06 03:52:04 +02:00
Lauri Ojansivu 0c0288a6c5 CHANGELOG: build.sh and releases scripts work on Arch Linux and Omarchy.
Thanks to xet7 !
2026-11-06 01:57:16 +02:00
Lauri Ojansivu c61a1b7f41 build.sh and releases/*.sh work on Arch Linux and Omarchy.
Omarchy (https://omarchy.org) is Arch with Hyprland: pacman, the Arch
repositories and yay for the AUR. The shared detector in
releases/ensure-tools.sh now names Omarchy and the other Arch derivatives
(EndeavourOS, CachyOS, Manjaro) explicitly, and what the scripts did there
is fixed:

- No more `pacman -Sy <package>`: that is a partial upgrade, which Arch does
  not support. _et_pacman installs with `-S --needed`, does a full `-Syu`
  only when pacman has never synced (a fresh archlinux container) or when
  WEKAN_PACMAN_SYU=1 asks for it, and otherwise says to update first, naming
  omarchy-update on Omarchy.
- 7zip instead of p7zip, which Arch has replaced.
- snapcraft: snapd from the AUR with yay or paru (never as root), then
  snapd.socket, /snap and the snapcraft snap, instead of only an error.
- The SSH server menu entries use sshd.service outside Debian/Ubuntu, where
  the unit is not called ssh.
- Playwright: `install --with-deps` runs apt-get, so outside Debian/Ubuntu
  the browser libraries come from pacman (or dnf) and the browsers are
  installed without --with-deps. WebKit, which Playwright builds for
  Debian/Ubuntu only, runs in the Playwright Docker image on Arch when
  Docker is there.
- The en_US.UTF-8 locale note gives the Arch/Omarchy steps too.

tests/archOmarchyScripts.test.cjs drives the helpers offline with stubbed
sudo/pacman/yay, and its negative test fails if any tracked shell script
does a `pacman -Sy` partial upgrade or installs p7zip.

Thanks to xet7 !
2026-11-06 01:57:00 +02:00
github-actions[bot] 9ce35869a7 Bump versions for v12.96 2026-11-05 19:41:40 +00:00
Lauri Ojansivu 2017d07e37 Prepare v12.96 release 2026-11-05 21:40:23 +02:00
Lauri Ojansivu 9ea5c81932 CHANGELOG: repoint commit links after history moved.
A rebase rewrote the commits these entries link to, so the links named commits
that no longer exist and would have 404ed on GitHub. Repointed by
releases/fix-changelog-hashes.sh, which only touches a link no ref in this clone
can reach.

Thanks to xet7 !
2026-11-05 21:39:54 +02:00
Lauri Ojansivu 45ccdd9c3c CHANGELOG: the bundle starts again, and v12.95 records that its bundle did not.
Thanks to xet7 !
2026-11-05 21:39:02 +02:00
Lauri Ojansivu 5a21b074d4 The bundle starts again: nodemailer 10 in meteor/email gets back the lib/ paths it was built against.
releases/bump-bundle-npm-deps.mjs raises the nodemailer inside Meteor's email
package from 8.0.3 to 10.3.1 (dad03959d9), and nodemailer 10 moved lib/ to
dist/cjs/. packages/email.js, compiled against 8.0.3, links
lib/nodemailer.js and lib/well-known/index.js and Npm.requires
lib/mail-composer by path, so v12.95's build-amd64 failed its smoke boot:
"Cannot find module .../meteor/email/node_modules/nodemailer/lib/nodemailer.js".

releases/bundle-npm-security-bumps.json now shims nodemailer the way it
already shims uuid 11: lib/nodemailer.js and every lib/<name> directory the
exports map lists are written back, each forwarding to dist/cjs, and
lib/package.json marks lib/ as CommonJS because nodemailer is "type":
"module". A fresh local bundle taken through build-amd64's install, bump,
trim and prune steps now passes releases/bundle-smoke-boot.sh.

tests/bundleNpmSecurityBumps.test.cjs applies the shim to node_modules'
nodemailer 10, requires the recorded paths, checks that every exported
lib/<name> has a forward to the same module, and fails without the CommonJS
marker.

Thanks to xet7 !
2026-11-05 21:39:02 +02:00
25 changed files with 642 additions and 64 deletions

No files matched your search

+17 -1
View File
@@ -5070,13 +5070,29 @@ jobs:
[ -e "$1" ] || return 1
case "$logs_before" in *" $1 "*) [ "$1" -nt "$attempt_marker" ] ;; *) return 0 ;; esac
}
# snapcraft's execution logs. v12.96 s390x: every attempt printed only
# "Could not find repository at path ...", because snapcraft reports
# the LAST exception; the cause before it - "HTTP Error 503: Service
# Unavailable" / "Error: Timeout" creating that repository - was in
# the "Full execution log" alone. Unread, three Launchpad outages
# counted as three WeKan failures and the arch was not published.
snapcraft_log_dir="$HOME/.local/state/snapcraft/log"
infra_failure() { # did THIS attempt fail on Launchpad, not on WeKan?
grep -qsE 'HTTP Error 5[0-9][0-9]|Error: Timeout|Service Unavailable' "$remote_log" && return 0
# The same v12.96 s390x run's last attempt: the repository the
# timed-out requests had half-created refused our push with
# "You do not have permission to create refs/heads/main" - the
# same credentials pushed every other architecture in that run.
grep -qsE 'remote: You do not have permission to create refs/' "$remote_log" && return 0
local log
for log in snapcraft-wekan-*.txt; do
this_attempts_log "$log" || continue
grep -qsE 'RPC failed;|GnuTLS recv error|fatal: early EOF|Gateway error \[IP: ' "$log" && return 0
done
for log in "$snapcraft_log_dir"/*.log; do
this_attempts_log "$log" || continue
grep -qsE 'HTTP Error 5[0-9][0-9]|Error: Timeout|Service Unavailable' "$log" && return 0
done
return 1
}
# Set once an attempt ends with Launchpad reporting the build as
@@ -5099,7 +5115,7 @@ jobs:
# earlier attempts' logs stay in the directory for the diagnostics
# below, but must not be printed again as if they were this one's.
touch "$attempt_marker"
logs_before=" $(ls snapcraft-wekan-*.txt 2>/dev/null | tr '\n' ' ')"
logs_before=" $(ls snapcraft-wekan-*.txt "$snapcraft_log_dir"/*.log 2>/dev/null | tr '\n' ' ')"
if [ "$fresh_next" = 1 ]; then
echo "The previous attempt's Launchpad build finished without a snap; submitting a new build instead of recovering it."
run_remote_build && rc=0 || rc=$?
+95
View File
@@ -124,6 +124,94 @@ its reason.
</details>
# v12.97 2026-11-06 WeKan ® release
**In short:** `build.sh` and the `releases/` scripts now work on **Arch Linux**
and **Omarchy**, and the release builds no longer lose the **OpenBSD arm64
bundle** to a smoke test that left WeKan running, or the **s390x snap** to
Launchpad outages that were counted as build failures.
This release fixes the following release build bugs:
**Release builds** - bundles and snaps that built correctly but failed the job.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/b46f155fa3">The BSD smoke test stops WeKan and FerretDB and removes its files before returning</a>. Thanks to xet7.</summary>
v12.96 openbsd-arm64: `releases/bsd/smoke.sh` returned OK with WeKan and
FerretDB still running, because `start-wekan.sh` runs its TERM trap only after
the foreground node exits. FerretDB then removed `wekan.sqlite-wal` and `-shm`
while the VM's copyback rsync walked the workspace, and rsync exit 24 failed a
bundle that had built and started. The smoke test now stops both, waits until
they are gone and deletes its work directory. `tests/bsdBundles.test.cjs`
runs it against a launcher of `start-wekan.sh`'s shape and fails if a process
or a smoke-test file is left behind.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/b46f155fa3">Launchpad outages seen only in snapcraft's execution log earn snap retries</a>. Thanks to xet7.</summary>
v12.96 s390x: all three attempts met Launchpad's `HTTP Error 503` /
`Error: Timeout`, but snapcraft printed only its last exception, "Could not
find repository at path ...", so the outage was counted as three WeKan
failures. `infra_failure()` now reads this attempt's snapcraft execution logs
too, and counts the "You do not have permission to create refs/" push refusal
from the half-created repository. `tests/snapLaunchpadInfraRetry.test.cjs`
pins both, and its negatives an earlier attempt's log and one with no outage.
</details>
and has the following developer-tooling fix:
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c61a1b7f41">build.sh and releases scripts work on Arch Linux and Omarchy</a>. Thanks to xet7.</summary>
Omarchy is Arch with Hyprland: pacman, the Arch repositories and yay for the
AUR. `releases/ensure-tools.sh` names it and the other Arch derivatives
explicitly. Packages are installed with `pacman -S --needed` instead of the
unsupported `-Sy` partial upgrade; a full `-Syu` runs only on a never-synced
pacman or with `WEKAN_PACMAN_SYU=1`, and a failed install says to update first
(`omarchy-update` on Omarchy). 7zip replaces Arch's retired p7zip, snapcraft
gets snapd from the AUR with yay or paru, the SSH server menu entries use
`sshd.service` outside Debian/Ubuntu, and Playwright's browser libraries come
from pacman (or dnf) instead of the apt-only `--with-deps`, with WebKit in the
Playwright Docker image on Arch. `tests/archOmarchyScripts.test.cjs` drives
the helpers offline, and its negative test fails if any shell script does a
`pacman -Sy` partial upgrade or installs p7zip.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.96 2026-11-05 WeKan ® release
**In short:** The release **bundle starts again**: v12.95's build stopped on
boot because the **nodemailer** 10 raised inside Meteor's email package no
longer had the files that package was built against.
This release fixes the following bug:
<details>
<summary><a href="https://github.com/wekan/wekan/commit/5a21b074d4">The bundle starts again, with nodemailer 10 inside Meteor's email package</a>. Thanks to xet7.</summary>
v12.95 raised the nodemailer in `meteor/email` from 8.0.3 to 10.3.1, and
nodemailer 10 moved `lib/` to `dist/cjs/`. `packages/email.js`, compiled
against 8.0.3, links `lib/nodemailer.js` and `lib/well-known/index.js` and
`Npm.require`s `lib/mail-composer` by path, so build-amd64 failed its smoke
boot. `releases/bundle-npm-security-bumps.json` now shims nodemailer as it
already shims uuid 11: `lib/nodemailer.js` and every `lib/<name>` directory the
exports map lists are written back, forwarding to `dist/cjs`, with a
`lib/package.json` marking them CommonJS because nodemailer is `"type":
"module"`. A fresh local bundle taken through build-amd64's install, bump, trim
and prune steps passes `releases/bundle-smoke-boot.sh`.
`tests/bundleNpmSecurityBumps.test.cjs` requires the recorded paths through the
shim, checks every exported `lib/<name>`, and fails without the CommonJS marker.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.95 2026-11-05 WeKan ® release
**In short:** **Continuous backup** now runs on one server at a time, with a
@@ -313,6 +401,13 @@ predates them. The runtime image installs no compiler (`gcc-14-base` is Debian's
one-file package), so the multi-stage build the report asks for is already how
it is built. Run against a copy of the published image's bundle, the bumper
raised all of the above and every raised copy loaded.
**This release's bundle did not start.** "Every raised copy loaded" was checked
by loading each package, not by booting the bundle: nodemailer 10 moved `lib/`
to `dist/cjs/`, Meteor's `email` package still links the `lib/` paths of 8.0.3,
and build-amd64 failed its smoke boot with "Cannot find module
.../meteor/email/node_modules/nodemailer/lib/nodemailer.js". The fix is in the
next release.
`tests/bundleNpmSecurityBumps.test.cjs` pins the minimums, the per-major
rule and the shim, and runs the bumper with `--dry-run` on a bundle of
body-parser, uuid and nodemailer copies at, below and above their minimums.
+17 -15
View File
@@ -5,6 +5,23 @@ each one changed and the project status are in
Written by `node releases/changelog-short.mjs` from CHANGELOG-big.md:
make changes there.
# v12.97 2026-11-06 WeKan ® release
`build.sh` and the `releases/` scripts now work on **Arch Linux**
and **Omarchy**, and the release builds no longer lose the **OpenBSD arm64
bundle** to a smoke test that left WeKan running, or the **s390x snap** to
Launchpad outages that were counted as build failures.
[All changes in v12.97](CHANGELOG-big.md#v1297-2026-11-06-wekan--release)
# v12.96 2026-11-05 WeKan ® release
The release **bundle starts again**: v12.95's build stopped on
boot because the **nodemailer** 10 raised inside Meteor's email package no
longer had the files that package was built against.
[All changes in v12.96](CHANGELOG-big.md#v1296-2026-11-05-wekan--release)
# v12.95 2026-11-05 WeKan ® release
**Continuous backup** now runs on one server at a time, with a
@@ -34,18 +51,3 @@ more databases: **pgrust** (PostgreSQL in Rust) and **TiDB**. Noria, YugabyteDB
and CockroachDB were looked at and cannot be used, for reasons the README gives.
[All changes in v12.93](CHANGELOG-big.md#v1293-2026-11-04-wekan--release)
# v12.92 2026-11-03 WeKan ® release
A missing **REST checklist** answers a JSON 404 instead of an
empty 500.
[All changes in v12.92](CHANGELOG-big.md#v1292-2026-11-03-wekan--release)
# v12.91 2026-11-03 WeKan ® release
A **GitHub CodeQL** finding in a test is fixed, and the guard
against
replacing text with itself now also sees patterns with a `^` or `$` anchor.
[All changes in v12.91](CHANGELOG-big.md#v1291-2026-11-03-wekan--release)
+1 -1
View File
@@ -38,7 +38,7 @@ FROM --platform=$BUILDPLATFORM debian:trixie AS bundle
ARG TARGETARCH
ARG TARGETVARIANT
ARG VERSION=12.95
ARG VERSION=12.97
ARG DEBIAN_FRONTEND=noninteractive
COPY --chmod=755 releases/check-telemetry.py /tmp/check-telemetry.py
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v12.95.0"
appVersion: "v12.97.0"
files:
userUploads:
- README.md
+26 -7
View File
@@ -1,9 +1,10 @@
#!/bin/bash
if [ -n "${ZSH_VERSION:-}" ]; then exec /bin/bash "$0" "$@"; fi
echo "Recommended for development: Newest Debian or Ubuntu amd64 based distro, directly to SSD disk or dual boot, not VM. Works fast."
echo "Recommended for development: Newest Debian, Ubuntu, Fedora or Arch/Omarchy amd64 based distro, directly to SSD disk or dual boot, not VM. Works fast."
echo "Note1: If you use other locale than en_US.UTF-8 , you need to additionally install en_US.UTF-8"
echo " with 'sudo dpkg-reconfigure locales' , so that MongoDB works correctly."
echo " so that MongoDB works correctly: Debian/Ubuntu 'sudo dpkg-reconfigure locales',"
echo " Arch/Omarchy: uncomment en_US.UTF-8 in /etc/locale.gen and run 'sudo locale-gen'."
echo " You can still use any other locale as your main locale."
echo "Note2: Console output is also logged to the operation-specific path printed below."
echo "Note3: All logs use .tools/log/<operation>/YYYY-MM-DD_HH-MM-SS/."
@@ -605,6 +606,14 @@ function browser_needs_docker(){
if [ "$PLATFORM_OS" = "linux" ] && [ "$PLATFORM_ARCH" = "arm64" ]; then
return 0
fi
# Playwright builds WebKit for Debian/Ubuntu only. On Arch and Omarchy its
# WebKit is linked against other ICU/libjpeg/libwebp versions than the
# rolling release has and does not launch, while Chromium and Firefox do.
# So WebKit alone goes to the Docker image there, when Docker is installed.
if [ "$browser" = webkit ] && [ "$PLATFORM_OS" = "linux" ] && \
[ "$(_et_linux_family)" = arch ] && docker_available; then
return 0
fi
return 1
}
@@ -825,8 +834,17 @@ function install_playwright_browsers(){
browser_needs_docker "$b" || nativeList="$nativeList $b"
done
if [ -n "$nativeList" ]; then
echo "Installing native Playwright browsers:$nativeList (with system deps; may use sudo)."
( cd "$pwdir" && export HOME="$reporoot/.tools" && npx playwright install --with-deps $nativeList )
if [ "$PLATFORM_OS" = "linux" ] && [ "$(_et_linux_family)" != debian ]; then
# `--with-deps` runs apt-get, which only Debian/Ubuntu have. Elsewhere
# install the libraries the browsers load with the native package
# manager, then the browsers themselves without --with-deps.
echo "Installing native Playwright browsers:$nativeList (system deps from the distribution's packages; may use sudo)."
ensure_playwright_system_deps
( cd "$pwdir" && export HOME="$reporoot/.tools" && npx playwright install $nativeList )
else
echo "Installing native Playwright browsers:$nativeList (with system deps; may use sudo)."
( cd "$pwdir" && export HOME="$reporoot/.tools" && npx playwright install --with-deps $nativeList )
fi
else
echo "All browsers are configured to run via Docker; skipping native browser install."
fi
@@ -2732,8 +2750,8 @@ RELEASE_SCRIPTS=( "Release|Release All: audit, commit, push and build with Actio
"Git and repo|Migrate a MongoDB database to FerretDB (--help first)|releases/migrate-mongodb-to-ferretdb.mjs|Arguments, e.g. --help||"
"Git and repo|Export local Git history to a new Fossil repository|releases/fossil.sh|||fossil-export"
"Git and repo|Open the local Fossil repository UI|releases/fossil-ui.sh|||fossil-ui"
"Server and VM|Enable and start the SSH server|!sudo systemctl enable ssh && sudo systemctl start ssh||linux|ssh-start"
"Server and VM|Disable and stop the SSH server|!sudo systemctl disable ssh && sudo systemctl stop ssh||linux|ssh-stop"
"Server and VM|Enable and start the SSH server|!sudo systemctl enable \"\$(_et_ssh_unit)\" && sudo systemctl start \"\$(_et_ssh_unit)\"||linux|ssh-start"
"Server and VM|Disable and stop the SSH server|!sudo systemctl disable \"\$(_et_ssh_unit)\" && sudo systemctl stop \"\$(_et_ssh_unit)\"||linux|ssh-stop"
"Server and VM|Enable the ufw firewall|!sudo ufw enable||linux|ufw-enable"
"Server and VM|Disable the ufw firewall|!sudo ufw disable||linux|ufw-disable"
"Server and VM|Remove the Multipass VM|!multipass stop --all && multipass delete --all && multipass purge && sudo snap remove multipass||linux|multipass-remove"
@@ -2999,7 +3017,8 @@ for _once in 1; do
sudo apk add --no-cache bash build-base git curl wget p7zip zip unzip file npm
;;
arch)
sudo pacman -Sy --needed --noconfirm base-devel git curl wget p7zip zip unzip file npm
# Arch and Omarchy: no `pacman -Sy` partial upgrade (see _et_pacman).
_et_pacman base-devel git curl wget 7zip zip unzip file npm
;;
fedora)
sudo dnf group install -y development-tools
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
Right click and download files 1-4:
1. [wekan-12.95-win64.zip](https://github.com/wekan/wekan/releases/download/v12.95/wekan-12.95-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
1. [wekan-12.97-win64.zip](https://github.com/wekan/wekan/releases/download/v12.97/wekan-12.97-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
2. [node.exe](https://nodejs.org/dist/latest-v26.x/win-x64/node.exe)
@@ -22,7 +22,7 @@ Right click and download files 1-4:
6. Double click `mongodb-windows-x86_64-7.0.34-signed.msi` . In installer, uncheck downloading MongoDB compass.
7. Unzip `wekan-12.95-win64.zip` , inside it is directory `bundle`, to it copy other files:
7. Unzip `wekan-12.97-win64.zip` , inside it is directory `bundle`, to it copy other files:
```
bundle (directory)
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "wekan",
"version": "v12.95.0",
"version": "v12.97.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wekan",
"version": "v12.95.0",
"version": "v12.97.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v12.95.0",
"version": "v12.97.0",
"description": "Open-Source kanban",
"private": true,
"repository": {
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf8" />
<title>WeKan REST API v12.95</title>
<title>WeKan REST API v12.97</title>
<!-- needed for adaptive design -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@@ -484,11 +484,11 @@ data-styled.g658[id="sc-hHttOM"]{content:"dKSNwp,"}/*!sc*/
<body>
<div id="redoc"><div hidden=""><script>try{var m=localStorage.getItem("redoc.colorMode");if(m!=="light"&&m!=="dark")m=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";document.documentElement.classList.add(m)}catch(e){}</script></div><div style="--navbar-height:0px;--navbar-stack-height:0px" class="sc-kIbBWU fFQMWf"><div class="sc-cjUTkN dGsweu menu-content"><div class="sc-jqwVmx jjzLa-d"><div data-testid="search-trigger" class="sc-cToEys dffrbr"><div class="sc-cRcubC ddjiE"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" data-component-name="icons/SearchIcon/SearchIcon" class="sc-cHazmW gTzQi"><path d="M14.5 13.7931L10.724 10.017C11.6313 8.9277 12.0838 7.5305 11.9872 6.11608C11.8907 4.70165 11.2525 3.37891 10.2055 2.423C9.15857 1.4671 7.78336 0.951637 6.36602 0.983845C4.94867 1.01605 3.5983 1.59345 2.59582 2.59593C1.59335 3.5984 1.01595 4.94877 0.983738 6.36612C0.95153 7.78347 1.46699 9.15867 2.4229 10.2057C3.3788 11.2526 4.70155 11.8908 6.11597 11.9873C7.53039 12.0839 8.92759 11.6314 10.0169 10.7241L13.793 14.5001L14.5 13.7931ZM2.00001 6.50012C2.00001 5.6101 2.26393 4.74007 2.7584 4.00005C3.25286 3.26003 3.95567 2.68325 4.77794 2.34266C5.6002 2.00207 6.505 1.91295 7.37792 2.08658C8.25083 2.26022 9.05266 2.6888 9.68199 3.31814C10.3113 3.94747 10.7399 4.7493 10.9135 5.62221C11.0872 6.49513 10.9981 7.39993 10.6575 8.22219C10.3169 9.04446 9.7401 9.74726 9.00008 10.2417C8.26006 10.7362 7.39003 11.0001 6.50001 11.0001C5.30694 10.9988 4.16312 10.5243 3.31949 9.68064C2.47587 8.83701 2.00133 7.69319 2.00001 6.50012Z"></path></svg><span>Loading...</span></div></div></div><div class="sc-dNLPWG dtcefb"><ul data-component-name="Menu/Menu" class="sc-kBPbfe ctPApS"><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-default" role="listitem"><a class="sc-iJONRL iBHKJo" href="/" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="true" class="sc-epufQc fYmDZR"><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="Wekan REST API" class="sc-eStdmu iyAVQU">Wekan REST API</span></span></div></li></a></div><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-default" role="listitem"><a class="sc-iJONRL iBHKJo" href="/section/production-security-concerns" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="false" class="sc-epufQc itBMtf"><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="Production Security Concerns" class="sc-eStdmu liVEkC">Production Security Concerns</span></span></div></li></a></div><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-group" role="listitem"><a class="sc-iJONRL iBHKJo" href="/login" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="false" class="sc-epufQc hKPPiy"><div class="sc-jokKPH jtaKKl"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" color="--tree-content-color-default" data-component-name="icons/ChevronRightIcon/ChevronRightIcon" class="sc-iUlCMS kEEIsB"><path d="M11 8L6.00002 13L5.30002 12.3L9.60002 8L5.30002 3.7L6.00002 3L11 8Z"></path></svg></div><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="Login" class="sc-eStdmu liVEkC">Login</span></span></div></li></a><div style="display:none;height:0px;overflow:hidden" class="sc-hgJgCR dlNiXx"></div></div><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-group" role="listitem"><a class="sc-iJONRL iBHKJo" href="/attachmentstoragesettings" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="false" class="sc-epufQc hKPPiy"><div class="sc-jokKPH jtaKKl"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" color="--tree-content-color-default" data-component-name="icons/ChevronRightIcon/ChevronRightIcon" class="sc-iUlCMS kEEIsB"><path d="M11 8L6.00002 13L5.30002 12.3L9.60002 8L5.30002 3.7L6.00002 3L11 8Z"></path></svg></div><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="AttachmentStorageSettings" class="sc-eStdmu liVEkC">AttachmentStorageSettings</span></span></div></li></a><div style="display:none;height:0px;overflow:hidden" class="sc-hgJgCR dlNiXx"></div></div><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-group" role="listitem"><a class="sc-iJONRL iBHKJo" href="/settings" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="false" class="sc-epufQc hKPPiy"><div class="sc-jokKPH jtaKKl"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" color="--tree-content-color-default" data-component-name="icons/ChevronRightIcon/ChevronRightIcon" class="sc-iUlCMS kEEIsB"><path d="M11 8L6.00002 13L5.30002 12.3L9.60002 8L5.30002 3.7L6.00002 3L11 8Z"></path></svg></div><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="Settings" classLine truncated
<div id="redoc"><div hidden=""><script>try{var m=localStorage.getItem("redoc.colorMode");if(m!=="light"&&m!=="dark")m=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";document.documentElement.classList.add(m)}catch(e){}</script></div><div style="--navbar-height:0px;--navbar-stack-height:0px" class="sc-kIbBWU fFQMWf"><div class="sc-cjUTkN dGsweu menu-content"><div class="sc-jqwVmx jjzLa-d"><div data-testid="search-trigger" class="sc-cToEys dffrbr"><div class="sc-cRcubC ddjiE"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" data-component-name="icons/SearchIcon/SearchIcon" class="sc-cHazmW gTzQi"><path d="M14.5 13.7931L10.724 10.017C11.6313 8.9277 12.0838 7.5305 11.9872 6.11608C11.8907 4.70165 11.2525 3.37891 10.2055 2.423C9.15857 1.4671 7.78336 0.951637 6.36602 0.983845C4.94867 1.01605 3.5983 1.59345 2.59582 2.59593C1.59335 3.5984 1.01595 4.94877 0.983738 6.36612C0.95153 7.78347 1.46699 9.15867 2.4229 10.2057C3.3788 11.2526 4.70155 11.8908 6.11597 11.9873C7.53039 12.0839 8.92759 11.6314 10.0169 10.7241L13.793 14.5001L14.5 13.7931ZM2.00001 6.50012C2.00001 5.6101 2.26393 4.74007 2.7584 4.00005C3.25286 3.26003 3.95567 2.68325 4.77794 2.34266C5.6002 2.00207 6.505 1.91295 7.37792 2.08658C8.25083 2.26022 9.05266 2.6888 9.68199 3.31814C10.3113 3.94747 10.7399 4.7493 10.9135 5.62221C11.0872 6.49513 10.9981 7.39993 10.6575 8.22219C10.3169 9.04446 9.7401 9.74726 9.00008 10.2417C8.26006 10.7362 7.39003 11.0001 6.50001 11.0001C5.30694 10.9988 4.16312 10.5243 3.31949 9.68064C2.47587 8.83701 2.00133 7.69319 2.00001 6.50012Z"></path></svg><span>Loading...</span></div></div></div><div class="sc-dNLPWG dtcefb"><ul data-component-name="Menu/Menu" class="sc-kBPbfe ctPApS"><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-default" role="listitem"><a class="sc-iJONRL iBHKJo" href="/" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="true" class="sc-epufQc fYmDZR"><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="Wekan REST API" class="sc-eStdmu iyAVQU">Wekan REST API</span></span></div></li></a></div><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-default" role="listitem"><a class="sc-iJONRL iBHKJo" href="/section/production-security-concerns" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="false" class="sc-epufQc itBMtf"><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="Production Security Concerns" class="sc-eStdmu liVEkC">Production Security Concerns</span></span></div></li></a></div><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-group" role="listitem"><a class="sc-iJONRL iBHKJo" href="/login" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="false" class="sc-epufQc hKPPiy"><div class="sc-jokKPH jtaKKl"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" color="--tree-content-color-default" data-component-name="icons/ChevronRightIcon/ChevronRightIcon" class="sc-iUlCMS kEEIsB"><path d="M11 8L6.00002 13L5.30002 12.3L9.60002 8L5.30002 3.7L6.00002 3L11 8Z"></path></svg></div><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="Login" class="sc-eStdmu liVEkC">Login</span></span></div></li></a><div style="display:none;height:0px;overflow:hidden" class="sc-hgJgCR dlNiXx"></div></div><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-group" role="listitem"><a class="sc-iJONRL iBHKJo" href="/attachmentstoragesettings" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="false" class="sc-epufQc hKPPiy"><div class="sc-jokKPH jtaKKl"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" color="--tree-content-color-default" data-component-name="icons/ChevronRightIcon/ChevronRightIcon" class="sc-iUlCMS kEEIsB"><path d="M11 8L6.00002 13L5.30002 12.3L9.60002 8L5.30002 3.7L6.00002 3L11 8Z"></path></svg></div><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="AttachmentStorageSettings" class="sc-eStdmu liVEkC">AttachmentStorageSettings</span></span></div></li></a><div style="display:none;height:0px;overflow:hidden" class="sc-hgJgCR dlNiXx"></div></div><div data-component-name="Menu/MenuItem" class="sc-gwVKcS fGSWQx menu-item-type-group" role="listitem"><a class="sc-iJONRL iBHKJo" href="/settings" data-discover="true"><li role="none" data-testid="menu-item-label" data-active="false" class="sc-epufQc hKPPiy"><div class="sc-jokKPH jtaKKl"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" color="--tree-content-color-default" data-component-name="icons/ChevronRightIcon/ChevronRightIcon" class="sc-iUlCMS kEEIsB"><path d="M11 8L6.00002 13L5.30002 12.3L9.60002 8L5.30002 3.7L6.00002 3L11 8Z"></path></svg></div><div class="sc-boLXHU jshptd"><span class="sc-bcyEQV hrIrOF"><span data-text="Settings" classLine truncated
<script type="module">
import { hydrate } from "https://cdn.redocly.com/redoc/v3.0.1/bundles/redoc.standalone.js";
const __redoc_definition = {"openapi":"3.2.0","info":{"title":"Wekan REST API","version":"v12.95","description":"The REST API allows you to control and extend Wekan with ease.\n\nIf you are an end-user and not a dev or a tester, [create an issue](https://github.com/wekan/wekan/issues/new) to request new APIs.\n\n> All API calls in the documentation are made using `curl`. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages.\n\n# Production Security Concerns\nWhen calling a production Wekan server, ensure it is running via HTTPS and has a valid SSL Certificate. The login method requires you to post your username and password in plaintext, which is why we highly suggest only calling the REST login api over HTTPS. Also, few things to note:\n\n* Only call via HTTPS\n* Implement a timed authorization token expiration strategy\n* Ensure the calling user only has permissions for what they are calling and no more\n"},"paths":{"/users/login":{"post":{"operationId":"login","summary":"Login with REST API","tags":["Login"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"}}}}},"description":"Login credentials","required":true},"responses":{"200":{"description":"Successful authentication","content":{"*/*":{"schema":{"type":"object","required":["id","token","tokenExpires"],"properties":{"id":{"type":"string","description":"User ID"},"token":{"type":"string","description":"Authentication token\n"},"tokenExpires":{"type":"string","format":"date-time","description":"Token expiration date\n"}}}}}},"400":{"description":"Error in authentication\n","content":{"*/*":{"schema":{"type":"object","properties":{"error":{"type":"string"},"reason":{"type":"string"}}}}}},"default":{"description":"Error in authentication\n"}}}},"/users/register":{"post":{"operationId":"register","summary":"Register with REST API","description":"Notes:\n - You will need to provide the token for any of the authenticated methods.\n","tags":["Login"],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"},"email":{"description":"Your email\n","type":"string"}},"required":["username","password","email"]}}},"required":true},"responses":{"200":{"description":"Successful registration","content":{"*/*":{"schema":{"items":{"properties":{"id":{"type":"string"},"token":{"type":"string"},"tokenExpires":{"type":"string"}}}}}}},"400":{"description":"Error in registration\n","content":{"*/*":{"schema":{"items":{"properties":{"error":{"type":"number"},"reason":{"type":"string"}}}}}}},"default":{"description":"Error in registration\n"}}}},"/api/admin/attachment-settings":{"get":{"operationId":"get_attachment_settings","summary":"Get the attachment storage / upload-block settings (GlobalAdmin)","description":"Only the global admin can call this. Returns the Admin Panel >\n Attachments settings document, including `limitSettings.attachmentsUploadBlocked`\n and `limitSettings.avatarsUploadBlocked` plus the size limits. Cloud-storage\n secret keys (S3 secretAccessKey, Azure accountKey/connectionString, GCS\n credentials) are masked and never returned; a boolean `\u003cfield>Set` marker\n shows whether a value exists.\n","tags":["AttachmentStorageSettings","Settings"],"security":[{"UserSecurity":[]}],"responses":{"200":{"description":"200 response","content":{"application/json":{"schema":{"type":"Object"}}}}}},"put":{"operationId":"update_attachment_settings","summary":"Update the attachment storage / upload-block settings (GlobalAdmin)","description":"Only the global admin can call this. The request body is a\n partial settings object (for example\n `{\"limitSettings\": {\"avatarsUploadBlocked\": true}}`). It is applied through\n the same update path as the `updateAttachmentStorageSettings` Meteor method,\n so validation and `$set` semantics are identical: cloud-storage secrets left\n blank are preserved. The masked, updated settings document is returned.\n","tags":["AttachmentStorageSettings","Settings"],"security":[{"UserSecurity":[]}],"responses":{"200":{"description":"200 response","content":{"application/json":{"schema":{"type":"Object"}}}}}}},"/api/admin/domains":{"get":{"operationId":"get_admin_domains","summary":"List email domains with their user counts (GlobalAdmin)","description":"Only the global admin can call this. CountsLine truncated
const __redoc_definition = {"openapi":"3.2.0","info":{"title":"Wekan REST API","version":"v12.97","description":"The REST API allows you to control and extend Wekan with ease.\n\nIf you are an end-user and not a dev or a tester, [create an issue](https://github.com/wekan/wekan/issues/new) to request new APIs.\n\n> All API calls in the documentation are made using `curl`. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages.\n\n# Production Security Concerns\nWhen calling a production Wekan server, ensure it is running via HTTPS and has a valid SSL Certificate. The login method requires you to post your username and password in plaintext, which is why we highly suggest only calling the REST login api over HTTPS. Also, few things to note:\n\n* Only call via HTTPS\n* Implement a timed authorization token expiration strategy\n* Ensure the calling user only has permissions for what they are calling and no more\n"},"paths":{"/users/login":{"post":{"operationId":"login","summary":"Login with REST API","tags":["Login"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"}}}}},"description":"Login credentials","required":true},"responses":{"200":{"description":"Successful authentication","content":{"*/*":{"schema":{"type":"object","required":["id","token","tokenExpires"],"properties":{"id":{"type":"string","description":"User ID"},"token":{"type":"string","description":"Authentication token\n"},"tokenExpires":{"type":"string","format":"date-time","description":"Token expiration date\n"}}}}}},"400":{"description":"Error in authentication\n","content":{"*/*":{"schema":{"type":"object","properties":{"error":{"type":"string"},"reason":{"type":"string"}}}}}},"default":{"description":"Error in authentication\n"}}}},"/users/register":{"post":{"operationId":"register","summary":"Register with REST API","description":"Notes:\n - You will need to provide the token for any of the authenticated methods.\n","tags":["Login"],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"username":{"description":"Your username\n","type":"string"},"password":{"description":"Your password\n","type":"string","format":"password"},"email":{"description":"Your email\n","type":"string"}},"required":["username","password","email"]}}},"required":true},"responses":{"200":{"description":"Successful registration","content":{"*/*":{"schema":{"items":{"properties":{"id":{"type":"string"},"token":{"type":"string"},"tokenExpires":{"type":"string"}}}}}}},"400":{"description":"Error in registration\n","content":{"*/*":{"schema":{"items":{"properties":{"error":{"type":"number"},"reason":{"type":"string"}}}}}}},"default":{"description":"Error in registration\n"}}}},"/api/admin/attachment-settings":{"get":{"operationId":"get_attachment_settings","summary":"Get the attachment storage / upload-block settings (GlobalAdmin)","description":"Only the global admin can call this. Returns the Admin Panel >\n Attachments settings document, including `limitSettings.attachmentsUploadBlocked`\n and `limitSettings.avatarsUploadBlocked` plus the size limits. Cloud-storage\n secret keys (S3 secretAccessKey, Azure accountKey/connectionString, GCS\n credentials) are masked and never returned; a boolean `\u003cfield>Set` marker\n shows whether a value exists.\n","tags":["AttachmentStorageSettings","Settings"],"security":[{"UserSecurity":[]}],"responses":{"200":{"description":"200 response","content":{"application/json":{"schema":{"type":"Object"}}}}}},"put":{"operationId":"update_attachment_settings","summary":"Update the attachment storage / upload-block settings (GlobalAdmin)","description":"Only the global admin can call this. The request body is a\n partial settings object (for example\n `{\"limitSettings\": {\"avatarsUploadBlocked\": true}}`). It is applied through\n the same update path as the `updateAttachmentStorageSettings` Meteor method,\n so validation and `$set` semantics are identical: cloud-storage secrets left\n blank are preserved. The masked, updated settings document is returned.\n","tags":["AttachmentStorageSettings","Settings"],"security":[{"UserSecurity":[]}],"responses":{"200":{"description":"200 response","content":{"application/json":{"schema":{"type":"Object"}}}}}}},"/api/admin/domains":{"get":{"operationId":"get_admin_domains","summary":"List email domains with their user counts (GlobalAdmin)","description":"Only the global admin can call this. CountsLine truncated
const __redoc_options = {"skipBundle":true,"disableTelemetry":false};
hydrate(__redoc_definition, __redoc_options, document.getElementById('redoc'));
+1 -1
View File
@@ -1,7 +1,7 @@
swagger: '2.0'
info:
title: Wekan REST API
version: v12.95
version: v12.97
description: |
The REST API allows you to control and extend Wekan with ease.
+23 -1
View File
@@ -17,7 +17,29 @@ log="$work/start-wekan.log"
exec ./start-wekan.sh
) > "$log" 2>&1 &
pid=$!
stop() { kill "$pid" 2>/dev/null || true; pkill -f "$bundle/ferretdb" 2>/dev/null || true; }
# Stop the whole stack, WAIT for it to be gone, and then remove its files.
# start-wekan.sh runs WeKan in the foreground, and bash runs a TERM trap only
# after the foreground command returns - so killing the script alone left
# node and FerretDB running after this test said OK. v12.96 openbsd-arm64:
# FerretDB closed its database while the VM's copyback rsync was walking the
# workspace, wekan.sqlite-wal and -shm vanished under it, and rsync's exit 24
# failed a bundle that had built and started fine. Nothing may still be
# running, or still be on disk under .tools/tmp, when this script returns.
running() { kill -0 "$pid" 2>/dev/null || pgrep -f "$bundle/(ferretdb|main\.js)" >/dev/null 2>&1; }
stop() {
kill "$pid" 2>/dev/null || true
pkill -f "$bundle/main\.js" 2>/dev/null || true
pkill -f "$bundle/ferretdb" 2>/dev/null || true
local n=0
while running && [ "$n" -lt 30 ]; do sleep 1; n=$((n + 1)); done
if running; then
kill -9 "$pid" 2>/dev/null || true
pkill -9 -f "$bundle/(ferretdb|main\.js)" 2>/dev/null || true
sleep 1
fi
wait "$pid" 2>/dev/null || true
rm -rf "$work"
}
trap stop EXIT
for i in $(seq 1 120); do
if ! kill -0 "$pid" 2>/dev/null; then
+30 -1
View File
@@ -20,7 +20,12 @@
"that moved its entry point cannot just be swapped in: uuid 11 moved it from",
"dist/index.js to dist/cjs/index.js and the server died on boot (\"Cannot find",
"module .../uuid/dist/index.js\"). \"shims\" writes the old entry file back,",
"re-exporting the new one.",
"re-exporting the new one. nodemailer 10 moved lib/ to dist/cjs/, while",
"meteor/email, built against 8.0.3, links lib/nodemailer.js and",
"lib/well-known/index.js and Npm.requires lib/mail-composer by path: v12.95's",
"bundle failed its smoke boot on the first. nodemailer is \"type\": \"module\",",
"so its shim marks lib/ as CommonJS and forwards lib/nodemailer.js and every",
"lib/<name> directory its exports map lists to dist/cjs.",
"",
"Applied by releases/bump-bundle-npm-deps.mjs, pinned by",
"tests/bundleNpmSecurityBumps.test.cjs. A package NOT listed here is a package",
@@ -63,6 +68,30 @@
]
},
"shims": {
"nodemailer": {
"lib/package.json": "{ \"type\": \"commonjs\" }\n",
"lib/nodemailer.js": "module.exports = require('../dist/cjs/nodemailer.js');\n",
"lib/addressparser/index.js": "module.exports = require('../../dist/cjs/addressparser/index.js');\n",
"lib/base64/index.js": "module.exports = require('../../dist/cjs/base64/index.js');\n",
"lib/dkim/index.js": "module.exports = require('../../dist/cjs/dkim/index.js');\n",
"lib/fetch/index.js": "module.exports = require('../../dist/cjs/fetch/index.js');\n",
"lib/json-transport/index.js": "module.exports = require('../../dist/cjs/json-transport/index.js');\n",
"lib/mail-composer/index.js": "module.exports = require('../../dist/cjs/mail-composer/index.js');\n",
"lib/mailer/index.js": "module.exports = require('../../dist/cjs/mailer/index.js');\n",
"lib/mime-funcs/index.js": "module.exports = require('../../dist/cjs/mime-funcs/index.js');\n",
"lib/mime-node/index.js": "module.exports = require('../../dist/cjs/mime-node/index.js');\n",
"lib/punycode/index.js": "module.exports = require('../../dist/cjs/punycode/index.js');\n",
"lib/qp/index.js": "module.exports = require('../../dist/cjs/qp/index.js');\n",
"lib/sendmail-transport/index.js": "module.exports = require('../../dist/cjs/sendmail-transport/index.js');\n",
"lib/ses-transport/index.js": "module.exports = require('../../dist/cjs/ses-transport/index.js');\n",
"lib/shared/index.js": "module.exports = require('../../dist/cjs/shared/index.js');\n",
"lib/smtp-connection/index.js": "module.exports = require('../../dist/cjs/smtp-connection/index.js');\n",
"lib/smtp-pool/index.js": "module.exports = require('../../dist/cjs/smtp-pool/index.js');\n",
"lib/smtp-transport/index.js": "module.exports = require('../../dist/cjs/smtp-transport/index.js');\n",
"lib/stream-transport/index.js": "module.exports = require('../../dist/cjs/stream-transport/index.js');\n",
"lib/well-known/index.js": "module.exports = require('../../dist/cjs/well-known/index.js');\n",
"lib/xoauth2/index.js": "module.exports = require('../../dist/cjs/xoauth2/index.js');\n"
},
"uuid": {
"dist/index.js": "export * from './esm/index.js';\n"
}
@@ -7,7 +7,7 @@ ensure_tools git
if ! command -v awk >/dev/null 2>&1; then
case "$(_et_linux_family)" in
alpine) sudo apk add --no-cache gawk ;;
arch) sudo pacman -Sy --needed --noconfirm gawk ;;
arch) _et_pacman gawk ;;
fedora|rhel) pm=dnf; command -v dnf >/dev/null 2>&1 || pm=yum; sudo "$pm" install -y gawk ;;
*) ensure_tools awk ;;
esac
+103 -7
View File
@@ -2,7 +2,8 @@
. "$(dirname "${BASH_SOURCE[0]}")/tools-env.sh" # temporary files and downloads go to .tools/tmp
#
# Shared helper: install required CLI tools for the current platform if missing.
# Supports Debian/Ubuntu, Fedora, RHEL/Oracle Linux, Alpine, Arch, and macOS.
# Supports Debian/Ubuntu, Fedora, RHEL/Oracle Linux, Alpine, Arch (and the
# Arch-based Omarchy, EndeavourOS, CachyOS, Manjaro ...), and macOS.
# The package-manager commands are architecture independent.
#
# Usage (source it, then call ensure_tools with the tools you need):
@@ -27,7 +28,10 @@ _et_linux_family() {
. "$os_release"
case "${ID:-} ${ID_LIKE:-}" in
*alpine*) echo alpine; return ;;
*arch*) echo arch; return ;;
# Omarchy (https://omarchy.org) is Arch with Hyprland on top: pacman,
# the Arch repositories and yay for the AUR. Named explicitly so it is
# recognised whether its os-release says ID=omarchy, ID=arch or ID_LIKE=arch.
*omarchy*|*arch*|*manjaro*|*endeavouros*|*cachyos*) echo arch; return ;;
*ol*|*oracle*|*rhel*|*centos*|*rocky*|*almalinux*) echo rhel; return ;;
*fedora*) echo fedora; return ;;
*debian*|*ubuntu*) echo debian; return ;;
@@ -43,6 +47,68 @@ _et_linux_family() {
_et_have() { command -v "$1" >/dev/null 2>&1; }
# Omarchy is detected by its own tooling, for the messages that name its
# update command; everything else treats it as the Arch family it is.
_et_is_omarchy() {
_et_have omarchy-update || [ -d "${HOME:-/nonexistent}/.local/share/omarchy" ]
}
# Install Arch packages WITHOUT a partial upgrade. `pacman -Sy <pkg>` refreshes
# the package lists but upgrades nothing else, which Arch does not support: the
# new package can be linked against libraries newer than the installed ones
# (https://wiki.archlinux.org/title/System_maintenance#Partial_upgrades_are_unsupported).
# So: `-S --needed` against the lists the system already has. A container that
# has never synced (archlinux:latest ships with no sync databases) gets a full
# `-Syu`, which is the supported way to sync, and so does WEKAN_PACMAN_SYU=1.
_et_pacman() {
local sync_dir="${WEKAN_PACMAN_SYNC_DIR:-/var/lib/pacman/sync}"
if [ "${WEKAN_PACMAN_SYU:-0}" = 1 ] || ! ls "$sync_dir"/*.db >/dev/null 2>&1; then
sudo pacman -Syu --needed --noconfirm "$@"
return
fi
if ! sudo pacman -S --needed --noconfirm "$@"; then
echo "pacman could not install: $*" >&2
if _et_is_omarchy; then
echo " The package lists are probably older than the mirrors. Update first with omarchy-update" >&2
else
echo " The package lists are probably older than the mirrors. Update first with sudo pacman -Syu" >&2
fi
echo " (or rerun with WEKAN_PACMAN_SYU=1 to do that full upgrade here), then run this again." >&2
return 1
fi
}
# Install from the AUR with the helper that is there: Omarchy ships yay, other
# Arch installs often have paru. Neither runs as root, so no sudo here.
_et_aur() {
local helper
for helper in yay paru; do
if _et_have "$helper"; then
"$helper" -S --needed --noconfirm "$@"
return
fi
done
echo "No AUR helper (yay or paru) found; install $* from https://aur.archlinux.org manually." >&2
return 1
}
# The OpenSSH server unit is ssh.service on Debian/Ubuntu and sshd.service on
# Arch/Omarchy, Fedora, RHEL and Alpine.
_et_ssh_unit() {
if [ "$(_et_linux_family)" = debian ]; then echo ssh; else echo sshd; fi
}
# How to make en_US.UTF-8 available, per distribution, for the build notes.
_et_locale_hint() {
case "$(_et_linux_family)" in
debian) echo "run sudo dpkg-reconfigure locales." ;;
fedora|rhel) echo "install glibc-langpack-en and enable en_US.UTF-8." ;;
arch) echo "uncomment en_US.UTF-8 in /etc/locale.gen and run sudo locale-gen." ;;
alpine) echo "Alpine uses musl; install musl-locales if the build requires locale data." ;;
*) echo "generate the en_US.UTF-8 locale with your distribution's tools." ;;
esac
}
# Ensure Homebrew exists and is on PATH (Apple Silicon: /opt/homebrew, Intel: /usr/local).
_et_brew_ensure() {
if ! _et_have brew; then
@@ -129,11 +195,19 @@ ensure_tools() {
g++) package=gcc ;;
awk) package=gawk ;;
pip3|python3-pip) package=python-pip ;;
7zip) package=p7zip ;;
snapcraft) echo "Install snapd from AUR, enable snapd.socket, then install snapcraft." >&2; return 1 ;;
# Arch replaced p7zip with 7zip (7-Zip itself) in its repositories.
7zip) package=7zip ;;
snapcraft)
# snapd is in the AUR, not the Arch repositories.
_et_aur snapd || return 1
sudo systemctl enable --now snapd.socket
sudo ln -sfn /var/lib/snapd/snap /snap
sudo snap install snapcraft --classic
continue
;;
*) package="$tool" ;;
esac
sudo pacman -Sy --needed --noconfirm "$package"
_et_pacman "$package"
;;
debian)
case "$tool" in
@@ -220,7 +294,7 @@ ensure_build_toolchain() {
local pm
case "$(_et_os):$(_et_linux_family)" in
linux:alpine) sudo apk add --no-cache bash build-base python3 curl wget p7zip zip unzip ;;
linux:arch) sudo pacman -Sy --needed --noconfirm base-devel python curl wget p7zip zip unzip ;;
linux:arch) _et_pacman base-devel python curl wget 7zip zip unzip ;;
linux:fedora) sudo dnf group install -y development-tools; sudo dnf install -y gcc gcc-c++ make python3 curl wget 7zip zip unzip ;;
linux:rhel) pm=dnf; _et_have dnf || pm=yum; sudo "$pm" groupinstall -y "Development Tools"; sudo "$pm" install -y gcc gcc-c++ make python3 curl wget p7zip zip unzip ;;
linux:debian) sudo apt-get update; sudo apt-get install -y libatomic1 build-essential g++ make python3 curl wget p7zip-full zip unzip ;;
@@ -234,7 +308,7 @@ ensure_archive_tools() {
local pm
case "$(_et_os):$(_et_linux_family)" in
linux:alpine) sudo apk add --no-cache curl wget p7zip zip unzip ;;
linux:arch) sudo pacman -Sy --needed --noconfirm curl wget p7zip zip unzip ;;
linux:arch) _et_pacman curl wget 7zip zip unzip ;;
linux:fedora) sudo dnf install -y curl wget 7zip zip unzip ;;
linux:rhel) pm=dnf; _et_have dnf || pm=yum; sudo "$pm" install -y curl wget p7zip zip unzip ;;
linux:debian) sudo apt-get update; sudo apt-get install -y curl wget p7zip-full zip unzip ;;
@@ -242,3 +316,25 @@ ensure_archive_tools() {
*) echo "Unsupported platform: install curl, wget, 7zip, zip and unzip." >&2; return 1 ;;
esac
}
# The shared libraries Playwright's Chromium and Firefox load, for the
# distributions where `playwright install --with-deps` cannot install them
# (it runs apt-get). Debian/Ubuntu keep using --with-deps.
ensure_playwright_system_deps() {
local pm
case "$(_et_os):$(_et_linux_family)" in
linux:arch)
# Arch and Omarchy. A desktop Omarchy already has nearly all of these;
# --needed makes that a no-op rather than a reinstall.
_et_pacman nss at-spi2-core libcups libdrm libxkbcommon libxcomposite \
libxdamage libxrandr libxfixes mesa pango cairo alsa-lib gtk3 dbus-glib ;;
linux:fedora|linux:rhel)
pm=dnf; _et_have dnf || pm=yum
sudo "$pm" install -y nss atk at-spi2-atk cups-libs libdrm libxkbcommon \
libXcomposite libXdamage libXrandr libXfixes mesa-libgbm pango cairo \
alsa-lib gtk3 dbus-glib ;;
linux:alpine)
echo "Playwright publishes no musl browsers; use WEKAN_PLAYWRIGHT_DOCKER=1 on Alpine." >&2 ;;
*) : ;;
esac
}
+1 -6
View File
@@ -20,12 +20,7 @@ if [ ! -x "$WEKAN_TOOLS_DIR/.meteor/meteor" ]; then
fi
echo "Note: If you use other locale than en_US.UTF-8 , you need to additionally install en_US.UTF-8"
case "$(_et_linux_family)" in
fedora|rhel) echo " install glibc-langpack-en and enable en_US.UTF-8." ;;
debian) echo " run sudo dpkg-reconfigure locales." ;;
arch) echo " enable en_US.UTF-8 in /etc/locale.gen and run locale-gen." ;;
alpine) echo " Alpine uses musl; install musl-locales if the build requires locale data." ;;
esac
[ "$(_et_os)" = linux ] && echo " $(_et_locale_hint)"
echo " You can still use any other locale as your main locale."
echo "Building Wekan."
+3 -2
View File
@@ -89,8 +89,9 @@ if [[ "$OSTYPE" == "linux-gnu" ]]; then
}
# Snapcraft is available natively on Debian, Fedora and RHEL families.
# Alpine has no snapd package; Arch requires snapd from AUR, so the shared
# helper reports the required manual prerequisite instead of running apt.
# Alpine has no snapd package. Arch and Omarchy get snapd from the AUR with
# yay or paru (Omarchy ships yay); the shared helper does that, or says what
# to install when there is no AUR helper.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$SCRIPT_DIR/ensure-tools.sh"
ensure_tools snapcraft
+2 -2
View File
@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
appTitle = (defaultText = "Wekan"),
# The name of the app as it is displayed to the user.
appVersion = 1295,
appVersion = 1297,
# Increment this for every release.
appMarketingVersion = (defaultText = "12.95~2026-11-05"),
appMarketingVersion = (defaultText = "12.97~2026-11-06"),
# Human-readable presentation of the app version.
minUpgradableAppVersion = 0,
+4 -4
View File
@@ -19,7 +19,7 @@
# base: core24 (not core26) · no build-base: devel · grade: stable
# ─────────────────────────────────────────────────────────────────────────────
name: wekan
version: '12.95'
version: '12.97'
base: core24
confinement: strict
grade: stable
@@ -703,18 +703,18 @@ parts:
armhf) WEKAN_ARCH="armhf" ;;
*) echo "Unsupported architecture: ${CRAFT_ARCH_BUILD_FOR}"; exit 1 ;;
esac
WEKAN_ZIP="wekan-12.95-${WEKAN_ARCH}.zip"
WEKAN_ZIP="wekan-12.97-${WEKAN_ARCH}.zip"
echo "Cleaning environment first"
rm -rf .build
mkdir .build
cd .build
#wget https://github.com/wekan/wekan/releases/download/v12.95/wekan-12.95-amd64.zip
#wget https://github.com/wekan/wekan/releases/download/v12.97/wekan-12.97-amd64.zip
# Retry: a release asset can 404 for a short while right after it is
# uploaded (CDN/propagation lag), and a plain wget treated that as
# fatal. --retry-on-http-error covers the 404/5xx window; a genuinely
# missing bundle still fails after the tries are spent.
wget --tries=20 --waitretry=20 --retry-on-http-error=404,403,500,502,503 \
"https://github.com/wekan/wekan/releases/download/v12.95/${WEKAN_ZIP}"
"https://github.com/wekan/wekan/releases/download/v12.97/${WEKAN_ZIP}"
unzip "${WEKAN_ZIP}"
python3 ../releases/check-telemetry.py --bundle bundle
rm "${WEKAN_ZIP}"
+163
View File
@@ -0,0 +1,163 @@
'use strict';
// build.sh and releases/*.sh on Arch Linux and Omarchy (https://omarchy.org),
// the Arch-based Hyprland desktop: pacman without partial upgrades, Arch
// package names, snapd from the AUR, the sshd unit, Playwright without the
// apt-only --with-deps. Everything runs offline: sudo, yay and pacman are
// shell functions that print what they would have run.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const ROOT = path.join(__dirname, '..');
const TMP = path.join(ROOT, '.tools', 'tmp');
fs.mkdirSync(TMP, { recursive: true });
let passed = 0;
function test(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
function run(body, env = {}) {
const shell = `
. releases/ensure-tools.sh
_et_os() { echo linux; }
_et_linux_family() { echo "\${TEST_FAMILY:-arch}"; }
_et_have() { [[ "|\${TEST_HAVE:-pacman}|" == *"|$1|"* ]]; }
sudo() { echo "SUDO $*"; [ -z "\${TEST_SUDO_FAIL:-}" ]; }
yay() { echo "YAY $*"; }
paru() { echo "PARU $*"; }
${body}
`;
const result = spawnSync('bash', ['-c', shell], {
cwd: ROOT, encoding: 'utf8',
env: { ...process.env, TMPDIR: TMP, ...env },
});
return result;
}
function withSyncDir(hasDb, fn) {
const dir = fs.mkdtempSync(path.join(TMP, 'pacman-sync-'));
if (hasDb) fs.writeFileSync(path.join(dir, 'core.db'), '');
try { return fn(dir); } finally { fs.rmSync(dir, { recursive: true, force: true }); }
}
console.log('archOmarchyScripts:');
test('pacman installs with -S --needed, never a -Sy partial upgrade', () => {
withSyncDir(true, (dir) => {
const r = run('ensure_tools gh 7zip awk python3', { WEKAN_PACMAN_SYNC_DIR: dir });
assert.equal(r.status, 0, r.stderr);
for (const pkg of ['github-cli', '7zip', 'gawk', 'python']) {
assert.match(r.stdout, new RegExp(`SUDO pacman -S --needed --noconfirm ${pkg}\\n`), r.stdout);
}
assert.doesNotMatch(r.stdout, /pacman -Sy /);
assert.doesNotMatch(r.stdout, /p7zip/, 'Arch replaced p7zip with 7zip');
});
});
test('a never-synced pacman (fresh container) does a full -Syu, not -Sy', () => {
withSyncDir(false, (dir) => {
const r = run('_et_pacman jq', { WEKAN_PACMAN_SYNC_DIR: dir });
assert.equal(r.status, 0, r.stderr);
assert.match(r.stdout, /SUDO pacman -Syu --needed --noconfirm jq\n/);
});
});
test('WEKAN_PACMAN_SYU=1 opts in to the full upgrade', () => {
withSyncDir(true, (dir) => {
const r = run('_et_pacman jq', { WEKAN_PACMAN_SYNC_DIR: dir, WEKAN_PACMAN_SYU: '1' });
assert.match(r.stdout, /SUDO pacman -Syu --needed --noconfirm jq\n/);
});
});
test('a failed install fails, and names omarchy-update on Omarchy', () => {
withSyncDir(true, (dir) => {
const r = run('_et_pacman jq', {
WEKAN_PACMAN_SYNC_DIR: dir, TEST_SUDO_FAIL: '1', TEST_HAVE: 'pacman|omarchy-update',
});
assert.notEqual(r.status, 0);
assert.match(r.stderr, /omarchy-update/);
assert.doesNotMatch(r.stdout, /pacman -Sy /, 'no automatic partial upgrade as a retry');
});
withSyncDir(true, (dir) => {
const r = run('_et_pacman jq', {
WEKAN_PACMAN_SYNC_DIR: dir, TEST_SUDO_FAIL: '1', HOME: path.join(TMP, 'no-such-home'),
});
assert.notEqual(r.status, 0);
assert.match(r.stderr, /sudo pacman -Syu/);
assert.doesNotMatch(r.stderr, /omarchy-update/);
});
});
test('snapcraft on Arch/Omarchy: snapd from the AUR with yay, then the snap', () => {
const r = run('ensure_tools snapcraft', { TEST_HAVE: 'pacman|yay' });
assert.equal(r.status, 0, r.stderr);
assert.match(r.stdout, /YAY -S --needed --noconfirm snapd\n/);
assert.match(r.stdout, /SUDO systemctl enable --now snapd.socket\n/);
assert.match(r.stdout, /SUDO snap install snapcraft --classic\n/);
assert.doesNotMatch(r.stdout, /SUDO yay|SUDO paru/, 'AUR helpers must not run as root');
});
test('snapcraft without an AUR helper fails with instructions instead of guessing', () => {
const r = run('ensure_tools snapcraft');
assert.notEqual(r.status, 0);
assert.match(r.stderr, /aur\.archlinux\.org/);
assert.doesNotMatch(r.stdout, /snap install/);
});
test('the SSH server unit is sshd on Arch and ssh on Debian', () => {
assert.equal(run('_et_ssh_unit').stdout.trim(), 'sshd');
assert.equal(run('_et_ssh_unit', { TEST_FAMILY: 'debian' }).stdout.trim(), 'ssh');
});
test('Playwright system libraries come from pacman on Arch', () => {
withSyncDir(true, (dir) => {
const r = run('ensure_playwright_system_deps', { WEKAN_PACMAN_SYNC_DIR: dir });
assert.equal(r.status, 0, r.stderr);
assert.match(r.stdout, /SUDO pacman -S --needed --noconfirm nss .*gtk3/);
});
});
test('the build toolchain and archive tools use 7zip and no partial upgrade', () => {
withSyncDir(true, (dir) => {
const r = run('ensure_build_toolchain; ensure_archive_tools', { WEKAN_PACMAN_SYNC_DIR: dir });
assert.equal(r.status, 0, r.stderr);
assert.match(r.stdout, /pacman -S --needed --noconfirm base-devel python curl wget 7zip zip unzip\n/);
assert.doesNotMatch(r.stdout, /p7zip|pacman -Sy /);
});
});
test('build.sh: Arch install, SSH menu and Playwright are distribution-aware', () => {
const src = fs.readFileSync(path.join(ROOT, 'build.sh'), 'utf8');
assert.match(src, /_et_pacman base-devel git curl wget 7zip zip unzip file npm/);
assert.match(src, /systemctl enable \\"\\\$\(_et_ssh_unit\)\\"/);
assert.doesNotMatch(src, /systemctl (enable|start|disable|stop) ssh\b/);
// --with-deps is apt-only: it must be behind the Debian check.
const withDeps = src.indexOf('npx playwright install --with-deps');
const guard = src.lastIndexOf('"$(_et_linux_family)" != debian', withDeps);
assert.ok(withDeps > 0 && guard > 0 && withDeps - guard < 800, 'with-deps is guarded');
assert.match(src, /Arch\/Omarchy/);
});
// Negative test: the fault cannot live anywhere else. No tracked shell script
// does a `pacman -Sy <package>` partial upgrade (-Syu is the supported form),
// and none installs Arch's retired p7zip package.
test('no shell script anywhere does a pacman -Sy partial upgrade or installs p7zip on Arch', () => {
const files = spawnSync('git', ['ls-files', '*.sh'], { cwd: ROOT, encoding: 'utf8' })
.stdout.split('\n').filter(Boolean);
assert.ok(files.length > 50);
const offenders = [];
for (const file of files) {
const lines = fs.readFileSync(path.join(ROOT, file), 'utf8').split('\n');
lines.forEach((line, i) => {
if (/^\s*#/.test(line)) return;
if (/pacman\s+-Sy(?!u)\w*\s/.test(line)) offenders.push(`${file}:${i + 1}: ${line.trim()}`);
if (/pacman\b.*\bp7zip\b/.test(line) || /_et_pacman\b.*\bp7zip\b/.test(line)) {
offenders.push(`${file}:${i + 1}: ${line.trim()}`);
}
});
}
assert.deepEqual(offenders, []);
});
console.log(`\n${passed} tests passed`);
+45
View File
@@ -111,6 +111,51 @@ test('the smoke test fails when WeKan never answers, and passes when it does (ru
}
});
// v12.96 openbsd-arm64: smoke.sh said OK and returned with WeKan and FerretDB
// still running - start-wekan.sh defers its TERM trap until the foreground
// node exits - and FerretDB removed wekan.sqlite-wal/-shm while the VM's
// copyback rsync walked the workspace, failing the job with rsync exit 24.
// This launcher has start-wekan.sh's shape: FerretDB in the background, WeKan
// in the foreground, a TERM trap, a restart loop.
test('the smoke test leaves nothing running and nothing on disk when it returns (runs here)', () => {
const tmp = fs.mkdtempSync(path.join(ROOT, '.tools', 'tmp', 'bsd-smoke-'));
const bundle = path.join(tmp, 'bundle');
const tdir = path.join(tmp, 't');
const alive = () => spawnSync('pgrep', ['-f', `${bundle}/(ferretdb|main\\.js)`]).status === 0;
try {
fs.mkdirSync(bundle); fs.mkdirSync(tdir);
fs.writeFileSync(path.join(bundle, 'ferretdb'),
"const fs=require('fs'),p=require('path');const d=p.join(process.env.WRITABLE_PATH,'files','db');fs.mkdirSync(d,{recursive:true});"
+ "for(const s of ['','-wal','-shm'])fs.writeFileSync(p.join(d,'wekan.sqlite'+s),'x');"
+ "process.on('SIGTERM',()=>setTimeout(()=>{for(const s of ['-wal','-shm'])fs.rmSync(p.join(d,'wekan.sqlite'+s),{force:true});process.exit(0)},500));"
+ 'setInterval(()=>{},1000);\n');
fs.writeFileSync(path.join(bundle, 'main.js'),
"require('http').createServer((q,r)=>r.end('ok')).listen(process.env.PORT,'127.0.0.1');\n");
fs.writeFileSync(path.join(bundle, 'start-wekan.sh'), [
'#!/usr/bin/env bash',
'DIR="$(cd "$(dirname "$0")" && pwd)"',
'FERRET_PID=""',
'stop_ferret() { [ -n "$FERRET_PID" ] && kill "$FERRET_PID" 2>/dev/null || true; }',
"trap 'stop_ferret; exit 0' INT TERM",
'while true; do',
' "$NODE" "$DIR/ferretdb" &',
' FERRET_PID=$!',
' "$NODE" "$DIR/main.js" || true',
' stop_ferret; FERRET_PID=""; sleep 1',
'done', ''].join('\n'), { mode: 0o755 });
const res = spawnSync('bash', [path.join(ROOT, 'releases/bsd/smoke.sh'), bundle, '3199'],
{ encoding: 'utf8', env: { ...process.env, TMPDIR: tdir, NODE: process.execPath }, timeout: 120000 });
assert.equal(res.status, 0, res.stdout + res.stderr);
assert.match(res.stdout, /WeKan answered HTTP 200/);
assert.equal(alive(), false, 'negative: no WeKan or FerretDB left running after smoke.sh returned');
assert.deepEqual(fs.readdirSync(tdir).filter(n => n.startsWith('wekan-smoke.')), [],
'negative: no smoke-test files left for a copyback to find');
} finally {
spawnSync('pkill', ['-9', '-f', `${bundle}/(ferretdb|main\\.js)`]);
fs.rmSync(tmp, { recursive: true, force: true });
}
});
test('the resolver answers "not built" for a BSD nobody publishes yet (negative), without failing', () => {
const res = spawnSync('bash', [path.join(ROOT, 'releases/resolve-node-source.sh'), 'netbsd-x64', 'v26.11.1'], { encoding: 'utf8', timeout: 120000 });
if (res.error || /Could not resolve host|curl: \(6\)/.test(res.stderr)) return; // offline: nothing to check
+48
View File
@@ -74,6 +74,54 @@ test('uuid is bumped to 11.1.1 and the entry point the bundle recorded is writte
}
});
test('nodemailer 10 gets back the lib/ files meteor/email was built against', () => {
// v12.95's bundle failed its smoke boot: nodemailer 10 moved lib/ to dist/cjs/,
// and packages/email.js links the paths it recorded for 8.0.3 - "Cannot find
// module .../meteor/email/node_modules/nodemailer/lib/nodemailer.js" - and
// Npm.requires nodemailer/lib/mail-composer, which Meteor resolves by path.
const shim = (manifest.shims || {}).nodemailer || {};
const os = require('os');
const src = path.join(repoRoot, 'node_modules/nodemailer');
const pkg = JSON.parse(fs.readFileSync(path.join(src, 'package.json'), 'utf8'));
assert.ok(Number(pkg.version.split('.')[0]) >= 10, `node_modules/nodemailer ${pkg.version} has the 10 layout`);
// every lib/<name> directory nodemailer 10 still exports has its old file back,
// so no path a nodemailer 8 caller could have recorded is left unresolvable
const dirs = Object.entries(pkg.exports)
.filter(([key, target]) => /^\.\/lib\/[a-z0-9-]+$/.test(key)
&& target && target.require === `./dist/cjs/${key.slice(6)}/index.js`)
.map(([key]) => `lib/${key.slice(6)}/index.js`);
assert.ok(dirs.includes('lib/mail-composer/index.js') && dirs.includes('lib/well-known/index.js'));
for (const rel of ['lib/package.json', 'lib/nodemailer.js', ...dirs]) {
assert.ok(rel in shim, `nodemailer shim for ${rel}`);
}
const tmp = fs.mkdtempSync(path.join(process.env.TMPDIR || os.tmpdir(), 'nodemailer-shim-'));
try {
const shimmed = path.join(tmp, 'with', 'nodemailer');
const bare = path.join(tmp, 'without', 'nodemailer');
for (const [dir, files] of [[shimmed, Object.keys(shim)], [bare, ['lib/nodemailer.js']]]) {
fs.cpSync(src, dir, { recursive: true });
for (const rel of files) {
fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true });
fs.writeFileSync(path.join(dir, rel), shim[rel]);
}
}
// the two paths the bundle recorded, required by file as Meteor's runtime does
assert.strictEqual(typeof require(path.join(shimmed, 'lib/nodemailer.js')).createTransport, 'function');
assert.strictEqual(typeof require(path.join(shimmed, 'lib/well-known/index.js')), 'function');
assert.strictEqual(typeof require(path.join(shimmed, 'lib/mail-composer')), 'function');
// and each forward is the module the exports map gives a require() caller
for (const rel of dirs) {
assert.strictEqual(require(path.join(shimmed, rel)),
require(path.join(shimmed, 'dist/cjs', rel.slice(4))), rel);
}
// negative: without lib/package.json the CommonJS shim is parsed as ESM
// (nodemailer is "type": "module") and does not load
assert.throws(() => require(path.join(bare, 'lib/nodemailer.js')));
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
});
test('body-parser 1.x and 2.x are each raised inside their own major', () => {
assert.strictEqual(manifest.minimums['body-parser'], '1.20.9');
assert.strictEqual(manifest.minimums['body-parser@2'], '2.4.0');
+8 -2
View File
@@ -23,6 +23,9 @@ const zshScripts = [
const fixtures = {
alpine: 'ID=alpine\n',
arch: 'ID=arch\n',
omarchy: 'ID=omarchy\nID_LIKE=arch\n',
omarchyAlone: 'NAME="Omarchy"\nID=omarchy\n',
endeavouros: 'ID=endeavouros\nID_LIKE=arch\n',
fedora: 'ID=fedora\n',
rhel: 'ID=rhel\n',
oracle: 'ID=ol\nID_LIKE="fedora rhel"\n',
@@ -44,7 +47,8 @@ for (const [name, contents] of Object.entries(fixtures)) {
});
fs.rmSync(dir, { recursive: true, force: true });
assert.strictEqual(result.status, 0, result.stderr);
const expected = name === 'oracle' ? 'rhel' : name;
const expected = name === 'oracle' ? 'rhel'
: ['omarchy', 'omarchyAlone', 'endeavouros'].includes(name) ? 'arch' : name;
assert.strictEqual(result.stdout.trim(), expected);
});
}
@@ -81,7 +85,9 @@ test('Homebrew uses macOS formula names', () => {
test('every Linux family has an explicit package-manager command', () => {
const src = fs.readFileSync(helper, 'utf8');
for (const command of ['apk add --no-cache', 'pacman -Sy --needed --noconfirm',
// Arch's command is `pacman -S --needed` inside _et_pacman: a bare `-Sy` is
// a partial upgrade, which Arch does not support (archOmarchyScripts.test.cjs).
for (const command of ['apk add --no-cache', 'pacman -S --needed --noconfirm',
'dnf', 'yum', 'apt-get']) assert.ok(src.includes(command), command);
});
+2 -1
View File
@@ -37,7 +37,8 @@ const { verify } = require('../releases/verify-mime-runtime.cjs');
const snap = fs.readFileSync(path.resolve('snapcraft.yaml'), 'utf8');
assert.match(snap, /stage-packages:\s*- file\s*- libmagic-mgc/);
const setup = fs.readFileSync(path.resolve('build.sh'), 'utf8');
for (const command of ['apk add', 'pacman -Sy', 'dnf install', 'apt-get install']) {
// Arch/Omarchy installs through _et_pacman (no `pacman -Sy` partial upgrade).
for (const command of ['apk add', '_et_pacman', 'dnf install', 'apt-get install']) {
assert.ok(setup.split('\n').some(line => line.includes(command) && /\bfile\b/.test(line)), command);
}
for (const filename of ['models/fileValidation.js', 'models/lib/fileTypeCorrection.js', 'server/methods/fileStatusAudit.js']) {
+43 -3
View File
@@ -29,21 +29,30 @@ const fnText = fnOf('this_attempts_log') + fnOf('infra_failure');
// The line that records which logs existed when the attempt started.
const logsBefore = /\n *(logs_before=" \$\(ls [^\n]*)\n/.exec(step)[1];
function infraFailure({ remoteLog = '', buildLogs = {}, oldBuildLogs = {} }) {
function infraFailure({ remoteLog = '', buildLogs = {}, oldBuildLogs = {}, execLogs = {}, oldExecLogs = {} }) {
const dir = fs.mkdtempSync(path.join(process.env.TMPDIR || os.tmpdir(), 'lp-'));
try {
for (const [name, text] of Object.entries(oldBuildLogs)) {
fs.writeFileSync(path.join(dir, name), text);
fs.utimesSync(path.join(dir, name), new Date(2000, 0, 1), new Date(2000, 0, 1));
}
// snapcraft's execution logs live in their own directory, not the cwd.
fs.mkdirSync(path.join(dir, 'sclog'));
for (const [name, text] of Object.entries(oldExecLogs)) {
fs.writeFileSync(path.join(dir, 'sclog', name), text);
fs.utimesSync(path.join(dir, 'sclog', name), new Date(2000, 0, 1), new Date(2000, 0, 1));
}
fs.writeFileSync(path.join(dir, 'marker'), '');
fs.utimesSync(path.join(dir, 'marker'), new Date(2010, 0, 1), new Date(2010, 0, 1));
// This attempt's logs arrive after logs_before is taken, as on Launchpad.
fs.mkdirSync(path.join(dir, 'new'));
for (const [name, text] of Object.entries(buildLogs)) fs.writeFileSync(path.join(dir, 'new', name), text);
fs.mkdirSync(path.join(dir, 'newsc'));
for (const [name, text] of Object.entries(execLogs)) fs.writeFileSync(path.join(dir, 'newsc', name), text);
fs.writeFileSync(path.join(dir, 'remote.log'), remoteLog);
const script = `cd "${dir}"; remote_log=remote.log; attempt_marker=marker\n${logsBefore}\n`
+ `for f in new/*; do [ -e "$f" ] && mv "$f" .; done\n${fnText}\ninfra_failure`;
const script = `cd "${dir}"; remote_log=remote.log; attempt_marker=marker; snapcraft_log_dir="${dir}/sclog"\n${logsBefore}\n`
+ `for f in new/*; do [ -e "$f" ] && mv "$f" .; done\n`
+ `for f in newsc/*; do [ -e "$f" ] && mv "$f" sclog/; done\n${fnText}\ninfra_failure`;
return spawnSync('bash', ['-c', script]).status === 0;
} finally {
fs.rmSync(dir, { recursive: true, force: true });
@@ -93,6 +102,37 @@ test('a new log counts even in the marker\'s own second (bash 3.2 -nt is whole s
}
});
// v12.96 s390x: snapcraft printed only its last exception; the 503 that caused
// it was in the "Full execution log" file alone.
const V1296_STDOUT = 'Recovering build snapcraft-wekan-5326\n'
+ 'Could not find repository at path ~xet7/xet7-craft-remote-build/+git/snapcraft-wekan-5326\n'
+ "Full execution log: '/home/runner/.local/state/snapcraft/log/snapcraft-20261105-201734.420875.log'\n";
const V1296_EXEC = 'lazr.restfulclient.errors.ServerError: HTTP Error 503: Service Unavailable\n'
+ "b'<html><head><title>Error: Timeout</title>'\n"
+ 'craft_application.launchpad.errors.NotFoundError: Could not find repository at path ~xet7/x\n';
test('v12.96 s390x: a 503 only in this attempt\'s snapcraft execution log is infrastructure', () => {
assert.equal(infraFailure({ remoteLog: V1296_STDOUT, execLogs: { 'snapcraft-20261105-201734.420875.log': V1296_EXEC } }), true);
});
test('negative: an earlier attempt\'s execution log does not count for this one', () => {
assert.equal(infraFailure({ remoteLog: V1296_STDOUT, oldExecLogs: { 'snapcraft-20261105-201734.420875.log': V1296_EXEC } }), false);
});
test('negative: an execution log with no outage in it is not infrastructure', () => {
assert.equal(infraFailure({ remoteLog: V1296_STDOUT, execLogs: { 'snapcraft-x.log': 'Stage package not found in part \'wekan\': libssl3\n' } }), false);
});
test('v12.96 s390x attempt 3: a half-created repository refusing our push is infrastructure', () => {
assert.equal(infraFailure({ remoteLog: 'remote: You do not have permission to create refs/heads/main.\n'
+ "Git operation failed with: Could not push 'HEAD' to 'git.launchpad.net/~xet7/x' with refspec 'refs/heads/master:refs/heads/main'\n" }), true);
});
test('the execution logs that existed before the attempt are recorded with the build logs', () => {
assert.match(logsBefore, /"\$snapcraft_log_dir"\/\*\.log/);
assert.match(step, /\n\s+snapcraft_log_dir="\$HOME\/\.local\/state\/snapcraft\/log"\n/);
});
test('negative: a 4xx from Launchpad is not taken for an outage', () => {
assert.equal(infraFailure({ remoteLog: 'HTTP Error 400: Bad Request' }), false);
});