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](https://github.com/wekan/wekan/issues/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.