Wekan logo

Wekan

Open source kanban board application built with Meteor

Alternative to: trello


About Versions (218)

v10.97

2026-08-16

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.

BundleBinaryFromVersionCheckedSHA256
amd64FerretDBwekan/FerretDBv1.53.0verifiedeae1f0a8f73bfc97…
amd64Node.jsnodejs.orgv24.19.0verified14b342e71204f811…
arm64FerretDBwekan/FerretDBv1.53.0verifiedbdc50caee3ac2849…
arm64Node.jsnodejs.orgv24.19.0verified01443c1e1a29e531…
armhfFerretDBwekan/FerretDBv1.53.0verifiedf7f050207e656d65…
armhfNode.jswekan/node-patchesv24.19.0verifiedb55350f3071b765a…
armv6FerretDBwekan/FerretDBv1.53.0verified34f5f553dcd2bff3…
armv6Node.jswekan/node-patchesv24.19.0verified128ded0cda638c1f…
armv7FerretDBwekan/FerretDBv1.53.0verifiedf7f050207e656d65…
armv7Node.jswekan/node-patchesv24.19.0verified8dbe0a9aa8550ad5…
i386FerretDBwekan/FerretDBv1.53.0verified515b1189b4c7d58f…
i386Node.jswekan/node-patchesv24.19.0verified3b0b3bbfe27daf58…
mac-arm64FerretDBwekan/FerretDBv1.53.0verifiedcb14ffe93e285903…
mac-arm64Node.jsnodejs.orgv24.19.0verified3f1cf157479c1480…
mac-x64FerretDBwekan/FerretDBv1.53.0verifiedd97dfa9afa60aa05…
mac-x64Node.jsnodejs.orgv24.19.0verifiedd35e95230f46f6f0…
ppc64leFerretDBwekan/FerretDBv1.53.0verified481d36a80ec73673…
ppc64leNode.jsnodejs.orgv24.19.0verifiedc510c6ce12f07010…
riscv64FerretDBwekan/FerretDBv1.53.0verified4b43f86eb0f3fa0b…
riscv64Node.jsunofficial-builds.nodejs.orgv24.19.0verifiedcd1f14af28121480…
s390xFerretDBwekan/FerretDBv1.53.0verifiedfe74e03894b84897…
s390xNode.jsnodejs.orgv24.19.0verifieda4792e65962ffa0a…
win-arm64FerretDBwekan/FerretDBv1.53.0verifiedf1186c310a4dd144…
win-arm64Node.jsnodejs.orgv24.19.0verified8502f4a50b458d4c…
win64FerretDBwekan/FerretDBv1.53.0verifiedc6dad7bc8ad56f47…
win64Node.jsnodejs.orgv24.19.0verified57f71ab3652e797d…

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.

v10.97 2026-08-16 WeKan ® release

In short: v10.96 shipped a bundle that could not start. Trimming what a bundle carries went one file too far: boot.js reads every source map NAMED in programs/server/program.json, unconditionally, so removing the maps left 63 dangling names and the server died before opening its port. The names go with the files now, and the fix was checked by BOOTING a trimmed bundle rather than by reading the code again. Then the snap, which had been taking itself offline at every restart: the startup comparison of the two database copies ran unbounded with nothing on the web port, on an ambiguity that its own reading of MongoDB kept recreating. Below that: another 61 MiB off every bundle, from packages no require() can reach, and a guard that keeps all 246 translations loading one at a time.

PlatformBinaryFromVersionSHA256
amd64Node.jsnodejs.orgv24.19.014b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647
amd64FerretDBwekan/FerretDBv1.53.0eae1f0a8f73bfc979738bfff7284d40fd1bc55de2cc56514721fc155c3624f7d
arm64Node.jsnodejs.orgv24.19.001443c1e1a29e531ccad5a46fefa6df490d2189c49f7955904aecdbb0fe86fdc
arm64FerretDBwekan/FerretDBv1.53.0bdc50caee3ac28495b42d2130b94a042a9dd6d3a38f732cac02b648f36c891da
mac-arm64Node.jsnodejs.orgv24.19.03f1cf157479c1480352083105e13faf9d008ede98e7e157746b6df940d197b94
mac-arm64FerretDBwekan/FerretDBv1.53.0cb14ffe93e285903e5a8a9c1821687ddb5b8a979a11c584bf4af534b272c6d3e
mac-x64Node.jsnodejs.orgv24.19.0d35e95230f46f6f0751df497c56622c6735e05d5e1fb1630996a005b9d328fe4
mac-x64FerretDBwekan/FerretDBv1.53.0d97dfa9afa60aa05f25384327de82efe7b71d958ed24c1f66618284294a65cd3

This release fixes the following bugs:

Bundles and images - what a build carries, and what it no longer does.

Dropping a source map must un-name it too, or the server will not boot. Thanks to xet7.

The entry above removed the source maps from every platform. A released image then crash-looped:

  Error: ENOENT: no such file or directory,
    open '/build/programs/server/packages/ecmascript.js.map'
    at /build/programs/server/boot.js:101:29

“Nothing on any loading path reads a .map was true of the client and false of the server. boot.js reads every map NAMED in programs/server/program.json, at boot, unconditionally:

  serverJson.load.forEach(function (fileInfo) {
    if (fileInfo.sourceMap) {
      var rawSourceMap = fs.readFileSync(
        path.resolve(serverDir, fileInfo.sourceMap), 'utf8');

63 of the 102 load entries name one — 60 MiB — so deleting the files left 63 dangling names and the server died before it opened its port.

The names now go with the files: the same pass deletes sourceMap and sourceMapRoot from every load entry. The client was never affected and still is not — its program.json names no maps at all (678 manifest entries, zero sourceMap fields) and webapp reads only program.json itself at startup.

Verified by BOOTING a trimmed bundle rather than by reading the code again: with uWebSockets.js, the legacy client and all 4766 maps removed, node main.js loads the whole server and reaches AccountsServer.init, failing only on the deliberately unreachable MONGO_URL it was given. That is the check that was missing the first time, and tests/bundleTrim.test.cjs now pins the invariant boot.js actually requires — every map the manifest names exists on disk — for both settings of --keep-maps.

The snap’s two copies of the database - after a migration both stay on disk, and starting is where that gets decided.

Reading MongoDB to compare the copies must not look like writing to it. Thanks to xet7.

An instance serving FerretDB printed this at every restart:

  WeKan: BOTH databases have been written to since the migration.
    MongoDB  last written 2026-08-16 01:41 (WiredTiger.wt)
    FerretDB last written 2026-08-16 01:38

Nothing had opened that MongoDB in a month. The giveaway is in the report itself: the MongoDB timestamp is the minute the snap started, three minutes AFTER the FerretDB it is being compared against.

bin/ferretdb-migration-stale decides MongoDB has been written to from the mtimes of the WiredTiger data files, and its own header admits an mtime cannot tell “somebody used this database” from “this database was started”. bin/database-autopick is the answer to that — it reads both copies and compares their contents. But to read MongoDB it STARTS mongod, and starting mongod does recovery and a checkpoint, which stamps exactly the files the staleness check reads. So the diagnostic manufactured its own evidence: after one run, MongoDB looked freshly written forever and the ambiguity could never resolve, however long nobody touched it.

The read now notes the newest data-file mtime before mongod starts and puts anything newer back afterwards — files mongod CREATED during the read included, since a new journal file is the newest thing in the directory and reports as a write on its own. mongod does not use mtimes, and what it checkpointed is not user data, so the metadata is made to say what is true. Every way out of the read restores, including the failures, because a failing read is exactly when the ambiguity gets reported. A write that happened BEFORE the read still survives it: the point is to hide WeKan’s own read, never somebody’s work.

The database comparison at startup cannot take the site down any more. Thanks to xet7.

The same instance went OFFLINE at every restart. wekan-control ran the comparison synchronously and unbounded before anything opened the web port — and it starts each database on a temporary port to read it, which on a real instance is minutes. Until it returned a browser got a connection timeout, and the reason was in snap logs, the last place somebody whose site is down thinks to look.

That is #6592 one step earlier than where it was fixed. The database WAIT already serves a waiting for its database page; the helpers for it were defined 200 lines BELOW the comparison that needed them. They move up, and the comparison uses them — after the same grace period, so a comparison that finishes in seconds does not flash a page up and teach proxies to cache a 503 for a healthy site.

And a bound. WEKAN_AUTOPICK_TIMEOUT is 900 seconds by default; when it runs out WeKan says so, changes nothing, and starts on the database already selected. Stopping the comparison is safe — it never deletes anything on either side, and the merge it may be in the middle of only INSERTS what is missing, so a partial one is fewer documents copied and the next start finishes the job. Serving the site beats finishing the comparison.

WEKAN_AUTOPICK was an env var and nothing else, so an admin whose site was down had no supported way to skip the comparison. It is a snap config key now, with the bound beside it, and the timeout message names both — somebody reading it is somebody whose site just came up late:

  snap set wekan autopick=false
  snap set wekan autopick-timeout=1800

This is the other half of the entry above. Before that fix the comparison ran at EVERY start, so every restart took the site down for a comparison that could never conclude. One stops it recurring; this one makes it survivable.

and changes what every platform ships:

Prune what no require() in the server bundle can reach. Thanks to xet7.

programs/server/npm/node_modules is 347 MB of an ~850 MB bundle, and measuring its require graph says 206 MB of that is unreachable from any server entry point. Two things cause it, neither a bug in Meteor:

  • The tree is outside the bundler’s graph. Meteor 3.5 compiles through rspack — the server output is minified, and programs/server/app/app.js requires only 76 bare specifiers because rspack inlined the app’s real dependencies into it. But this tree is what Atmosphere packages declare with Npm.depends() and load through Npm.require(), which rspack cannot follow. It is largely the INPUT to a build whose OUTPUT ships beside it.
  • It is a full npm install, devDependencies included.

Duplication is the smaller half: 586 distinct packages exist as 815 copies, but the redundant copies are only ~28 MB, because Meteor keeps per-package node_modules on purpose so packages can pin conflicting versions.

releases/prune-unreachable-npm.mjs removes 61.3 MiB, in four categories whose reason is PROVABLE rather than merely plausible — the standard the uWebSockets.js removal met: typescript (23.2 MiB, a devDependency of 196 packages here and a runtime dependency of none), openpgp with nodemailer-openpgp (21.3 MiB, reachable only through an optional nodemailer plugin nothing requires), @types/* (9.6 MiB across 24 copies, verified to contain no .js at all) and sinon (7.2 MiB, a test framework).

The remaining 145 MB of unreachable packages STAYS. jquery, hotkeys-js and the @azure storage adapters are almost certainly dead too, but almost certainly is not the standard, and the tail of 590 packages is where a static scan is most likely to be wrong.

Two independent safeties, because this is riskier than the uws removal was. The reachable set is recomputed from the bundle every run and deliberately OVER-approximated — once a package is reached, every file in it is scanned and every require() string literal counts. The policy then only proposes; the graph has a VETO, so an entry whose package is actually required is refused and said out loud rather than applied. And after deleting, every path in the reachable set must still exist or the run fails. Verified on a real bundle: 473M to 410M, with the reachable count 211 before and after.

and adds the following guard:

Every language stays lazily loaded, and a guard keeps it that way. Thanks to xet7.

WeKan ships 246 languages, 37 MB of JSON. What makes that affordable is one character in each of 246 near-identical blocks in imports/i18n/languages.js:

  load: () => import('./data/ace.i18n.json'),

A dynamic import() is a SPLIT POINT. rspack emits each language as its own chunk — measured on a built bundle, 238 chunks and 34 MiB of JS — and a browser fetches exactly one, about 145 KB for the language in use. Written instead as import data from './data/ace.i18n.json', or with require(), the same line stops splitting and 34 MiB joins the main bundle for every user, in every language. That edit is made by hand each time a language is added, which is why it wants a guard rather than a convention.

Seven checks: every entry has a load:, every load: is the dynamic form, none uses require(), no static import of a data file, nothing outside languages.js imports one, every file is claimed and every claim has a file, and a Transifex pull writes the file the app actually loads.

The last two are symlink-aware, and that is the point of them. .tx/config’s lang_map renames most of Transifex’s underscored locales to WeKan’s hyphenated files; for the two it does not — km_KH and ru_RU — the hyphenated name is a SYMLINK to the file Transifex writes. Two names for one file, not two copies. Reading it the other way costs a language its real translations, so both checks compare through realpath and say so.

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