Skip to content

Commit

Permalink
Replace moment dependency with pure native impl and Migrate to Jest a…
Browse files Browse the repository at this point in the history
…nd Github Actions
  • Loading branch information
Souler authored and 3imed-jaberi committed Oct 12, 2024
1 parent 5be6fcc commit 642d935
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 39 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Node.js CI

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage --maxWorkers 2
- name: Upload results to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
12 changes: 0 additions & 12 deletions .nycrc

This file was deleted.

10 changes: 0 additions & 10 deletions .travis.yml

This file was deleted.

106 changes: 106 additions & 0 deletions format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const util = require('util');

/**
* Pads the provided number into a string of 2 digits; adding leading
* zeros if needed.
* Values resulting in more than 2 digits are left untouched.
*
* @param {string} number
* @returns A two-digit number
* @throws {TypeError} if the provided value is not a valid integer
*/
function toTwoDigits(number) {
if (!Number.isInteger(number)) {
throw new TypeError(`Not a valid integer: ${number}`);
}

return number.toString().padStart(2, '0');
}

/**
* Look-up map of month number into month short name (in english).
* A month number is the value returned by Date#getMonth() and a short month name
* is a 3 letter representation of a month of the year.
*/
const shortMonthByMonthNumber = {
0: 'Jan',
1: 'Feb',
2: 'Mar',
3: 'Apr',
4: 'May',
5: 'Jun',
6: 'Jul',
7: 'Aug',
8: 'Sep',
9: 'Oct',
10: 'Nov',
11: 'Dec'
};

/**
* Returns a [short](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#month)
* (3-letters) representation of the given month index **in English**.
*
* @param {number} month
* @returns {string}
* @throws {TypeError} if the provided value is not a valid month number
*/
function toShortMonth(month) {
if (!(month in shortMonthByMonthNumber)) {
throw new TypeError(`Not a valid month value: ${month}`);
}

return shortMonthByMonthNumber[month];
}

/**
* Returns a [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant
* offset string.
*
* @returns {string}
* @throws {TypeError} if the provided value is not a valid integer
*/
function toOffset(offsetMinutes) {
if (!Number.isInteger(offsetMinutes)) {
throw new TypeError(`Not a valid integer: ${offsetMinutes}`);
}

const absoluteOffset = Math.abs(offsetMinutes);
const hours = toTwoDigits(Math.floor(absoluteOffset / 60));
const minutes = toTwoDigits(absoluteOffset % 60);
const sign = offsetMinutes >= 0 ? '-' : '+';

return `${sign}${hours}${minutes}`;
}

/**
* Formats the provided date into a [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format)
* compliant string.
*
* @param {Date} date
* @returns {string}
*/
function toCommonAccessLogDateFormat(date) {
if (!(date instanceof Date)) {
throw new TypeError('Not a valid date');
}

// e.g: 10/Oct/2000:13:55:36 -0700
return util.format(
'%s/%s/%s:%s:%s:%s %s',
toTwoDigits(date.getDate()),
toShortMonth(date.getMonth()),
date.getFullYear(),
toTwoDigits(date.getHours()),
toTwoDigits(date.getMinutes()),
toTwoDigits(date.getSeconds()),
toOffset(date.getTimezoneOffset())
);
}

module.exports = {
toCommonAccessLogDateFormat,
toOffset,
toShortMonth,
toTwoDigits
};
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const util = require('util');
const moment = require('moment');
const { toCommonAccessLogDateFormat } = require('./format');

module.exports = function (stream) {
if (!stream) stream = process.stdout;
Expand All @@ -13,7 +13,7 @@ module.exports = function (stream) {

// eslint-disable-next-line unicorn/explicit-length-check
const length = ctx.length ? ctx.length.toString() : '-';
const date = moment().format('D/MMM/YYYY:HH:mm:ss ZZ');
const date = toCommonAccessLogDateFormat(new Date());

stream.write(
util.format(
Expand Down
17 changes: 4 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@
"main": "index.js",
"scripts": {
"lint": "xo",
"pretest": "npm run lint",
"test": "mocha",
"precoverage": "rimraf .nyc_output coverage",
"coverage": "nyc npm run test",
"preci": "echo start CI ........",
"ci": "npm run coverage"
"test": "jest"
},
"repository": {
"type": "git",
Expand All @@ -30,14 +25,10 @@
"3imed-jaberi <[email protected]> (https://www.3imed-jaberi.com)"
],
"license": "MIT",
"dependencies": {
"moment": "^2.26.0"
},
"devDependencies": {
"eslint-config-xo-lass": "^1.0.3",
"jest": "^29.7.0",
"koa": "^2.12.1",
"mocha": "^8.0.1",
"nyc": "^15.1.0",
"supertest": "^4.0.2",
"xo": "^0.32.0"
},
Expand All @@ -63,8 +54,8 @@
}
},
"engines": {
"node": ">= 10"
},
"node": ">= 18"
},
"homepage": "https://github.com/koajs/accesslog",
"bugs": {
"url": "https://github.com/koajs/accesslog/issues"
Expand Down
95 changes: 95 additions & 0 deletions test/format.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const {
toCommonAccessLogDateFormat,
toOffset,
toShortMonth,
toTwoDigits,
} = require('../format')

describe('toTwoDigits', () => {
test.each([
{ value: 0, expected: '00' },
{ value: 1, expected: '01' },
{ value: 11, expected: '11' },
{ value: 99, expected: '99' },
{ value: 123, expected: '123' },
])('returns $expected for $value', ({ value, expected }) => {
expect(toTwoDigits(value)).toBe(expected)
})

test('throws TypeError if provided value is Infinity', () => {
expect(() => toTwoDigits(Infinity)).toThrow(TypeError)
})

test('throws TypeError if provided value is NaN', () => {
expect(() => toTwoDigits(NaN)).toThrow(TypeError)
})

test('throws TypeError if provided value is a decimal number', () => {
expect(() => toTwoDigits(0.99)).toThrow(TypeError)
})

test('throws TypeError if provided value is a string', () => {
expect(() => toTwoDigits('0')).toThrow(TypeError)
})
})

describe('toOffset', () => {
test.each([
{ value: 480, expected: '-0800' },
{ value: 0, expected: '+0000' },
{ value: -180, expected: '+0300' },
])('returns $expected for $value', ({ value, expected }) => {
expect(toOffset(value)).toBe(expected)
})

test('throws TypeError if provided value is Infinity', () => {
expect(() => toOffset(Infinity)).toThrow(TypeError)
})

test('throws TypeError if provided value is NaN', () => {
expect(() => toOffset(NaN)).toThrow(TypeError)
})

test('throws TypeError if provided value is a decimal number', () => {
expect(() => toOffset(60.5)).toThrow(TypeError)
})

test('throws TypeError if provided value is a string', () => {
expect(() => toOffset('60')).toThrow(TypeError)
})
})

describe('toShortMonth', () => {
test.each([
{ value: 0, expected: 'Jan' },
{ value: 1, expected: 'Feb' },
{ value: 2, expected: 'Mar' },
{ value: 3, expected: 'Apr' },
{ value: 4, expected: 'May' },
{ value: 5, expected: 'Jun' },
{ value: 6, expected: 'Jul' },
{ value: 7, expected: 'Aug' },
{ value: 8, expected: 'Sep' },
{ value: 9, expected: 'Oct' },
{ value: 10, expected: 'Nov' },
{ value: 11, expected: 'Dec' },
])('returns $expected for $value', ({ value, expected }) => {
expect(toShortMonth(value)).toBe(expected)
})

test('throws TypeError for a non-valid month number', () => {
expect(() => toShortMonth(13)).toThrow(TypeError)
})
})

describe('toCommonAccessLogDateFormat', () => {
test('correctly formats a date', () => {
const date = new Date('2020-01-01T12:34:56Z')
const expectedValue = '01/Jan/2020:10:34:56 +0200'
expect(toCommonAccessLogDateFormat(date)).toBe(expectedValue)
})

test('throws TypeError for non-Date value', () => {
expect(() => toCommonAccessLogDateFormat({})).toThrow(TypeError)
})
})
4 changes: 2 additions & 2 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ describe('lib accesslog', function () {
let agent;
let server;

before(function (done) {
beforeAll(function (done) {
server = app.listen(done);
agent = request.agent(server);
});

after(function () {
afterAll(function () {
server.close();
});

Expand Down

0 comments on commit 642d935

Please sign in to comment.