Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:

jobs:
quality:
uses: robert7/workflows/.github/workflows/node-ci.yml@v0.3.0
uses: robert7/workflows/.github/workflows/node-ci.yml@9b0544fccadffeadad23ac555f5861f41a98e61f
with:
node_versions: '["22.13.0", "24"]'
coverage_node_version: '24'
secrets:
codecov_token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.19.0
24
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Versioning](https://semver.org/spec/v2.0.0.html).

### Changed

- Require Node.js `22.13.0` or newer, use Node.js 24 LTS for local development, and validate both Node.js 22 and 24 in
CI now that Node.js 20 is end-of-life.
- Ignore Python bytecode caches and remove previously tracked cache artifacts.
- Link the canonical cross-repo pull request validation and adoption workflow for maintainers and coding agents.
- Update `remnote_search` tools reference, CLI command `search` reference, and agent skill documentation to include advanced scoped search with parent ID and document cursor limitations.
Expand All @@ -30,6 +32,8 @@ Versioning](https://semver.org/spec/v2.0.0.html).

### Fixed

- Make Node.js environment checks reject malformed versions cleanly and report a missing `.nvmrc` without leaking
shell errors.
- Reject empty `parentRemId` values for `remnote_search` instead of treating them as unscoped search.

## [0.16.0] - 2026-06-05
Expand Down
5 changes: 3 additions & 2 deletions docs/guides/development-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Instructions for contributors who want to modify, test, or develop the RemNote M

## Prerequisites

- **Node.js** >= 20.19.0 for local development and code-quality commands (preferably via nvm)
- **Node.js** >= 22.13.0 for local development and code-quality commands; `.nvmrc` selects Node.js 24 LTS
- **git** - Version control
- **RemNote app** with RemNote Automation Bridge plugin (for testing)
- **Claude Code CLI** or another MCP client (for integration testing)
Expand Down Expand Up @@ -80,7 +80,8 @@ source ./node-check.sh && npm run dev
```

This script ensures a Node.js version compatible with the local development toolchain is available via nvm, and will
accept newer installed Node versions when they still satisfy the repo floor.
accept newer installed Node versions when they still satisfy the repo floor. CI verifies both the minimum supported
Node.js 22 line and the recommended Node.js 24 LTS line.

### Type Checking

Expand Down
8 changes: 4 additions & 4 deletions docs/guides/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Complete installation instructions for the RemNote MCP Server.

## Prerequisites

- **Node.js** >= 20.19.0
- **Node.js** >= 22.13.0 (Node.js 24 LTS recommended)
- **RemNote app** (desktop or web browser)
- **RemNote Automation Bridge plugin** - Install from [GitHub](https://github.com/robert7/remnote-mcp-bridge) -
registration in the RemNote marketplace is pending approval
Expand Down Expand Up @@ -251,11 +251,11 @@ remnote-cli search "MCP Test" --text
**Solution:**
```bash
node --version
# Ensure >= 20.19.0
# Ensure >= 22.13.0

# Update Node.js using nvm:
nvm install 18
nvm use 18
nvm install 24
nvm use 24
```

### Permission Errors (npm install -g)
Expand Down
6 changes: 3 additions & 3 deletions docs/guides/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ remnote-mcp-server --version
```bash
# Check version
node --version
# Development tooling requires >= 20.19.0
# Development tooling requires >= 22.13.0

# Update Node.js
nvm install 20.19.0
nvm use 20.19.0
nvm install 24
nvm use 24
```

3. **Missing dependencies**
Expand Down
100 changes: 74 additions & 26 deletions node-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,85 @@
# Usage:
# source "$(dirname "$0")/node-check.sh" || exit 1

# Check if Node.js is already available in PATH
if command -v node &> /dev/null && command -v npm &> /dev/null; then
# Node and npm are already available, exit silently
return 0 2>/dev/null || exit 0
fi
MIN_NODE_VERSION="22.13.0"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

version_is_supported() {
local current_version="${1//$'\r'/}"
local current_major current_minor current_patch
local minimum_major minimum_minor minimum_patch

if [[ ! "$current_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
return 1
fi

IFS='.' read -r current_major current_minor current_patch <<< "$current_version"
IFS='.' read -r minimum_major minimum_minor minimum_patch <<< "$MIN_NODE_VERSION"

((current_major > minimum_major)) ||
((current_major == minimum_major && current_minor > minimum_minor)) ||
((current_major == minimum_major && current_minor == minimum_minor && current_patch >= minimum_patch))
}
Comment thread
robert7 marked this conversation as resolved.

current_node_version() {
node -p 'process.versions.node'
}

project_node_version() {
local version_file="$SCRIPT_DIR/.nvmrc"
local version

if [ ! -r "$version_file" ]; then
return 1
fi

IFS= read -r version < "$version_file"
if [ -z "$version" ]; then
return 1
fi

# Node not in PATH, attempt to load via NVM
if [ -s "$HOME/.nvm/nvm.sh" ]; then
# Source NVM
printf '%s' "$version"
}

activate_project_node() {
local project_version

if [ ! -s "$HOME/.nvm/nvm.sh" ]; then
return 1
fi

project_version="$(project_node_version)" || return 1
source "$HOME/.nvm/nvm.sh" &> /dev/null
nvm use "$project_version" &> /dev/null
}
Comment thread
robert7 marked this conversation as resolved.

# Load default Node version
if ! nvm use default &> /dev/null; then
echo "Error: NVM found but unable to load default Node version." >&2
echo "Please configure NVM with: nvm install --lts && nvm alias default lts/*" >&2
return 1 2>/dev/null || exit 1
commands_available() {
command -v node &> /dev/null && command -v npm &> /dev/null
}

print_install_guidance() {
local project_version

if project_version="$(project_node_version)"; then
echo "Install the project version with: nvm install $project_version" >&2
else
echo "Project Node version file is missing or unreadable: $SCRIPT_DIR/.nvmrc" >&2
fi
else
# NVM not found
echo "Error: Node.js not found and NVM is not installed." >&2
echo "Please install Node.js via:" >&2
echo " - NVM: https://github.com/nvm-sh/nvm" >&2
echo " - Official installer: https://nodejs.org/" >&2
return 1 2>/dev/null || exit 1
}

if commands_available && version_is_supported "$(current_node_version)"; then
return 0 2>/dev/null || exit 0
fi

if activate_project_node && commands_available && version_is_supported "$(current_node_version)"; then
return 0 2>/dev/null || exit 0
fi

# Final verification that both node and npm are now available
if ! command -v node &> /dev/null || ! command -v npm &> /dev/null; then
echo "Error: Node.js setup failed. node or npm command not found." >&2
return 1 2>/dev/null || exit 1
if commands_available; then
echo "Error: Node.js >= $MIN_NODE_VERSION is required; found $(current_node_version)." >&2
else
echo "Error: Node.js >= $MIN_NODE_VERSION and npm are required." >&2
fi

# Success - Node.js and npm are available
return 0 2>/dev/null || exit 0
print_install_guidance
return 1 2>/dev/null || exit 1
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@
"vitest": "^4.1.3"
},
"engines": {
"node": ">=20.19.0"
"node": ">=22.13.0"
}
}
107 changes: 107 additions & 0 deletions test/unit/node-check-script.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
chmodSync,
copyFileSync,
existsSync,
mkdtempSync,
mkdirSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { spawnSync } from 'node:child_process';
import { describe, expect, it } from 'vitest';

function runNodeCheck(version: string, includeNvmrc = true, shell = 'bash') {
const tempRoot = mkdtempSync(join(tmpdir(), 'remnote-node-check-'));
const binDir = join(tempRoot, 'bin');
mkdirSync(binDir);

copyFileSync(resolve('node-check.sh'), join(tempRoot, 'node-check.sh'));
if (includeNvmrc) {
copyFileSync(resolve('.nvmrc'), join(tempRoot, '.nvmrc'));
}

writeFileSync(
join(binDir, 'node'),
`#!/usr/bin/env bash
if [[ "$1" == "-p" ]]; then
echo "${version}"
exit 0
fi
echo "v${version}"
`
);
writeFileSync(join(binDir, 'npm'), '#!/usr/bin/env bash\nexit 0\n');
chmodSync(join(binDir, 'node'), 0o755);
chmodSync(join(binDir, 'npm'), 0o755);

try {
const result = spawnSync(shell, ['-c', 'source ./node-check.sh && echo ready'], {
cwd: tempRoot,
encoding: 'utf-8',
env: {
PATH: `${binDir}:/bin:/usr/bin`,
HOME: tempRoot,
},
});

return { result, tempRoot };
} finally {
rmSync(tempRoot, { recursive: true, force: true });
}
}

describe('node-check.sh', () => {
const zshAvailable = spawnSync('zsh', ['--version']).status === 0;

it.each(['22.13.0', '22.14.0', '24.0.0', '22.13.0\r'])(
'accepts supported Node.js version %s',
(version) => {
const { result } = runNodeCheck(version);

expect(result.status).toBe(0);
expect(result.stdout).toContain('ready');
}
);

it.each(['20.19.0', '22.12.0'])('rejects unsupported Node.js version %s', (version) => {
const { result } = runNodeCheck(version);

expect(result.status).not.toBe(0);
expect(result.stderr).toContain(`Node.js >= 22.13.0 is required; found ${version}.`);
expect(result.stderr).toContain('nvm install 24');
});

it.each(['22.13.0-rc.1', 'not-a-version'])(
'rejects malformed Node.js version %s without a shell error',
(version) => {
const { result } = runNodeCheck(version);

expect(result.status).not.toBe(0);
expect(result.stderr).toContain(`Node.js >= 22.13.0 is required; found ${version}.`);
expect(result.stderr).not.toContain('arithmetic syntax error');
}
);

it('reports a missing project Node version file without leaking a cat error', () => {
const { result } = runNodeCheck('20.19.0', false);

expect(result.status).not.toBe(0);
expect(result.stderr).toContain('Project Node version file is missing or unreadable:');
expect(result.stderr).not.toContain('cat:');
});

it('removes its temporary test directory', () => {
const { tempRoot } = runNodeCheck('24.0.0');

expect(existsSync(tempRoot)).toBe(false);
});

it.skipIf(!zshAvailable)('can be sourced from zsh', () => {
const { result } = runNodeCheck('24.0.0', true, 'zsh');

expect(result.status).toBe(0);
expect(result.stdout).toContain('ready');
});
});
17 changes: 17 additions & 0 deletions test/unit/project-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@ import { readFileSync } from 'node:fs';
import vitestConfig from '../../vitest.config.js';

describe('project config', () => {
it('keeps the supported Node.js policy aligned across package, lockfile, nvm, and CI', () => {
const packageJson = JSON.parse(readFileSync('package.json', 'utf-8')) as {
engines: { node: string };
};
const packageLock = JSON.parse(readFileSync('package-lock.json', 'utf-8')) as {
packages: { '': { engines: { node: string } } };
};
const nvmVersion = readFileSync('.nvmrc', 'utf-8').trim();
const ciWorkflow = readFileSync('.github/workflows/ci.yml', 'utf-8');

expect(packageJson.engines.node).toBe('>=22.13.0');
expect(packageLock.packages[''].engines.node).toBe(packageJson.engines.node);
expect(nvmVersion).toBe('24');
expect(ciWorkflow).toContain(`node_versions: '["22.13.0", "24"]'`);
expect(ciWorkflow).toContain("coverage_node_version: '24'");
});

it('prevents production dist emit when TypeScript reports errors', () => {
const tsconfig = JSON.parse(readFileSync('tsconfig.json', 'utf-8')) as {
compilerOptions: { noEmitOnError?: boolean };
Expand Down