github wekan/wekan v11.49

3 hours ago

Binaries in these bundles

Each bundle carries a Node.js, a FerretDB and the MongoDB Database Tools.
Which source has a given CPU varies from release to release - nodejs.org
builds some architectures, unofficial-builds others, and the
wekan/node-patches build the ones
neither of them does - and not every source publishes a checksum. This is
what went into this release, and which downloads were checked against a
published SHA256.

Bundle Binary From Version Checked SHA256
amd64 FerretDB wekan/FerretDB v1.67.0 verified be7a7c3a7b83dee0…
amd64 Node.js nodejs.org v24.20.0 verified 2f2c0da162318f0d…
arm64 FerretDB wekan/FerretDB v1.67.0 verified 8f0b1df0fdff9f26…
arm64 Node.js nodejs.org v24.20.0 verified 5f4ddab610c1ab20…
armhf FerretDB wekan/FerretDB v1.67.0 verified 4e35884b7e826048…
armhf Node.js wekan/node-patches v24.20.0 verified b8ed7065d44f0afe…
armv6 FerretDB wekan/FerretDB v1.67.0 verified fc657a2929becd70…
armv6 Node.js wekan/node-patches v24.20.0 verified d5cefa6f8cc4acb1…
armv7 FerretDB wekan/FerretDB v1.67.0 verified 4e35884b7e826048…
armv7 Node.js wekan/node-patches v24.20.0 verified c04c81e539347f39…
i386 FerretDB wekan/FerretDB v1.67.0 verified 6e51a7278f850f82…
i386 Node.js wekan/node-patches v24.20.0 verified bb44927307460dcf…
mac-arm64 FerretDB wekan/FerretDB v1.67.0 verified 9478ab9f907a4106…
mac-arm64 Node.js nodejs.org v24.20.0 verified b7bf7707070b950b…
mac-x64 FerretDB wekan/FerretDB v1.67.0 verified 9a052beb1c9d0324…
mac-x64 Node.js nodejs.org v24.20.0 verified 26fc30891004603d…
ppc64le FerretDB wekan/FerretDB v1.67.0 verified 8d96ed0624e0a637…
ppc64le Node.js nodejs.org v24.20.0 verified 341307dcee20d883…
riscv64 FerretDB wekan/FerretDB v1.67.0 verified 791b9294ffb1f48f…
riscv64 Node.js unofficial-builds.nodejs.org v24.20.0 verified a149c5bf85f98ff1…
s390x FerretDB wekan/FerretDB v1.67.0 verified 2914f0e00c0d0b5d…
s390x Node.js nodejs.org v24.20.0 verified ca381121cb5a8d38…
win-arm64 FerretDB wekan/FerretDB v1.67.0 verified d00ed8f3c5300983…
win-arm64 Node.js nodejs.org v24.20.0 verified 31c6799744de8a54…
win64 FerretDB wekan/FerretDB v1.67.0 verified 7e559755fa5b38ce…
win64 Node.js nodejs.org v24.20.0 verified 6cac9ffbca8f6a47…

A row saying no checksum published is not a failed check - it is a
source that publishes nothing to check against. Those are the ones worth
fixing at the source.

v11.49 2026-09-04 WeKan ® release

In short: the single Windows EXE stops running WeKan out of a
closed-source virtual filesystem, and stops unpacking the bundle instead. It
carries the published win64 ZIP as a checksummed payload, unpacks only the
fifteen or so files Windows itself must open, and mounts the remaining
~39,000 in the server process
. That ends the crash loop 11.48 shipped with,
cuts the download from 690 MB to about 232 MB, and turns a damaged copy into a
clear message rather than a restarting server. bundle-trim drops the native
prebuilds no bundle can open, and moving a list to a swimlane now binds it
there instead of silently doing nothing.

Platform Binary From Version SHA256
amd64 Node.js nodejs.org v24.19.0 14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647
amd64 FerretDB wekan/FerretDB v1.53.0 eae1f0a8f73bfc979738bfff7284d40fd1bc55de2cc56514721fc155c3624f7d
arm64 Node.js nodejs.org v24.19.0 01443c1e1a29e531ccad5a46fefa6df490d2189c49f7955904aecdbb0fe86fdc
arm64 FerretDB wekan/FerretDB v1.53.0 bdc50caee3ac28495b42d2130b94a042a9dd6d3a38f732cac02b648f36c891da
mac-arm64 Node.js nodejs.org v24.19.0 3f1cf157479c1480352083105e13faf9d008ede98e7e157746b6df940d197b94
mac-arm64 FerretDB wekan/FerretDB v1.53.0 cb14ffe93e285903e5a8a9c1821687ddb5b8a979a11c584bf4af534b272c6d3e
mac-x64 Node.js nodejs.org v24.19.0 d35e95230f46f6f0751df497c56622c6735e05d5e1fb1630996a005b9d328fe4
mac-x64 FerretDB wekan/FerretDB v1.53.0 d97dfa9afa60aa05f25384327de82efe7b71d958ed24c1f66618284294a65cd3

This release fixes the following bugs:

The single Windows EXE - what the one downloadable file is made of, what it
checks before it starts WeKan, and how little of it ever reaches the disk.

It carries a checksummed copy of the win64 ZIP instead of a virtual filesystem. Thanks to xet7.

WeKan-11.48-win64.exe died on every start, restarted, and died again:

...\accounts-password\node_modules\bcrypt\promises.js:1
MZ......  !.L.!This program cannot be run in DOS mode.
SyntaxError: Invalid or unexpected token
    at Object.<anonymous> (...\bcrypt\bcrypt.js:6:18)

Both published files were correct. wekan-11.48-win64.zip holds the real
1123-byte promises.js, and so did the EXE: in its packed image
bcrypt.node (195584 bytes) is entry 0x5c9c and promises.js is
0x5c9d, stored back to back, each with the right bytes at the offset its
own record gives. What was wrong was the READ. Enigma Virtual Box served
all 44,401 bundle files from a virtual filesystem inside the EXE, and once
Node.js had loaded the native addon out of it (bcrypt.js line 2), the
next read - require('./promises') on line 6, the blob immediately after
that addon - came back as the addon's own PE bytes.

So that packer is gone. The EXE is now the compiled launcher with the
published ZIP appended to it and an 80-byte trailer saying where that
payload starts, how long it is, its SHA-256 and which WeKan it is. The
download drops from 690 MB to about 232 MB, because the payload is the
compressed ZIP rather than an uncompressed image, and a damaged copy - a
truncated download, a half-written file - now stops at the checksum with
the expected and actual hashes and a line saying to download it again,
instead of reaching Node.js as a crash loop.

Its release smoke test is why a broken EXE was published at all.
start-wekan.bat restarts WeKan every three seconds when it exits, and the
smoke test polled http://localhost:8080/sign-in for three minutes: an EXE
that crashed on nine starts out of ten still answered inside that window,
and the job went green. It now starts the EXE twice - the run that unpacks
and the run that must find its files already there - and fails if either
log contains WeKan exited; restarting or SyntaxError. It also no longer
trips over PowerShell's read-only $pid while freeing the ports FerretDB
needs.

tests/windowsSingleExe.test.cjs pins the trailer format from both ends -
every TRAILER_* the launcher defines must equal the constant
releases/append-windows-payload.mjs exports under that name - round-trips
a packed file through pack and verify, and requires a flipped byte, a
truncated payload, a missing trailer and an over-long version to be
refused. A negative test reads every file under .github/workflows and
releases and fails on any Enigma Virtual Box download, console or .evb
project, so that virtual filesystem cannot come back through a second
packing path.

It reads the bundle from inside itself instead of unpacking 39,035 files. Thanks to xet7.

Replacing the packer left the EXE writing its whole bundle - 39,035 files,
685 MB - beside itself on the first run. It does not any more.

Only what cannot be virtual is unpacked, and the reason is the same in
every case: something other than WeKan's own module loader has to open it.
node.exe and ferretdb.exe are separate processes, so Windows needs a
real path to start one; .node addons are loaded with LoadLibrary, same
reason; main.js is the entry Node resolves before any hook could see it;
start-wekan.bat is read by cmd.exe; and wekan-vfs.cjs is read by
node --require before anything is mounted. main.js also chdir()s into
programs/server, and a working directory is a kernel concept no hook can
answer, so that one directory is created for real. That is about thirty
files out of 39,035, and the list is computed from the archive rather than
written by hand, so a bundle that gains an addon or a database tool cannot
silently lose it.

The mount is releases/single-exe/wekan-vfs.cjs, and it needs two
mechanisms because Node needs both. module.registerHooks() answers
require: the CJS loader resolves through internal C++ bindings, so
patching fs cannot make require see a virtual file, and Module._stat
is captured as a module-local inside Module._findPath, so replacing it
does nothing either. Resolution is therefore reimplemented, and checked
against Node's own answer for every package in a real bundle - 1407 package
directories and 1865 bare specifiers, all matching. fs patching covers
everything that is not a module: Meteor's boot.js reads program.json
and every server package with fs.readFileSync and runs them through
vm.runInThisContext, and webapp serves the client files with
fs.createReadStream. Below both sits one more: Meteor's runtime.js
hands reify a resolver that calls Module._resolveFilename directly, which
registerHooks never sees.

Two traps are worth naming because both were hit here. A .node addon must
be resolved with NO format declared - saying commonjs for one makes Node
compile the binary as JavaScript and die with SyntaxError: Invalid or
unexpected token
on its own header, which is the 11.48 crash reproduced
from the other side. And a directory that exists both really and in the
archive has to merge the two listings, or wekan-app/programs/server would
list the two unpacked files and hide the entire server.

This was verified by running it, not by reading it. With 23 real files on
disk and 38,931 served from the ZIP, WeKan boots, connects to FerretDB and
answers 200 on /sign-in and on its 6.9 MB client bundle.
tests/bundleArchiveVfs.test.cjs builds a small archive and pins the three
things that have to stay right - the ZIP reader, the resolution and the
declared format - including the two traps above, and the release smoke test
now counts what reached the disk, so a change that quietly went back to
unpacking everything fails the job instead of passing it.

It drops the native prebuilds the target platform can never open. Thanks to xet7.

bcrypt and argon2 (Meteor's accounts-password) each ship one
prebuilds/<platform>-<arch>/ directory per platform they support: 21
native binaries in every WeKan bundle, of which exactly one is ever opened.
Their loader is node-gyp-build, and its resolve() reads a single
directory - readdirSync('prebuilds'), filtered by matchTuple(platform, arch) with platform and arch from os.platform() and os.arch().

That is the same argument releases/bundle-trim.mjs already makes about
uWebSockets.js, so it is the same code path. --trim-prebuilds keeps the
directories node-gyp-build would match and drops the rest, using that
loader's own tuple parsing - multi-arch names such as darwin-x64+arm64
included - and keeps both libc flavours of the target, because glibc versus
musl is decided at runtime and not here.

Three things make it safe rather than merely smaller. It is off by default
and refuses to run without an explicit --platform and --arch, since the
defaults are linux/x64 and a Windows or macOS bundle trimmed with those
would lose the only addon it can load - the fault the single EXE was just
fixed for. build-amd64 deliberately does not pass it, because every other
bundle WeKan ships is that bundle repacked and trimming there would take
the prebuilds away from architectures not yet built; each final
per-platform job passes its own target. And every decision is made before
anything is deleted, so a package whose prebuilds cannot be reasoned about
is left whole rather than half-trimmed - which is how bare-fs,
bare-path and bare-url were found, shipping prebuilds/ of .bare
files for the Bare runtime. Those are left untouched, and say so.

On the mac-arm64 bundle 19 of the 21 go and the two for darwin-arm64
stay. For the single Windows EXE it takes the files that have to be
unpacked from 33 down to about fifteen.

Swimlanes - which swimlane a list belongs to, and whether that can be set.

Moving a list to a swimlane binds it to that swimlane. Thanks to TawsTm and xet7.

Moving a list to another swimlane on the same board did nothing at all. The
list kept its empty swimlaneId, so it went on rendering under every
swimlane, and deleting "the one in the second swimlane" deleted the only
list document there was. A board backup showed the empty swimlaneId on
lists that the report describes.

List.move(boardId, swimlaneId) asked does the target board already have
a list with this name?
without first asking whether the target board IS
this list's board:

const boardList = await ReactiveCache.getList({
  boardId, title: this.title, archived: false });
if (boardList) { ...merge, never writes a swimlaneId... }
else           { ...insert a new list WITH the swimlaneId... }

On a same-board move that search finds THIS LIST, so the merge branch ran -
and the merge branch is the one branch that never writes a swimlaneId.
The chosen swimlane was discarded every time, which is also why the binding
could not be put back by hand.

Merging is only meaningful across boards; on the same board a move is a
re-bind, and that is what it does now. The decision lives in
models/lib/listMovePlan.js, where it is unit-tested, and models/lists.js
applies it: same board re-binds this list, another board with the name
already taken merges the cards into that list, another board creates it
there and binds it. An empty swimlaneId remains a deliberate un-bind back
to board-wide, which is still a legitimate layout, and moveList now
refuses a swimlane that is not on the board the list is moving to.

The same branch also called card.move(boardId, this._id, boardList._id).
Card.move's second argument is a swimlaneId, so this set every card's
swimlaneId to a LIST id - a swimlane that does not exist - and those cards
became the orphaned cards the board-open repair has to rescue. The cards
were selected as this.cards(swimlaneId) as well, filtering the SOURCE
list's cards by a swimlaneId belonging to the TARGET board, which on a
cross-board move matches nothing and left every card behind. Both are gone:
a list's own cards travel with it, into the chosen swimlane. A negative test
scans models/lists.js for any card.move() whose second argument is not a
swimlane, so the argument order cannot go wrong at another call site.

This does not restore bindings already lost. Boards opened under the
versions before #6515 had
every list un-bound automatically, and which swimlane each list belonged to
is not recoverable from the data - a list's cards can be spread across
several swimlanes, and on a board whose second swimlane is new they are all
in the first, so guessing would bind every list to one swimlane and hide it
from the others. With this fix the binding can at least be set again from
Move List.

Thanks to above GitHub users for their contributions and translators for their translations.

Don't miss a new wekan release

NewReleases is sending notifications on new releases.