Skip to content
Merged

4.x #33

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
6940429
feat!: convert to pnpm monorepo with server app
May 24, 2026
d994a3e
chore(core): scaffold @rsscloud/core package
May 24, 2026
1372c94
style(server): apply eslint --fix to align code with config rules
May 24, 2026
59a687f
build: add turbo for task orchestration and caching
andrewshell May 24, 2026
a6cc964
chore(server): rename test script to e2e-test
andrewshell May 24, 2026
b8ca40a
ci: add github actions workflow with node 22/24/26 matrix
andrewshell May 24, 2026
3351793
ci: remove circleci configuration
andrewshell May 24, 2026
e9d7ff6
fix(server): restore /docs and /LICENSE.md routes after monorepo split
andrewshell May 24, 2026
2d4563a
refactor(server)!: extract e2e test suite into apps/e2e
andrewshell May 24, 2026
ebcc699
build: split Dockerfile into per-app images and colocate LICENSE
andrewshell May 24, 2026
b91de2a
ci: point integration job at apps/e2e docker-compose
andrewshell May 24, 2026
3878a64
build: make xunit pattern recursive in .dockerignore
andrewshell May 24, 2026
9eb1738
docs: refresh CLAUDE.md and prune .dockerignore for new structure
andrewshell May 24, 2026
e72a97e
docs: trim CLAUDE.md to non-discoverable context only
andrewshell May 24, 2026
a4a523f
ci: bump checkout and setup-node to v5 for Node 24 runtime
andrewshell May 24, 2026
2f3c205
chore(deps): clear moderate audit findings on dev tooling
andrewshell May 24, 2026
bf57497
feat(core): add @rsscloud/core interface contracts
andrewshell Jun 9, 2026
07d5c86
feat(core): implement REST-capable rssCloud engine
andrewshell Jun 9, 2026
ea7060b
feat(core): add xml-rpc rssCloud plugin and dispatcher
andrewshell Jun 10, 2026
ae5acbf
refactor(core): organize src into domain-grouped folders
andrewshell Jun 10, 2026
bb427cc
docs: require tdd skill and 100% package coverage in CLAUDE.md
andrewshell Jun 10, 2026
af68cdc
chore: ignore *.local.* files
andrewshell Jun 10, 2026
26b14a5
feat(core): add REST front door dispatcher
andrewshell Jun 10, 2026
c70f6ad
fix(core): match dispatcher wire messages to the rssCloud contract
andrewshell Jun 10, 2026
05c1e39
feat(core): add file-backed Store adapter
andrewshell Jun 10, 2026
f3a0038
feat(express): add Express middleware for the rssCloud front doors
andrewshell Jun 10, 2026
a49103c
chore(core): pin tsconfigRootDir in eslint config
andrewshell Jun 10, 2026
98248e3
build(server): depend on @rsscloud/core + @rsscloud/express, build th…
andrewshell Jun 10, 2026
58d6f63
refactor(server): bridge core onto the legacy store via an adapter (s…
andrewshell Jun 10, 2026
8d31bef
refactor(server): serve /ping via @rsscloud/express middleware (slice 2)
andrewshell Jun 10, 2026
fe90806
refactor(server): serve /pleaseNotify via @rsscloud/express (slice 3)
andrewshell Jun 10, 2026
2a2bda1
refactor(server): mount the core-backed protocol doors in the control…
andrewshell Jun 10, 2026
d0d238e
refactor(server): serve /RPC2 via @rsscloud/express (slice 4)
andrewshell Jun 10, 2026
779de88
refactor(server): delete orphaned RPC/REST protocol-glue services (sl…
andrewshell Jun 10, 2026
34f64df
test(server): characterize stats service behavior
andrewshell Jun 10, 2026
31f7f8d
refactor(server): back the stats service with @rsscloud/core (slice 6)
andrewshell Jun 10, 2026
6d9eebf
test(server): characterize the OPML export
andrewshell Jun 10, 2026
f5ca20c
refactor(server): build OPML from the core store (slice 7)
andrewshell Jun 10, 2026
6ffbc27
test(server): characterize remove-expired-subscriptions
andrewshell Jun 10, 2026
ff4dca4
refactor(server): back remove-expired-subscriptions with @rsscloud/core
andrewshell Jun 10, 2026
8cd7b6a
refactor(server): delete the orphaned legacy notify/parse services
andrewshell Jun 10, 2026
b559915
refactor(server): route /test/* + /subscriptions.json through the cor…
andrewshell Jun 10, 2026
3ef87e0
test(server): seed the service unit tests via the core store interface
andrewshell Jun 10, 2026
eb788b6
refactor(server): back core with createFileStore; retire json-store
andrewshell Jun 10, 2026
3fd3df6
docs: add TODO.md tracking open work and future projects
andrewshell Jun 11, 2026
4e8f152
ci: track @rsscloud/express in release-please + add node-workspace
andrewshell Jun 11, 2026
646cc23
refactor(core): collapse the duplicated subscribe-request assembly in…
andrewshell Jun 11, 2026
3528c55
feat(core): own async store construction and teardown
andrewshell Jun 11, 2026
2c829c0
refactor(server): inject core into stats, opml, and remove-expired se…
andrewshell Jun 12, 2026
9876a44
docs: drop completed injectable-core follow-up; note reduced unify su…
andrewshell Jun 12, 2026
1fb33d3
feat(core): persist the domain model as a versioned v2 file format
andrewshell Jun 12, 2026
c5baeff
refactor(server): expose the core model over /test/* and /subscriptio…
andrewshell Jun 12, 2026
58d7350
docs: mark the on-disk v2 format unification done
andrewshell Jun 12, 2026
fbbec72
docs: drop stale PLAN/json-store references in service comments
andrewshell Jun 12, 2026
865f48a
docs: track architecture cleanup (deepening opportunities) in TODO
andrewshell Jun 12, 2026
6f14cfc
refactor(core): seal the store port behind a narrow listFeeds/seed seam
andrewshell Jun 13, 2026
1a7912f
refactor(server): build controllers via a createControllers({ core })…
andrewshell Jun 13, 2026
bd2d194
refactor(core): extract maintenance jobs from the core factory
andrewshell Jun 13, 2026
6138b16
docs: mark the maintenance-job extraction done in TODO
andrewshell Jun 13, 2026
afc84f3
refactor(core): collapse three fetchWithTimeout copies into one util
andrewshell Jun 13, 2026
0a067db
docs: mark the fetchWithTimeout consolidation done in TODO
andrewshell Jun 13, 2026
a0a8456
feat(core): expose the change-window size on Stats
andrewshell Jun 13, 2026
7c855c1
fix(server): stop the stats label hardcoding "7 days"
andrewshell Jun 13, 2026
730e2fd
docs: mark the stats-label fix done; close out architecture cleanup
andrewshell Jun 13, 2026
4d93648
refactor(server): drop the remove-expired-subscriptions pass-through
andrewshell Jun 13, 2026
47ea1f5
docs: log the pass-through removal; record second-review findings
andrewshell Jun 13, 2026
eed7de7
refactor(server): rename toLegacyStats to toStatsView
andrewshell Jun 13, 2026
bebd250
docs: mark the toStatsView rename done
andrewshell Jun 13, 2026
5f62675
refactor(server): concentrate the /test/* response envelope in wrap()
andrewshell Jun 13, 2026
9df1e81
docs: close out the architecture-cleanup reviews
andrewshell Jun 13, 2026
4734972
docs: drop the redundant done section from TODO
andrewshell Jun 13, 2026
2ec4243
docs: scope the client extraction with a shared @rsscloud/xml-rpc codec
andrewshell Jun 13, 2026
02a9ce7
chore(xml-rpc): scaffold the @rsscloud/xml-rpc package
andrewshell Jun 13, 2026
db8658b
feat(xml-rpc): add the methodCall decoder
andrewshell Jun 13, 2026
7f03617
feat(xml-rpc): add the typed methodCall/response builder
andrewshell Jun 13, 2026
37a209a
refactor(core): build XML-RPC on the shared @rsscloud/xml-rpc codec
andrewshell Jun 13, 2026
b3fe585
chore(client): scaffold the @rsscloud/client package
andrewshell Jun 13, 2026
87c95fb
feat(client): add the rssCloud pleaseNotify/ping XML-RPC builders
andrewshell Jun 13, 2026
13aac3c
feat(client): add the createRssCloudClient send layer
andrewshell Jun 13, 2026
1ac6fa3
feat(client): add notification receive helpers and the cloud feed ren…
andrewshell Jun 13, 2026
48abae0
refactor(server): thin client.js onto @rsscloud/client
andrewshell Jun 13, 2026
fe32370
refactor(server): relocate the client harness to a private apps/client
andrewshell Jun 13, 2026
a8647cb
docs: mark the client extraction done in TODO
andrewshell Jun 13, 2026
fd364e0
docs: add the client-end vocabulary to CONTEXT.md
andrewshell Jun 13, 2026
34ae9b4
docs(client): add subscribe/ping usage examples to the README
andrewshell Jun 13, 2026
db8713e
fix(client): honor an optional callback domain on every transport
andrewshell Jun 13, 2026
31a8681
refactor: fold the @rsscloud/client package into apps/client
andrewshell Jun 13, 2026
aea23b8
fix(server): build @rsscloud/xml-rpc in the Docker image
andrewshell Jun 14, 2026
9d72411
docs: document all workspace packages in the README
andrewshell Jun 14, 2026
b13b558
ci: stop running every check twice on pull requests
andrewshell Jun 14, 2026
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
10 changes: 0 additions & 10 deletions .circleci/config.yml

This file was deleted.

10 changes: 6 additions & 4 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.git
.github
.circleci
node_modules
data
xunit
**/node_modules
**/data
packages/*/dist
packages/*/coverage
.turbo
**/xunit
.nyc_output
.env
.DS_Store
Expand Down
77 changes: 77 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
lint-and-unit:
name: Lint & unit (Node ${{ matrix.node }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: ['22', '24', '26']
steps:
- uses: actions/checkout@v5

- name: Enable corepack
run: corepack enable

- uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node }}
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm build

- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

- name: Unit tests
run: pnpm test:unit

- name: Upload core coverage
if: matrix.node == '22'
uses: actions/upload-artifact@v4
with:
name: core-coverage
path: packages/core/coverage/
if-no-files-found: ignore

integration:
name: Integration (Docker, Node 22)
runs-on: ubuntu-latest
needs: lint-and-unit
steps:
- uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Run integration tests
run: docker compose -f apps/e2e/docker-compose.yml up --build --abort-on-container-exit --attach rsscloud-tests --no-log-prefix

- name: Upload xunit results
if: always()
uses: actions/upload-artifact@v4
with:
name: server-xunit
path: apps/e2e/xunit/test-results.xml
if-no-files-found: warn
20 changes: 9 additions & 11 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
name: release-please

on:
push:
branches:
- main
push:
branches:
- main

permissions:
contents: write
pull-requests: write
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
release-type: node
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
.DS_Store
.env
.nyc_output/
/data/
/node_modules/
/xunit/
coverage/
data/
dist/
node_modules/
xunit/
.turbo/
Procfile
tunnel.sh
*.local.*
2 changes: 1 addition & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "esversion":8 }
{ "esversion": 8 }
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package-lock.json
# Build output
dist/
build/
coverage/

# Third-party libraries
public/js/
Expand Down
22 changes: 11 additions & 11 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": false,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid",
"printWidth": 80,
"endOfLine": "lf"
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "none",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid",
"printWidth": 80,
"endOfLine": "lf"
}
5 changes: 4 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
".": "3.0.0"
"apps/server": "4.0.0",
"packages/xml-rpc": "0.0.0",
"packages/core": "0.0.0",
"packages/express": "0.0.0"
}
98 changes: 13 additions & 85 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,100 +2,28 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview
## What this is

This is an rssCloud Server v2 implementation in Node.js - a notification protocol server that allows RSS feeds to notify subscribers when they are updated. The server handles subscription management and real-time notifications for RSS/feed updates.
An [rssCloud](http://rsscloud.org/) notification protocol server. Subscribers register a callback URL via `/pleaseNotify`; publishers `/ping` when feeds update; the server fans notifications out to subscribers. Implementation lives in `apps/server/`.

## Development Commands
## Development workflow

This project uses pnpm with corepack. Run `corepack enable` to set up pnpm automatically.
Build features and fix bugs with the **`tdd` skill** — strict red-green-refactor, one vertical slice at a time. Write a failing test, make it pass, refactor; don't batch all the tests and all the code together.

### Start Development
Packages under `packages/` (e.g. `@rsscloud/core`) are held to **100% code coverage**. Keep them there — every branch and line exercised by a test. If something genuinely can't or shouldn't be covered, justify it with an explicit ignore rather than letting coverage slip.

- `pnpm start` - Start server with nodemon (auto-reload on changes)
- `pnpm run client` - Start client with nodemon
## Data storage

### Testing & Quality
State (resources and subscriptions) is held in memory and persisted atomically to a JSON file (default `./data/subscriptions.json`, configurable via `DATA_FILE_PATH`). The flush happens on an interval, at shutdown, and on unexpected exit. There is no external database.

- `pnpm test` - Run full API tests using Docker containers (MacOS tested)
- `pnpm run lint` - Run ESLint with auto-fix on controllers/, services/, test/
## End-to-end tests

## Architecture
`apps/e2e/` is a private workspace package holding a full mocha suite. Tests talk to the server over HTTP via `APP_URL` and spin up their own mock servers on ports 8002/8003.

### Core Application Structure
A handful of server-internal helpers (RPC builders, dayjs wrapper, `init-subscription`, three config keys) are **intentionally duplicated** in `apps/e2e/test/helpers/` rather than imported across the workspace boundary. This preserves the e2e package as an independent consumer of the server's HTTP+RPC protocol, at the cost of some maintenance overhead if those helpers' wire-shape ever changes. If you find yourself adding a new `require('../...')` in a test file, prefer copying the dependency into `helpers/` instead.

- **app.js** - Main Express application entry point, sets up middleware, loads jsonStore from disk, and starts server
- **config.js** - Configuration management reading from env vars with defaults
- **controllers/** - Express route handlers for API endpoints
- **services/** - Business logic modules for core functionality
- **views/** - Handlebars templates for web interface
## Releases

### Key Services
Conventional Commits are enforced by commitlint (via husky). Pushes to `main` trigger [release-please](https://github.com/googleapis/release-please) which opens or updates a Release PR per tracked package (`apps/server`, `packages/core`, `packages/express`). The `node-workspace` plugin keeps internal `workspace:*` deps in lockstep, cascading a release to dependents when a dependency bumps (`core` → `express` → `server`). `apps/e2e` is private and not tracked. Merging the Release PR cuts the release and git tag.

- **services/json-store.js** - Disk-backed in-memory store; the sole source of truth for resources and subscriptions. Flushes atomically to `./data/subscriptions.json` on an interval and at shutdown.
- **services/notify-\*.js** - Notification system for subscribers
- **services/ping.js** - RSS feed update detection and processing
- **services/please-notify.js** - Subscription management

### API Endpoints (defined in controllers/index.js)

- `/pleaseNotify` - Subscribe to RSS feed notifications
- `/ping` - Notify server of RSS feed updates
- `/viewLog` - Event log viewer for debugging
- `/RPC2` - XML-RPC endpoint
- Web forms available at `/pleaseNotifyForm` and `/pingForm`

### Configuration

Environment variables (with defaults in config.js):

- `DOMAIN` (default: localhost)
- `PORT` (default: 5337)
- `DATA_FILE_PATH` (default: `./data/subscriptions.json`)
- Resource limits: MAX_RESOURCE_SIZE, REQUEST_TIMEOUT, etc.

### Data Storage

State is persisted to a JSON file (default `./data/subscriptions.json`) managed by services/json-store.js. The store loads into memory at startup and flushes atomically on an interval and at shutdown. No external database is required.

### Testing

- Unit tests in test/ directory using Mocha/Chai
- Docker-based API testing with mock endpoints
- Test fixtures and SSL certificates in test/keys/

## Commits and Releases

This project uses [Conventional Commits](https://www.conventionalcommits.org/) enforced by commitlint via husky git hooks.

### Commit Format

```
type: description

[optional body]
```

### Commit Types

**Trigger releases:**
- `fix:` - Bug fixes → patch release (2.2.1 → 2.2.2)
- `feat:` - New features → minor release (2.2.1 → 2.3.0)
- `feat!:` or `BREAKING CHANGE:` → major release (2.2.1 → 3.0.0)

**No release triggered:**
- `chore:` - Maintenance tasks, dependencies
- `docs:` - Documentation only
- `style:` - Code style/formatting
- `refactor:` - Code refactoring
- `test:` - Adding/updating tests
- `ci:` - CI/CD changes
- `build:` - Build system changes

### Release Workflow

1. Push commits to `main`
2. release-please automatically creates/updates a Release PR
3. Review the Release PR (contains changelog and version bump)
4. Merge the Release PR when ready to release
5. release-please creates GitHub Release and git tag
`fix:` → patch, `feat:` → minor, `feat!:` / `BREAKING CHANGE:` → major. Other types (`chore:`, `docs:`, `style:`, `refactor:`, `test:`, `ci:`, `build:`) don't trigger releases.
Loading
Loading