Skip to content

chore(release): 3.2.0 - #43

Merged
xiaodoudou merged 266 commits into
mainfrom
dev
Oct 12, 2026
Merged

xiaodoudou merged 266 commits into
mainfrom
dev

Conversation

@xiaodoudou

Copy link
Copy Markdown
Owner

Added

  • A token field. input: 'token' holds a key or a secret that two systems share. Type it, paste it, or make a random one with the Generate button, and copy it with Copy. options.length (random bytes), options.prefix and options.format (base64url or hex) shape what Generate makes, and options.autoGenerate gives a new record a token by itself, in the admin and over the API. See token.
  • The Token under This CMS in the Sync settings is a token field. It has a Generate and a Copy button, so the secret no longer has to be invented by hand.

Changed

  • Replication is live, and needs no port of its own. A node keeps a link open to each peer it receives a resource from: the peer sends what the node lacks, then each change as it is written, and the link connects again by itself after an outage. Records go over a websocket on the HTTP port of the CMS, at /_replicator next to the REST API, so they travel over wss:// when the CMS is served over HTTPS; files still go over the REST API. A node a peer connects to only sends, so a connecting peer can no longer write anything. With MongoDB or PostgreSQL, a node pulls again every replication.pollInterval (30 seconds). To upgrade: remove netPort, give each peer the url of its REST API, set replication.secret on every node, and list on each node the peers it receives from. settleDelay and POST /replicator/sync/:resource/:id are gone. See REPLICATION.md.
  • The resources of the CMS stay out of the global peers list. _users, _groups, _settings, _sync and _xlsx now replicate only when replication.peersByResource names them. Every node makes its own admins group and localAdmin on its first start, and a node that fetched another node's admins group could no longer update it, so its admin login stopped working. See REPLICATION.md.
  • The Replicator page shows the replication, live, and starts nothing. For each resource that replicates, it shows the peers this node pulls it from, the state of each link (live, reconnecting with the error and the next try) and its last change, and the peers that pull it from here. The Sync all and Sync a record buttons are gone. The page moved from the System menu to the CMS menu, and is drawn with the plugin kit.
  • A record made on another node says so in the editor. Its form showed "All changes saved" above fields that can't be edited. It now reads "Read-only, made on" and the mid of the node to edit it on. See REPLICATION.md.

Real components mounted with Vuetify: the record list, select fields, pillbox, colour picker, record editor, the app
(routing, address, unsaved-edits guard, breadcrumb), blocks, the JSON editor and the navigation. Fixed along the way:
a new record opens a blank editor again, a refused request is not logged as a server error, record names and labels
after the mustache 4 update, a record whose id overlaps the machine id can be changed and removed, and remove and
update throw instead of failing silently.
…inst the code

Documentation refactor: one source of truth per topic, checked against the code

A README that goes from nothing to a running CMS, a map of the docs, architecture, concepts and contributing pages, one
configuration reference, the API, the plugins and every field page rewritten against the code, diagrams in Mermaid,
and the bugs found on the way recorded.
…nent

Every bug from the documentation review fixed, and every component tested

- The 39 bugs found while the docs were checked, each with a regression test: among them, the login page and the admin
  agree on the theme, one JSON body limit with a clear 413, the cms command is a plain CMS in a project, installs from
  git come with the admin built, ?locale= stores values field first, and syncing one record checks it.
- Smart cropping uses sharp's attention strategy: no AI model, and every TensorFlow trace removed.
- Component tests for every remaining component, and the bugs they found; FormService and SchemaService tested.
- The remaining lint warnings fixed.
Green timestamps, levels coloured by severity and highlighted values on a terminal, plain text in files and pipes
(stdout and stderr checked separately), NO_COLOR and FORCE_COLOR. The log page shows the colours and reads the level of
a coloured line.
… README

Every protection is on by default and the legacy profile is gone. The bug and audit notes leave the docs now that they
are covered. The README gets a centred header with badges, a screenshot of the default resources, and says the default
store is a key-value store saved to a file.
A delete goes to the replication peers right away again, cms-import prints no empty progress bars into logs, the xlsx
import answers 400 without a file, express-session defaults stated, quiet test runs, eslint 10, and canvas and
rollup-plugin-node-polyfills dropped.
…ngoDB and PostgreSQL

Record stores: LevelDB and SQLite beside the JSON file, MongoDB and PostgreSQL

dbEngine.type can be sqlite or leveldb; LevelDB is the default for new content, and a resource that still has a
db.json keeps it until it is migrated with the tool that moves content between the local engines. A storage engines
page with measurements and a crash test, and the benchmark for all five engines.
The version becomes 3.0.0 and the license GPL-3.0-only. Versions up to 2.6.1 were MIT: the license file keeps their
notice, with its copyright line, as the MIT license requires, so the brand test leaves that one file out.
…l alias

Text fields: autocomplete "nope", so Brave stops offering an email alias on emails and links

The shared mixin set autocomplete back to "off" after Vuetify rendered the field, and browsers ignore "off" for what they take for an
email or a sign-up form. Text fields now get an invalid token, passwords keep "new-password". The email field is a plain text box.
Icon buttons everywhere: move, delete, fold, add, Edit JSON and Object Properties. Selects are a button and a list like the other
dropdowns of the admin, and booleans a checkbox. Nesting is an indentation with a thin rule, not boxes inside boxes: a container is
a header line (fold chevron, name, ghost icon buttons) over its body, and the root object has no box. List items are rows
(position, field, move and delete icons); items that are objects or lists have their own header line; "Add item" is one dashed
button under the rows, and "Delete All" an icon on the header, moved there by a small hook (the library builds it under the rows).
A type chip on mixed items and added properties; the name field of Object properties is emptied after each add. Tables keep their
bordered look with ghost row actions. Works in the dark theme and on a phone; tokens only.

The boolean of the catalogue's Settings field is declared as a checkbox (format: 'checkbox'). The object field page describes this
editor (header lines, icons, Object Properties, lists, added properties) and its five screenshots show it.
Remove the Cms Config page and its routes, and list Syslog in the CMS menu

The page rewrote cms.json from the browser and restarted the server: it let an admin change the secrets and ports, and a mistake could
leave the CMS unable to start. /admin/cms-config is gone, with the helpers only it used. Syslog moves from the System menu to the
CMS group, next to Users, Groups and Settings.

The names of the plugin pages (Syslog, Replicator, Import, Sync) are translation keys now, in the menus, the breadcrumb, the tab and the
quick switcher (Syslog reads 系统日志 in Chinese). Their display name does not change: it is what the `plugins` right of a group names.
…a stalled page

Syslog: a very long log no longer fills the disk, the memory or a stalled page

syslog.maxLineLength cuts a line on the page (10000 characters, the file keeps it whole), syslog.maxFileSize copies a log file past
10 MB to <path>.1 and empties it, syslog.maxClientBuffer drops a page that stopped reading (8 MB). Each is off at 0.
Login page: a softer, more finished look, still following the system theme

An accent glow behind the card, a gradient strip along its top edge, a larger logo, a deeper shadow and a short fade-up when it appears.
…al since boot

node-os-utils has no bandwidth on Windows, so the server keeps the previous byte counters and works out bytes per second between two reports. The card shows the rate (the total is its tooltip, and what shows until a second report gives a rate).
Omnibar: Tab stays in the switcher instead of leaving for the page behind

The switcher is a modal dialog and its search field is its only stop: Tab walks the results, Shift+Tab walks back.
…m scrollbars

Record list: no colour fade on a row, and the scrollbars of the admin look like the system's

The list recycles its rows, so a row that stopped showing the selected record faded out of the selected look while already showing another one: a record looked selected for a split second when the list was filtered or sorted.

The record list, the panel of the selected records and the quick switcher drew the browser's own scrollbar, with its arrow buttons: a mixin set `scrollbar-width` on them, which in Chromium switches off the thin, rounded scrollbar that base.css gives every scroll container. The mixin is gone, and mixins.scss says why not to bring it back.
…ing several

Choosing several records: a click on a row adds or removes it, and the selected chips are not faded

A click on a row used to start the selection over with that record. The fade that hints at more records below now only shows when the list really overflows; it was washing out a short list of chips.
…p the language switch

The admin follows the user: theme, language and the title of the tab; the language switch is gone

A Language field on the user (the languages the admin is translated into, enUS by default), told by the login status and applied when
the admin opens; saving your own user with another theme or language applies it at once (the page reloads for a language). The date
pickers speak that language instead of the language of the field being edited. The language buttons of the app bar are removed: the
language is the user's.

The browser tab says where you are: "Gamma · Reference items · Newsroom", the record being edited, the resource or plugin page that is
open, and the title of the site (Settings > Title, the product name when there is none). The title used to be set as a side effect of
rendering the top bar, and only to the site title, so every page of the admin had the same tab. The page the server sends names the
product instead of the package.

Documentation: CONFIG and CONCEPTS describe the Theme and Language of a user and the admin languages they pick from, the date field
page says the calendar speaks the user's language, and UI_REDESIGN no longer lists a language switch in the app bar.
The name changes everywhere: the package (embed-cms), the display name and texts of the admin, the docs, the types, the CLI, the cookies
and session keys (embedCmsJwt, embedCmsUser), the browser storage keys, the global (window.embedCms) and the EMBED_CMS_* environment
variables. This is a breaking change for existing installs: everyone is logged out once, and saved preferences start again.
The repository moves to https://github.com/xiaodoudou/embed-cms (the URLs of the README, CONTRIBUTING, the install instructions and package.json).

The cookies are named after the server: embedCmsJwt-<mid> for the login token and embedCmsSid-<mid> for the session (a session.name in
the configuration still wins). Browsers share the cookies of a host between its ports, so two CMS on localhost used to overwrite and
clear each other's login. importFromRemote sends its token in the x-access-token header, which needs no cookie name.

New logo: an E drawn with node dots on its arms (the same language as the former N): the inline component, the standalone SVG, the
favicons (svg, ico, png) and the touch icon.

The README calls the product Embed CMS and UI_REDESIGN describes the new logo (and the mark alone in the app bar of the rail). The
screenshots of docs/ui that show the app bar or the logo (the README hero, the lists, the rail, the table, the dialogs, the toasts,
the settings, the design-system page, the phone views, in both themes) are retaken with the catalogue resources.
UI_REDESIGN: the multi-select click, the Tab rule of the switcher, where the toasts sit (bottom right, above the uploads panel, not
bottom centre) and what the screenshots of docs/ui show. ARCHITECTURE: the live network rate. SECURITY: the removed configuration
editor, the bounded log and the renamed cookies, in the changelog and the hardening checklist. string.md: dark mode is off by
default.
All relative links and anchors and every npm script the docs name were checked.
Build: rollup-plugin-visualizer 7, loaded only for `ANALYZE=1 npm run build`

Version 7 is ESM only, which the CommonJS-loaded Vite config cannot import statically. The bundle map (stats.html, git-ignored) was a
by-product of every build that nobody asked for: it is now written only on request, through a dynamic import (CONTRIBUTING says so).
The mocha suites load it with require(), which works with its ESM-only build on the Node versions the package supports (22.12 or later).
Unit and security suites give the same results as with chai 4 (the failures left are the Windows file locks at cleanup, and the
network timeouts of a few tests that pass when run again).
The plugin install, the stylesheet path and the props the record list and the log page use (items, item-size, key-field, size-field)
are unchanged in version 3, and the tests and the build pass. The scrolling itself has not been looked at in a browser yet.
…endency

Dependencies: @vuepic/vue-datepicker 14 (and date-fns 4, now a direct dependency)

The component is imported by name, its classes are renamed (dp__input is dp--input and so on, in the field and in base.css), the time
options moved into time-config, clearable into input-attrs, the day marker into ui.dayClass, the format function into formats.input,
and the locale is now a date-fns locale object instead of a language tag: the admin's two languages are mapped to them
(datePickerLocale). The calendar now starts its week where the language does (Sunday for English).
The component tests pass with the new props; the popup has not been looked at in a browser yet.
No-login mode works: the admin opens, has something to show, and signs what it writes

With disableAuthentication and disableJwtLogin both on, the admin reloaded for ever: the schemas of the paragraphs answered 401 for a
visitor who has no user, and the admin answers a failed load by logging out, which reloads the page. Now the schemas are served in
that mode, the admin shows the error instead of logging out when there is no login, and the anonymous group is given every right at
each start (an admin that shows nothing could not even have its groups edited; the log says so, loudly, like it does for localAdmin).
What an anonymous visitor writes is signed "anonymous~anonymous", not "anonymous~false" ("Updated by false" in the lists).
Version 3.0.1 (the first release under the name embed-cms; v3.0.0 was the last node-cms). package.json gets the bugs and homepage links of
the npm page and public access. The getting started guide installs from npm and says the package already holds the built admin app.
The package contents were checked with `npm pack --dry-run`: 253 files, 1.3 MB packed, no tests, keys, logs or data files.
…ase on a version tag

Release workflow: a version tag runs the tests, publishes to npm and creates the GitHub release

Pushing a version tag runs .github/workflows/release.yml: a check that the tag names the version of package.json and is on main, the
same jobs as on every push, `npm publish` (trusted publishing, so no token, with provenance, npm 11; a prerelease goes to the `next`
tag), then the GitHub release with generated notes. Each step needs the one before it to succeed, so nothing is published from a red build.

The test workflow can be called by it, and it follows the branch that is called main (it only ran on `master`). CONTRIBUTING says how to
release and what to set up once on npmjs.com (the trusted publisher: this repository, the workflow file release.yml).
vue3-shortkey is a development dependency, fetched as a tarball instead of from git

The admin is built with a fork of vue3-shortkey pinned to a commit. It was listed as a runtime dependency fetched from git, so
`npm install embed-cms` had to clone it (version 3.0.1 on npm does), and npm 12 refuses dependencies fetched from git, or from a URL, by
default. The admin is bundled in the package, so the library is only needed to build it: it is a development dependency now, pinned to
the tarball of the same commit (no git, and the lockfile holds its integrity). CONTRIBUTING says to use npm 10 or 11 to build from source.
3.0.1 was published to npm with vue3-shortkey as a runtime dependency fetched from git; 3.0.2 is the first release without it (and the first
published by the release workflow, with its provenance). The package contents are otherwise those of 3.0.1.
Vuetify 4 and the other dependency updates, the Vite build that also writes dist/kit.css, the lint, knip and test configuration, the
CI workflows, and the npm scripts for the benchmarks, the docs and the screenshots.
input: 'token' holds a key or an id another system uses. A new record gets one as its form opens, and the server makes one for a record created over the API without it. The field is read-only, with Copy and Regenerate, and options.length, options.prefix and options.format shape the token. The docs count of input types was out of date and now says 36.
…a TCP port

Records went over netPort, a TCP port of their own. They now go over a websocket at /_replicator next to the REST API, so a CMS needs one port, and the records travel over wss:// when it is served over HTTPS. A node listens for peers only when replication.secret is set, and answers 404 there otherwise. Peers are reached by the url of their API; host, port and netPort are ignored, with a warning for netPort. The Diffie-Hellman step is gone: its key was never used. The record updates socket shares the HTTP server through a small upgrade router.

BREAKING CHANGE: remove netPort, give each peer its url, set replication.secret on every node.
…ies them

The type of a resource says what the node that starts a sync does: upstream sends, downstream fetches, normal does both, and every node declares the same type. The direction of a peer says which way it is: downstream is a node this one sends to, upstream one it fetches from. The example of a central node and its sites now works, and the page says why users, groups and settings are best kept to each node.
…rs list

_users, _groups, _settings, _sync and _xlsx now replicate only when replication.peersByResource names them. Every node makes its own admins group and localAdmin on its first start, and a node that fetched another node's admins group could no longer update it, so its admin login stopped working.
…c token

The token field was read-only and filled itself. It is now a text field with Generate and Copy buttons: a token is typed, pasted, or made at random. options.autoGenerate gives a new record one by itself, in the admin and over the API; it is off by default. The Token under This CMS in the Sync settings is a token field, so its secret no longer has to be invented by hand; the token of the other CMS stays a text field, where it is pasted.
…it lists

A node keeps a link open to each peer it receives a resource from. The peer sends the changes the node lacks, then each change as it is written, and the link connects again by itself after an outage, after a delay that grows while it fails. The node a peer connects to only sends, so a connecting peer can no longer write. Files follow the records half a second later. A MongoDB or PostgreSQL store sends what it has and ends: the node pulls again every replication.pollInterval.

The type of a resource is what the node does with it: upstream is only sent from here, normal and downstream are also pulled from the peers. With strictTypes, links go only to peers marked upstream or normal. Writes no longer start a sync, and the Replicator page shows the state of the links instead of buttons. POST /replicator/sync/:resource connects the links of a resource again.

BREAKING CHANGE: list on each node the peers it receives from; settleDelay and POST /replicator/sync/:resource/:id are gone.
A peer is named the same way in both columns: its host, with its machine id next to it, which a link learns at its handshake. The direction is the main text and the type sits under it, each link takes two lines (the state and the peer, then the last change or the error and the next try), the two link columns have the same width, and an empty side says None.
…eplicator page

Each direction is a pill with an icon and a colour: Sends with an arrow up, Receives with an arrow down, Both ways with both. The headers of the two link columns carry the same arrows and colours, and each link sits in a small box edged with the colour of its side. Green stays the colour of a live link.
The head takes the darker inset surface and a stronger line under it, and its cells are centred, so the titles with and without an arrow sit on one line.
The table sat in its own bordered box inside a card: the card and its title are gone, so the table is the one box of the page, under the intro. Its head is the light inset grey again, its titles still centred.
feat(fields): add a token field, and use it for the Sync token
feat(replication)!: replicate live, over a websocket on the HTTP port
@xiaodoudou
xiaodoudou merged commit 5b899e2 into main Oct 12, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant