diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 654359a6..a9d676c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,7 +54,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 registry-url: https://registry.npmjs.org # trusted publishing needs npm 11.5.1 or later; not npm 12, which refuses by default the dependencies fetched from a URL (vue3-shortkey is one) - run: npm install -g 'npm@^11.5.1' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 901c14f2..9fad4fd7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 - name: Commits of the pull request if: github.event_name == 'pull_request' run: node scripts/commitlint.js --range "$BASE..$HEAD" @@ -48,7 +48,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 cache: npm - run: npm ci - name: Lint @@ -96,7 +96,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 cache: npm - run: npm ci - name: Driver contract suite diff --git a/CHANGELOG.md b/CHANGELOG.md index 29604be2..0014aa18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ What changed in each version of Embed CMS, from 3.0.0 on, newest first. The vers ## [Unreleased] +## [3.1.1] - 2026-10-11 + +### Changed + +- **Node 24 is the version Embed CMS requires, and Node 22 no longer is.** `engines` was `^22.12.0`. It is now `^24.0.0`, so installing on Node 22 prints an engine warning. CI runs every job on Node 24, and the Docker example runs on `node:24-bookworm-slim`. + +### Fixed + +- **`cms-import` and `cms-import-remote` crashed on Node 24 when their input ended right after a wrong answer.** Asked again after the wrong answer, the question was put to a closed input, which Node 24 refuses with `ERR_USE_AFTER_CLOSE`. It now answers no, as it does when the input closes before any answer. + ## [3.1.0] - 2026-10-11 ### Added @@ -221,6 +231,7 @@ The release that turned node-cms into a project that stands on its own: the firs - 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. [Unreleased]: https://github.com/xiaodoudou/embed-cms/compare/v3.0.5...HEAD +[3.1.1]: https://github.com/xiaodoudou/embed-cms/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/xiaodoudou/embed-cms/compare/v3.0.7...v3.1.0 [3.0.7]: https://github.com/xiaodoudou/embed-cms/compare/v3.0.6...v3.0.7 [3.0.6]: https://github.com/xiaodoudou/embed-cms/compare/v3.0.5...v3.0.6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a39b577..b69b0a7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Thanks for helping. This page gets you from a clone to a change that passes CI: ## Setting up -You need Node.js 22.12 or later (CI runs Node 22) and git. +You need Node.js 24 (the version CI runs) and git. ```sh git clone https://github.com/xiaodoudou/embed-cms.git embed-cms diff --git a/README.md b/README.md index f1ff7f29..e5209b92 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ > A headless CMS for Node.js: describe your content in JavaScript files, get an admin app, a REST API and a JavaScript API -[![License](https://img.shields.io/badge/License-GPL--3.0--only-blue?style=flat-square)](LICENSE) [![Node](https://img.shields.io/badge/Node-%3E%3D22.12-339933?style=flat-square)](#try-it-in-five-minutes) [![Storage](https://img.shields.io/badge/Storage-JSON%20%7C%20MongoDB%20%7C%20PostgreSQL-orange?style=flat-square)](docs/reference/CONFIG.md) [![Express](https://img.shields.io/badge/Express-mountable-lightgrey?style=flat-square)](docs/start/GETTING_STARTED.md) [![Locales](https://img.shields.io/badge/Content-translatable-blueviolet?style=flat-square)](docs/start/CONCEPTS.md) [![Tests](https://img.shields.io/badge/Tests-unit%20%7C%20security%20%7C%20frontend-brightgreen?style=flat-square)](docs/contributing/TESTING.md) +[![License](https://img.shields.io/badge/License-GPL--3.0--only-blue?style=flat-square)](LICENSE) [![Node](https://img.shields.io/badge/Node-24-339933?style=flat-square)](#try-it-in-five-minutes) [![Storage](https://img.shields.io/badge/Storage-JSON%20%7C%20MongoDB%20%7C%20PostgreSQL-orange?style=flat-square)](docs/reference/CONFIG.md) [![Express](https://img.shields.io/badge/Express-mountable-lightgrey?style=flat-square)](docs/start/GETTING_STARTED.md) [![Locales](https://img.shields.io/badge/Content-translatable-blueviolet?style=flat-square)](docs/start/CONCEPTS.md) [![Tests](https://img.shields.io/badge/Tests-unit%20%7C%20security%20%7C%20frontend-brightgreen?style=flat-square)](docs/contributing/TESTING.md) You describe your content in plain JavaScript files, one per resource, and Embed CMS gives you three things from them: an admin app where editors write and translate that content, a REST API that serves it to your sites and apps, and a JavaScript API for your own server code. @@ -18,7 +18,7 @@ It keeps your content in a LevelDB database on disk by default, so there is no d ## Try it in five minutes -You need [Node.js](https://nodejs.org/) 22.12 or later and git. +You need [Node.js](https://nodejs.org/) 24 and git. ```sh git clone https://github.com/xiaodoudou/embed-cms.git embed-cms diff --git a/docs/examples/docker/Dockerfile b/docs/examples/docker/Dockerfile index fce35a99..0d92410f 100644 --- a/docs/examples/docker/Dockerfile +++ b/docs/examples/docker/Dockerfile @@ -4,14 +4,14 @@ # Built by scripts/build.sh. # --- the modules: the only stage that needs the network and a compiler's worth of tools ----------------------------------- -FROM node:22-bookworm-slim AS modules +FROM node:24-bookworm-slim AS modules WORKDIR /app # the lock file first: this layer is only made again when the dependencies change COPY package.json package-lock.json ./ RUN npm ci --omit=dev --no-audit --no-fund # --- the image that runs ------------------------------------------------------------------------------------------------- -FROM node:22-bookworm-slim +FROM node:24-bookworm-slim ENV NODE_ENV=production \ PORT=3000 \ DATA_DIR=/data diff --git a/docs/examples/docker/README.md b/docs/examples/docker/README.md index 5980ad6e..09076c50 100644 --- a/docs/examples/docker/README.md +++ b/docs/examples/docker/README.md @@ -201,7 +201,7 @@ Put a proxy (nginx, Caddy or Traefik) on the machine and give it the https certi |---|---| | Change a secret (a leak, someone leaves) | Edit `.env`, or empty the line and run `./scripts/env.sh`. Then run `./scripts/start.sh`, and compose replaces the container with one that has the new value. Changing `AUTH_SECRET` or `SESSION_SECRET` signs everybody out and does nothing else. | | Upgrade embed-cms | Raise `embed-cms` in `package.json` and run `npm install --package-lock-only`. Then run `./scripts/build.sh` and `./scripts/start.sh`. The data in the volume stays. | -| Back up | Stop the container first, because the databases are files that are written while it runs. Run `docker compose stop`, then `docker run --rm -v embed-cms-docker_data:/data -v "$PWD":/backup node:22-bookworm-slim tar czf /backup/data.tgz -C /data .`, then `./scripts/start.sh`. | +| Back up | Stop the container first, because the databases are files that are written while it runs. Run `docker compose stop`, then `docker run --rm -v embed-cms-docker_data:/data -v "$PWD":/backup node:24-bookworm-slim tar czf /backup/data.tgz -C /data .`, then `./scripts/start.sh`. | | Use your own project | Copy this folder. Replace `resources/` and `content.json` (and add your site's routes to `server.js`). Keep the rest. Add every variable your code reads to `.env.example`. | | Use another store | PostgreSQL and MongoDB read their credentials from the environment ([storage engines](../../reference/CONFIG.md#storage-engines)). Put `POSTGRES_USER` and `POSTGRES_PASSWORD` in `.env` like the other secrets, and add the database as one more service in `compose.yaml`. | diff --git a/docs/examples/docker/package-lock.json b/docs/examples/docker/package-lock.json index 8903db3c..59447e60 100644 --- a/docs/examples/docker/package-lock.json +++ b/docs/examples/docker/package-lock.json @@ -12,7 +12,7 @@ "express": "^4.18.2" }, "engines": { - "node": "^22.12.0" + "node": "^24.0.0" } }, "node_modules/@aws-sdk/core": { diff --git a/docs/examples/docker/package.json b/docs/examples/docker/package.json index 16d81d7d..2a04caf6 100644 --- a/docs/examples/docker/package.json +++ b/docs/examples/docker/package.json @@ -12,6 +12,6 @@ "express": "^4.18.2" }, "engines": { - "node": "^22.12.0" + "node": "^24.0.0" } } diff --git a/docs/reference/CONFIG.md b/docs/reference/CONFIG.md index 14f720ff..58622a5d 100644 --- a/docs/reference/CONFIG.md +++ b/docs/reference/CONFIG.md @@ -178,7 +178,7 @@ Every resource keeps its records in a store, and you choose which kind with `dbE | `dbEngine.type` | What it is | Needs | |---|---|---| | `leveldb` (the default) | a LevelDB folder per resource, read from disk | nothing (the `classic-level` package comes with embed-cms) | -| `sqlite` | one SQLite file per resource, read from disk | nothing (it is part of Node 22; Node marks it experimental) | +| `sqlite` | one SQLite file per resource, read from disk | nothing (it is part of Node; Node marks it experimental) | | `jsondown` | all records in memory, saved to one JSON file per resource | nothing | | `mongodb` | one collection per resource | a MongoDB server | | `postgres` | one table per resource | a PostgreSQL server | diff --git a/lib/util/confirm.js b/lib/util/confirm.js index 37fd483f..3c9dbd1f 100644 --- a/lib/util/confirm.js +++ b/lib/util/confirm.js @@ -9,6 +9,10 @@ const readline = require('readline') */ async function confirm (question, { input = process.stdin, output = process.stdout } = {}) { const rl = readline.createInterface({ input, output }) + // an input that ended in the same chunk as a wrong answer has closed the interface by the time the question is asked again, and Node 24 + // throws when a closed interface is prompted + let closed = false + rl.on('close', () => { closed = true }) rl.setPrompt(`${question} `) rl.prompt() try { @@ -19,7 +23,9 @@ async function confirm (question, { input = process.stdin, output = process.stdo return text === 'yes' } output.write('yes / no\n') - rl.prompt() + if (!closed) { + rl.prompt() + } } return false } finally { diff --git a/package-lock.json b/package-lock.json index 713bfcec..3c2f40f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "embed-cms", - "version": "3.1.0", + "version": "3.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "embed-cms", - "version": "3.1.0", + "version": "3.1.1", "license": "GPL-3.0-only", "dependencies": { "@e965/xlsx": "^0.20.3", @@ -116,7 +116,7 @@ "vue-eslint-parser": "^10.4.1" }, "engines": { - "node": "^22.12.0" + "node": "^24.0.0" } }, "node_modules/@asamuzakjp/css-color": { diff --git a/package.json b/package.json index 93a7a0fc..e31f0410 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "embed-cms", - "version": "3.1.0", + "version": "3.1.1", "description": "Content management system, written in node.js, uses leveldb as an asset and content store.", "main": "index.js", "types": "index.d.ts", @@ -53,7 +53,7 @@ "migrate-store": "node scripts/migrateStore.js" }, "engines": { - "node": "^22.12.0" + "node": "^24.0.0" }, "repository": { "type": "git", diff --git a/skills/embed-cms-create-site/SKILL.md b/skills/embed-cms-create-site/SKILL.md index cdc2e85d..c37a00d4 100644 --- a/skills/embed-cms-create-site/SKILL.md +++ b/skills/embed-cms-create-site/SKILL.md @@ -11,7 +11,7 @@ The docs are in the `docs/` folder of https://github.com/xiaodoudou/embed-cms, n ## Steps -1. **Ask only what you need**: what the site is for, the content types, one language or several (`enUS`, `zhCN`). Node must be `^22.12.0`. +1. **Ask only what you need**: what the site is for, the content types, one language or several (`enUS`, `zhCN`). Node must be 24. 2. **Scaffold** in the project folder: ```sh npm init -y && npm install embed-cms express