Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ updates:
dependency-type: development
ignore:
# commander is version-synced with cli/package.json (a test enforces
# it), and the cli supports Node >=20.12 while commander 15 requires
# Node >=22.12. Major bumps are a deliberate cli decision.
# it). Major bumps are a deliberate cli decision.
- dependency-name: commander
update-types: ["version-update:semver-major"]
# typescript is aliased to @typescript/typescript6 (the JS compiler
Expand Down
31 changes: 29 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,22 @@ jobs:

- run: npm run build

# The cli package targets older runtimes than the server (engines >=20.12),
# The cli package targets older runtimes than the server (engines >=22.12),
# but the repo's @types/node tracks the server's Node version — so only a
# runtime smoke test catches accidental use of too-new APIs in the CLI.
# The floor row pins 22.12 so the oldest supported API surface actually
# runs; label keeps the required-check name at the major, so the ruleset
# contexts survive future floor bumps.
cli-smoke:
name: cli-smoke (${{ matrix.label }})
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22, 24]
include:
- node-version: "22.12"
label: "22"
- node-version: "24"
label: "24"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -98,3 +106,22 @@ jobs:
mkdir -p /tmp/smoke-vault/.obsidian
node cli/dist/bin.js init --yes --vault-path /tmp/smoke-vault --dir /tmp/smoke-out
grep -q "VAULT_PATH=/tmp/smoke-vault" /tmp/smoke-out/.env

# The refusal branch never executes on supported majors, so the floor
# row re-runs the built CLI on EOL Node 20 and asserts the refusal
# shape: nonzero exit, the floor in the message, both setup pointers.
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
if: matrix.label == '22'
with:
node-version: 20

- name: Refusal smoke on Node 20
if: matrix.label == '22'
run: |
if node cli/dist/bin.js --help > refusal-output.txt 2>&1; then
echo "expected the CLI to refuse Node 20"
exit 1
fi
grep -F "requires Node.js >= 22.12.0" refusal-output.txt
grep -F "deploy/local/README.md" refusal-output.txt
grep -F "deploy/remote/README.md" refusal-output.txt
9 changes: 5 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,13 @@ truth. Key points:
```

4. **Fill out the PR template** — the checklist mirrors CI
5. **Required checks must pass** — the `main` ruleset requires all eight;
5. **Required checks must pass** — the `main` ruleset requires all seven;
each blocks the merge and the finding details are in its job log:
- `checks` — prettier, lint, markdownlint, knip, test, and build
- `cli-smoke (20)` / `cli-smoke (22)` / `cli-smoke (24)` — builds the
CLI and runs `init` on each supported Node major, catching APIs too
new for the CLI's `engines` range
- `cli-smoke (22)` / `cli-smoke (24)` — builds the
CLI and runs `init` on the engines floor (22.12) and the newest
major (24), catching APIs too new for the CLI's `engines` range;
the floor row also asserts the too-old refusal on Node 20
- `arch-smoke (amd64)` / `arch-smoke (arm64)` — builds the Docker image
and boots it on a native runner for each architecture, then boots the
remote image with a stubbed Sync client to run its init chain
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

### Local (2 minutes — Docker + your vault folder)

**Prerequisites:** [Docker](https://docs.docker.com/get-docker/) (or a Docker-compatible runtime, e.g. OrbStack, Colima, Podman), Node.js >= 20.12 (only for the CLI — the server itself runs in Docker), and an Obsidian vault (or any folder of `.md` files).
**Prerequisites:** [Docker](https://docs.docker.com/get-docker/) (or a Docker-compatible runtime, e.g. OrbStack, Colima, Podman), Node.js >= 22.12 (only for the CLI — the server itself runs in Docker), and an Obsidian vault (or any folder of `.md` files).

```bash
npx vault-cortex@latest init
Expand Down Expand Up @@ -111,7 +111,7 @@ All three need an [Obsidian Sync](https://obsidian.md/sync) subscription. Whiche

#### Self-hosted: your own VPS

The [vault-cortex CLI](./cli/) sets up the same container on any Linux box you run — you manage the server, the image, and updates. You need Node.js >= 20.12 for the CLI itself; the server runs in Docker.
The [vault-cortex CLI](./cli/) sets up the same container on any Linux box you run — you manage the server, the image, and updates. You need Node.js >= 22.12 for the CLI itself; the server runs in Docker.

```bash
# On your VPS:
Expand Down
2 changes: 1 addition & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ During `init --mode remote`, this flow is offered automatically.

## Requirements

- Node.js >= 20.12 (only for this CLI — the server itself runs in Docker)
- Node.js >= 22.12 (only for this CLI — the server itself runs in Docker)
- [Docker](https://docs.docker.com/get-docker/) or a Docker-compatible
runtime (e.g. OrbStack, Colima, Podman) to run the server — the CLI
manages the container through the `docker` command (on Linux, see
Expand Down
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"README.md"
],
"engines": {
"node": ">=20.12.0"
"node": ">=22.12.0"
},
"repository": {
"type": "git",
Expand Down
26 changes: 23 additions & 3 deletions cli/src/__tests__/node-version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { readFileSync } from "node:fs"
import { fileURLToPath } from "node:url"
import { describe, expect, it } from "vitest"

import { minimumNodeVersion, satisfiesMinimum } from "../node-version.js"
import {
minimumNodeVersion,
nodeVersionRefusalMessage,
satisfiesMinimum,
} from "../node-version.js"

describe("minimumNodeVersion", () => {
it("extracts the floor from a >= range", () => {
Expand All @@ -27,12 +31,12 @@ describe("minimumNodeVersion", () => {

const minimum = minimumNodeVersion(manifest.engines.node)

expect(minimum).toBe("20.12.0")
expect(minimum).toBe("22.12.0")
})

it("throws on a range with no version in it", () => {
expect(() => minimumNodeVersion("latest")).toThrow(
"Cannot parse engines range",
"Cannot parse engines range: latest",
)
})
})
Expand Down Expand Up @@ -95,3 +99,19 @@ describe("satisfiesMinimum", () => {
expect(satisfied).toBe(expected)
})
})

describe("nodeVersionRefusalMessage", () => {
it("names the floor, the running version, and both no-Node setup guides", () => {
const message = nodeVersionRefusalMessage({
minimum: "22.12.0",
current: "20.20.2",
})

expect(message).toBe(
"vault-cortex requires Node.js >= 22.12.0 (you have 20.20.2).\n" +
"Upgrade at https://nodejs.org — or use a no-Node manual setup:\n" +
" local: https://github.com/aliasunder/vault-cortex/blob/main/deploy/local/README.md\n" +
" remote: https://github.com/aliasunder/vault-cortex/blob/main/deploy/remote/README.md",
)
})
})
13 changes: 9 additions & 4 deletions cli/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import { readFileSync } from "node:fs"
import { fileURLToPath } from "node:url"

import { minimumNodeVersion, satisfiesMinimum } from "./node-version.js"
import {
minimumNodeVersion,
nodeVersionRefusalMessage,
satisfiesMinimum,
} from "./node-version.js"

const pkg: { version: string; engines: { node: string } } = JSON.parse(
readFileSync(
Expand All @@ -19,9 +23,10 @@ const { version, engines } = pkg
const requiredNodeVersion = minimumNodeVersion(engines.node)
if (!satisfiesMinimum(process.versions.node, requiredNodeVersion)) {
console.error(
`vault-cortex requires Node.js >= ${requiredNodeVersion} (you have ${process.versions.node}).\n` +
`Upgrade at https://nodejs.org — or use the no-Node manual setup:\n` +
`https://github.com/aliasunder/vault-cortex/blob/main/deploy/local/README.md`,
nodeVersionRefusalMessage({
minimum: requiredNodeVersion,
current: process.versions.node,
}),
)
process.exit(1)
}
Expand Down
27 changes: 23 additions & 4 deletions cli/src/node-version.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
/** Matches the first dotted version number in an engines range like ">=20.12.0". */
/** Matches the first dotted version number in an engines range like ">=22.12.0". */
const VERSION_IN_RANGE = /(\d+)\.(\d+)(?:\.(\d+))?/

/**
* Extracts the minimum version from a simple engines range (">=20.12.0").
* Extracts the minimum version from a simple engines range (">=22.12.0").
* The CLI only ever declares a floor, so the first version in the string
* is the minimum.
*/
export const minimumNodeVersion = (enginesRange: string): string => {
const match = VERSION_IN_RANGE.exec(enginesRange)
if (match === null)
throw new Error(`Cannot parse engines range: ${enginesRange}`)
if (!match) throw new Error(`Cannot parse engines range: ${enginesRange}`)
const [, major, minor, patch] = match
return `${major}.${minor}.${patch ?? "0"}`
}

/**
* The refusal printed when the running Node is below the engines floor.
* Lives in this zero-import module so bin.ts can build it before any
* dependency-laden import runs on an unsupported runtime.
*/
export const nodeVersionRefusalMessage = ({
minimum,
current,
}: {
minimum: string
current: string
}): string => {
return (
`vault-cortex requires Node.js >= ${minimum} (you have ${current}).\n` +
`Upgrade at https://nodejs.org — or use a no-Node manual setup:\n` +
` local: https://github.com/aliasunder/vault-cortex/blob/main/deploy/local/README.md\n` +
` remote: https://github.com/aliasunder/vault-cortex/blob/main/deploy/remote/README.md`
)
}

/**
* Numeric major.minor.patch comparison: is current >= minimum?
* The most significant differing segment decides; equal versions satisfy.
Expand Down
2 changes: 1 addition & 1 deletion deploy/local/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Obsidian Sync — just Docker and a folder of `.md` files.
## Prerequisites

- [Docker](https://docs.docker.com/get-docker/) (v20.10+)
- Node.js >= 20.12 — only for the CLI setup below; the
- Node.js >= 22.12 — only for the CLI setup below; the
[manual setup](#setup) needs just Docker
- An Obsidian vault (or any folder of Markdown files)

Expand Down
4 changes: 2 additions & 2 deletions deploy/remote/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ starts the server and prints the connection details for your MCP client
<details>
<summary><strong>Don't have Node.js installed?</strong></summary>

The CLI needs Node.js >= 20.12 (the server itself runs in Docker). On Ubuntu/Debian:
The CLI needs Node.js >= 22.12 (the server itself runs in Docker). On Ubuntu/Debian:

```bash
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
Expand Down Expand Up @@ -68,7 +68,7 @@ Or clone the repo and `cd deploy/remote`.

**3. Generate your Obsidian Sync auth token** (one-time):

If you have Node.js >= 20.12 on this machine, the CLI signs in to your
If you have Node.js >= 22.12 on this machine, the CLI signs in to your
Obsidian account and captures the token:

```bash
Expand Down
Loading