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
46 changes: 1 addition & 45 deletions .github/workflows/build-publish.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Docker build & publish
name: CI

on:
pull_request:
Expand Down Expand Up @@ -36,47 +36,3 @@ jobs:

- name: Test
run: pnpm test

docker-image:
runs-on: ubuntu-latest
needs: checks

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up QEMU
uses: docker/setup-qemu-action@v4

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

- name: Login to DockerHub
if: github.event_name == 'push'
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Work out tags
id: container
run: |
TAGS=$(cat release | awk '{print "phpdockerio/readability-js-server:latest,phpdockerio/readability-js-server:" $1 ",phpdockerio/readability-js-server:" $2 ",phpdockerio/readability-js-server:" $3}')
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
echo "Docker tags to build: ${TAGS}"

- name: Check if release version has been bumped
id: release_file_changed
uses: tj-actions/changed-files@v46
with:
files: |
release

- name: Build & push container image
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.ref == 'refs/heads/master' && steps.release_file_changed.outputs.any_changed == 'true' }}
pull: true
tags: "${{ steps.container.outputs.tags }}"
79 changes: 79 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Release

on:
push:
tags:
- "v*"

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write
env:
IMAGE_NAME: phpdockerio/readability-js-server

steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up QEMU
uses: docker/setup-qemu-action@v4

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

- name: Derive release metadata
id: meta
run: |
TAG_NAME="${GITHUB_REF_NAME}"
VERSION="${TAG_NAME#v}"

if [ "${VERSION}" = "${TAG_NAME}" ]; then
echo "Tag must start with v" >&2
exit 1
fi

IFS='.' read -r MAJOR MINOR PATCH <<EOF
${VERSION}
EOF

if [ -z "${MAJOR}" ] || [ -z "${MINOR}" ] || [ -z "${PATCH}" ]; then
echo "Tag must be in vMAJOR.MINOR.PATCH format" >&2
exit 1
fi

PACKAGE_VERSION="$(node -p "require('./package.json').version")"
if [ "${PACKAGE_VERSION}" != "${VERSION}" ]; then
echo "Tag version ${VERSION} does not match package.json version ${PACKAGE_VERSION}" >&2
exit 1
fi

{
echo "version=${VERSION}"
echo "major=${MAJOR}"
echo "minor=${MINOR}"
echo "tags=${IMAGE_NAME}:latest,${IMAGE_NAME}:${VERSION},${IMAGE_NAME}:${MAJOR}.${MINOR},${IMAGE_NAME}:${MAJOR}"
} >> "${GITHUB_OUTPUT}"

- name: Login to DockerHub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build & push container image
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
pull: true
push: true
tags: ${{ steps.meta.outputs.tags }}

- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release create "${GITHUB_REF_NAME}" --generate-notes --verify-tag
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
- Memory soak: `make soak` or `node scripts/memory-soak.js --requests 100 --concurrency 2 --sample-every 10`
- Docker build: `docker build -t readability-js .`
- Docker run: `docker run --rm -p 3000:3000 readability-js`
- Release tag: `make release-tag VERSION=1.8.0`

The Makefile mirrors those workflows with `make install`, `make start`, `make lint`, `make lint-fix`, `make build-container`, `make run-container`, and `make example-request`.
The Makefile mirrors those workflows with `make install`, `make start`, `make lint`, `make lint-fix`, `make build-container`, `make run-container`, `make release-tag`, and `make example-request`.

`package.json` is the single source of truth for the service version. Release publishing is tag-driven: bump `package.json`'s `version`, commit it, create a matching `vX.Y.Z` tag, and push the tag to trigger Docker publish plus GitHub Release creation.

## Testing expectations

Expand Down
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ WORKDIR /application

ARG RUNTIME_USER=readability

RUN apk add --no-cache tini

RUN adduser -D ${RUNTIME_USER} \
&& mkdir -p /home/${RUNTIME_USER} /application \
&& chown -R ${RUNTIME_USER}:${RUNTIME_USER} /home/${RUNTIME_USER} /application

COPY --from=deps /application/node_modules ./node_modules
COPY src src
COPY release .
COPY package.json .

USER ${RUNTIME_USER}

ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "src/server.js"]
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ build-container:
run-container:
docker run --rm -p3000:3000 readability-js

release-tag:
@if [ -z "$(VERSION)" ]; then \
echo "Usage: make release-tag VERSION=x.y.z"; \
exit 1; \
fi
git tag v$(VERSION)
@echo "Created tag v$(VERSION). Push it with: git push origin v$(VERSION)"

example-request:
curl -XPOST http://localhost:3000/ \
-H "Content-Type: application/json" \
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ make install
make start
```

Release versions come from [`package.json`](package.json). To publish a release, bump `version`, commit the change, create a `vX.Y.Z` tag, and push that tag. The release workflow publishes Docker images for `X.Y.Z`, `X.Y`, `X`, and `latest`, and creates the matching GitHub Release with generated notes.

## Testing

Run the lint and test suites with pnpm:
Expand All @@ -155,6 +157,12 @@ make lint
make lint-fix
```

For release tagging there is also:

```bash
make release-tag VERSION=1.8.0
```

## Docker

Build and run the container locally:
Expand All @@ -166,6 +174,8 @@ docker run --rm -p 3000:3000 readability-js

The image is based on `node:24-alpine`, installs production dependencies only, and runs the service as a non-root user.

CI on pull requests and pushes to `master` runs lint and tests only. Container publishing happens from the tag-triggered release workflow.

For Docker Compose setup, see [`examples/compose.yaml`](examples/compose.yaml).

## Security posture
Expand Down
41 changes: 41 additions & 0 deletions eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const js = require("@eslint/js");
const n = require("eslint-plugin-n").default;
const eslintConfigPrettier = require("eslint-config-prettier/flat");

const nodeRecommendedScript = n.configs["flat/recommended-script"];

module.exports = [
{
ignores: ["**/node_modules/**", "**/coverage/**", "**/dist/**"],
},
js.configs.recommended,
{
files: ["src/**/*.js", "test/**/*.js", "scripts/**/*.js"],
...nodeRecommendedScript,
settings: {
...(nodeRecommendedScript.settings || {}),
node: {
version: ">=24.0.0",
},
},
rules: {
...nodeRecommendedScript.rules,
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
},
},
{
files: ["test/**/*.js"],
rules: {
"n/no-unpublished-import": "off",
"n/no-unpublished-require": "off",
},
},
eslintConfigPrettier,
];
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "readability-js-server",
"version": "1.8.0",
"description": "Mozilla's Readability.js as a service",
"author": "Luis Pabon @ phpdocker.io",
"homepage": "https://github.com/phpdocker-io/readability-js-server",
Expand All @@ -15,12 +16,16 @@
},
"scripts": {
"start": "nodemon src/server.js",
"test": "node --test",
"lint": "prettier -c src/ test/ scripts/",
"lint:fix": "prettier -w src/ test/ scripts/",
"test": "node --test test/*.test.js",
"lint": "eslint src/ test/ scripts/ && prettier -c src/ test/ scripts/",
"lint:fix": "eslint --fix src/ test/ scripts/ && prettier --write src/ test/ scripts/",
"memory:soak": "node scripts/memory-soak.js"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"eslint": "^10.5.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-n": "^18.1.0",
"nodemon": "^3.1.14",
"prettier": "^3.8.4",
"supertest": "^7.1.4"
Expand Down
Loading