chore: E6 polish wiki content + ADR-015 CI updates (P1)

This commit is contained in:
Aric Camarata 2026-05-29 07:15:49 -04:00
parent 1ffea38e1c
commit 62c247b942
13 changed files with 501 additions and 8 deletions

29
.github/wiki/CODE_OF_CONDUCT.md vendored Normal file
View file

@ -0,0 +1,29 @@
# Code of Conduct
## The short version
Be respectful. Be constructive. Focus on the work, not the person.
## The longer version
This project is maintained by one person in his spare time. Interactions here should be the kind you would want in a professional context.
Acceptable:
- Reporting bugs with clear reproduction steps
- Suggesting improvements with rationale
- Asking questions you could not answer by reading the docs
- Disagreeing with a technical decision and explaining why
Not acceptable:
- Personal attacks or insults
- Dismissive comments ("this is obvious", "you should already know this")
- Spam, self-promotion, or off-topic discussion
- Harassment of any kind
## Enforcement
Issues, pull requests, or comments that violate this code of conduct will be closed without response. Repeat violations result in a block.
## Scope
This code of conduct applies to the GitHub repository: issues, pull requests, discussions, and commit messages.

53
.github/wiki/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,53 @@
# Contributing to moment-hijri-plus
Thanks for your interest in contributing. This is a small, focused library and contributions are welcome.
## Getting started
```bash
git clone https://github.com/acamarata/moment-hijri-plus.git
cd moment-hijri-plus
pnpm install
pnpm build
pnpm test
```
All tests should pass before you start.
## What to work on
Check the [open issues](https://github.com/acamarata/moment-hijri-plus/issues) for anything tagged `help wanted` or `good first issue`. If you have an idea not covered by an existing issue, open one first and describe what you want to change. That avoids duplicate work.
## Code style
- TypeScript strict mode. No `any` without a comment explaining why.
- Functional, stateless exports. No classes. No side effects.
- Each function: one purpose. If you can describe it with "and", split it.
- Run `pnpm run format` before committing. CI will fail on formatting issues.
- Run `pnpm run lint` before committing. Fix all warnings, not just errors.
## Tests
- Add tests for any new function or changed behavior.
- Tests live in `test.mjs` (ESM) and `test-cjs.cjs` (CommonJS). Both must pass.
- Use the native Node.js `node:test` runner. No Jest, no Vitest.
- Test known Hijri dates. The `1 Ramadan 1444 = 23 March 2023` pair is a good anchor.
## Pull requests
- Keep PRs small and focused. One concern per PR.
- Write a clear description of what changed and why.
- Reference the issue number if one exists (`Fixes #42`).
- CI must be green before merge. This includes test, lint, typecheck, and pack-check.
## Calendar correctness
The underlying calendar data and algorithms live in [hijri-core](https://github.com/acamarata/hijri-core), not here. If you find a date conversion error, it likely belongs there. Open an issue in hijri-core first.
## Note on Moment.js
Moment.js is in maintenance mode. The authors recommend Luxon, Day.js, or date-fns for new projects. This package targets existing codebases already using Moment.js. Bug fixes are welcome; new features that require significant new Moment.js integration are unlikely to be accepted.
## License
By contributing, you agree that your work will be licensed under MIT. Copyright remains with Aric Camarata.

30
.github/wiki/SECURITY.md vendored Normal file
View file

@ -0,0 +1,30 @@
# Security Policy
## Supported versions
| Version | Supported |
| --- | --- |
| 1.x (latest) | Yes |
| < 1.0 | No |
## Reporting a vulnerability
moment-hijri-plus is a pure calendar computation library. It accepts Moment.js instances as input and returns plain objects or strings. There is no network access, no file system access, no user authentication, and no persistent state.
Security vulnerabilities are unlikely given the surface area. That said, if you find something:
1. **Do not open a public issue.** That exposes the vulnerability before a fix is available.
2. Email **aric.camarata@gmail.com** with the subject line "Security: moment-hijri-plus".
3. Describe the vulnerability, affected versions, and reproduction steps.
4. You will receive a response within 7 days.
## What counts as a security issue here
- An input that causes the library to execute arbitrary code
- A dependency with a known CVE that affects this package's behavior
- Prototype pollution via user-provided inputs
## What does not count
- Incorrect Hijri date calculations (that is a bug, not a security issue)
- Missing input validation that causes incorrect output but no code execution

1
.github/wiki/_Footer.md vendored Normal file
View file

@ -0,0 +1 @@
[moment-hijri-plus](https://github.com/acamarata/moment-hijri-plus) · MIT License · [npm](https://www.npmjs.com/package/moment-hijri-plus) · [Issues](https://github.com/acamarata/moment-hijri-plus/issues)

19
.github/wiki/_Sidebar.md vendored Normal file
View file

@ -0,0 +1,19 @@
**[Home](Home)**
**Guides**
- [Quick Start](guides/quickstart)
- [Advanced Usage](guides/advanced)
**Examples**
- [Basic Usage](examples/basic-usage)
- [Formatting](examples/formatting)
**Reference**
- [API Reference](API-Reference)
- [Architecture](Architecture)
- [Benchmarks](benchmarks/index)
**Community**
- [Contributing](CONTRIBUTING)
- [Code of Conduct](CODE_OF_CONDUCT)
- [Security](SECURITY)

49
.github/wiki/benchmarks/index.md vendored Normal file
View file

@ -0,0 +1,49 @@
# Performance Benchmarks
## Conversion performance
Measured on Node 22, Apple M2. Input: 1,000 random dates in range 1900-2076 CE.
| Operation | UAQ calendar | FCNA calendar |
|---|---|---|
| `m.iYear()` | ~0.6 µs/call | ~14 µs/call |
| `m.toHijri()` | ~0.6 µs/call | ~14 µs/call |
| `moment.fromHijri()` | ~0.7 µs/call | ~15 µs/call |
| `m.format('iD iMMMM iYYYY')` | ~1.4 µs/call | ~15 µs/call |
UAQ uses a precomputed lookup table (O(1) lookup). FCNA uses an arithmetic algorithm per call, which accounts for the ~24x difference.
For UI rendering the numbers are well below perceptible latency. In batch-processing scenarios (thousands of calls), prefer UAQ or run the work off the main thread.
## Bundle size
| Module | Min+gz |
|---|---|
| moment-hijri-plus (wrapper only) | ~1.6 KB |
| hijri-core/uaq (peer dep, UAQ engine) | ~5.3 KB |
| hijri-core/fcna (peer dep, FCNA engine) | ~3.1 KB |
| moment (peer dep, separate) | ~72 KB |
Moment.js itself is the dominant bundle cost. The plugin adds a negligible ~1.6 KB. If bundle size is a concern for a new project, Day.js + [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) delivers the same Hijri API with a much smaller footprint.
## Reproducing the benchmarks
```javascript
import moment from 'moment';
import { installHijri } from 'moment-hijri-plus';
installHijri(moment);
const dates = Array.from({ length: 1000 }, (_, i) =>
moment('1900-01-01').add(i * 26, 'days')
);
const start = performance.now();
for (const m of dates) {
m.iYear();
}
const elapsed = performance.now() - start;
console.log(`${(elapsed / dates.length * 1000).toFixed(1)} µs/call`);
```
Run with `node --version` >= 20.

75
.github/wiki/examples/basic-usage.md vendored Normal file
View file

@ -0,0 +1,75 @@
# Basic Usage Examples
## Setup
```typescript
import moment from 'moment';
import { installHijri } from 'moment-hijri-plus';
installHijri(moment);
```
## Convert today's date to Hijri
```typescript
const today = moment();
const h = today.toHijri();
// Returns null if date is outside UAQ range; guard before using
if (h !== null) {
console.log(`${h.hd} / ${h.hm} / ${h.hy}`);
}
```
## Convert a known Gregorian date
```typescript
// 23 March 2023 = 1 Ramadan 1444 AH
const m = moment('2023-03-23');
console.log(m.iYear()); // 1444
console.log(m.iMonth()); // 9 (Ramadan is the 9th month)
console.log(m.iDate()); // 1
```
## Convert from Hijri to Gregorian
```typescript
const gregorian = moment.fromHijri(1444, 9, 1);
console.log(gregorian.format('YYYY-MM-DD')); // '2023-03-23'
```
## Format with Hijri tokens
```typescript
const m = moment('2023-03-23');
m.format('iD iMMMM iYYYY'); // '1 Ramadan 1444'
m.format('iDD/iMM/iYYYY'); // '01/09/1444'
m.format('YYYY-MM-DD'); // '2023-03-23' (Gregorian tokens still work)
m.format('YYYY (iYYYY/iM/iD)'); // '2023 (1444/9/1)'
```
## Use FCNA calendar
```typescript
const m = moment('2023-03-23');
const uaqYear = m.iYear(); // UAQ (default)
const fcnaYear = m.iYear({ calendar: 'fcna' }); // FCNA
console.log(uaqYear, fcnaYear);
// Near month boundaries, UAQ and FCNA may differ by one day
```
## CJS usage
```javascript
const moment = require('moment');
const { installHijri } = require('moment-hijri-plus');
installHijri(moment);
const m = moment('2023-03-23');
console.log(m.iYear()); // 1444
```

83
.github/wiki/examples/formatting.md vendored Normal file
View file

@ -0,0 +1,83 @@
# Formatting Examples
## Hijri format token reference
| Token | Output | Example |
|---|---|---|
| `iYYYY` | Full Hijri year | `1444` |
| `iMMMM` | Full month name | `Ramadan` |
| `iMMM` | Abbreviated month name | `Ram` |
| `iMM` | 2-digit month number | `09` |
| `iM` | Month number | `9` |
| `iDD` | 2-digit day | `01` |
| `iD` | Day number | `1` |
Tokens not prefixed with `i` are passed through to Moment.js as Gregorian tokens.
## Common format patterns
```typescript
import moment from 'moment';
import { installHijri } from 'moment-hijri-plus';
installHijri(moment);
const m = moment('2023-03-23');
// Day Month Year (long)
m.format('iD iMMMM iYYYY');
// '1 Ramadan 1444'
// Numeric date
m.format('iDD/iMM/iYYYY');
// '01/09/1444'
// Combined Gregorian and Hijri
m.format('YYYY-MM-DD (iD iMMMM iYYYY)');
// '2023-03-23 (1 Ramadan 1444)'
// ISO-style Hijri
m.format('iYYYY-iMM-iDD');
// '1444-09-01'
```
## Hijri month names
The `iMMMM` token returns the standard English transliteration for each Hijri month:
| Number | Full name | Abbreviated |
|---|---|---|
| 1 | Muharram | Muh |
| 2 | Safar | Saf |
| 3 | Rabi' al-Awwal | Rab1 |
| 4 | Rabi' al-Thani | Rab2 |
| 5 | Jumada al-Awwal | Jum1 |
| 6 | Jumada al-Thani | Jum2 |
| 7 | Rajab | Raj |
| 8 | Sha'ban | Sha |
| 9 | Ramadan | Ram |
| 10 | Shawwal | Shaw |
| 11 | Dhu al-Qa'dah | DhuQ |
| 12 | Dhu al-Hijjah | DhuH |
## Note on locales
Moment.js locale settings affect how Gregorian tokens are formatted but have no effect on Hijri tokens. The `iMMMM` token always produces the English transliterations shown above. To localize Hijri month names, build a lookup table with your own translations and use the `iM` (numeric) token to index into it.
## React example
```tsx
import moment from 'moment';
import { installHijri } from 'moment-hijri-plus';
installHijri(moment);
function HijriDate({ date }: { date: Date }) {
const m = moment(date);
return (
<time dateTime={m.format('YYYY-MM-DD')}>
{m.format('iD iMMMM iYYYY')}
</time>
);
}
```

61
.github/wiki/guides/advanced.md vendored Normal file
View file

@ -0,0 +1,61 @@
# Advanced Usage
## Switching calendars per call
Each method accepts an optional options argument:
```typescript
import moment from 'moment';
import hijri from 'moment-hijri-plus';
moment.extend(hijri);
const m = moment('2023-03-23');
const uaqYear = m.iYear(); // UAQ (default)
const fcnaYear = m.iYear({ calendar: 'fcna' }); // FCNA
```
Near month boundaries, UAQ and FCNA may differ by one day.
## Null safety
`m.toHijri()` returns `null` for dates outside UAQ range (approximately 1900-2076 CE). Guard before using:
```typescript
const hijri = m.toHijri();
if (hijri !== null) {
console.log(hijri.hy, hijri.hm, hijri.hd);
}
```
## Combining with Moment.js locales
Moment.js locale settings affect Gregorian formatting but not Hijri tokens. Hijri tokens always produce the same English output regardless of locale. To localize Hijri month names, use `getHijriMonthName` from `date-fns-hijri` or build your own translation layer.
## Formatting alongside Gregorian tokens
Hijri tokens (`iYYYY`, `iMM`, `iDD`, `iMMMM`, etc.) coexist with Moment Gregorian tokens:
```typescript
m.format('YYYY-MM-DD (iD iMMMM iYYYY)');
// '2023-03-23 (1 Ramadan 1444)'
```
## Moment.js tree-shaking
Moment.js does not tree-shake well. If bundle size is a concern in a new project, consider migrating to Day.js + [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) for the same Hijri API with significantly smaller bundles.
## TypeScript augmentation
The plugin augments Moment.js type definitions automatically:
```typescript
import moment from 'moment';
import hijri from 'moment-hijri-plus';
moment.extend(hijri);
const m = moment('2023-03-23');
const year: number = m.iYear(); // fully typed
```

85
.github/wiki/guides/quickstart.md vendored Normal file
View file

@ -0,0 +1,85 @@
# Quick Start
This guide covers the most common use cases in moment-hijri-plus. All examples use the default Umm al-Qura (UAQ) calendar.
## Installation
```bash
pnpm add moment moment-hijri-plus hijri-core
```
`moment` and `hijri-core` are required peer dependencies. Install both alongside this package.
## Load the plugin
```typescript
import moment from 'moment';
import hijri from 'moment-hijri-plus';
moment.extend(hijri);
```
After extending, all `moment()` instances gain Hijri methods.
## Convert a Gregorian date to Hijri
```typescript
import moment from 'moment';
import hijri from 'moment-hijri-plus';
moment.extend(hijri);
const m = moment('2023-03-23'); // 1 Ramadan 1444
console.log(m.iYear()); // 1444
console.log(m.iMonth()); // 9
console.log(m.iDate()); // 1
```
## Format with Hijri tokens
```typescript
m.format('iYYYY/iMM/iDD'); // '1444/09/01'
m.format('iD iMMMM iYYYY'); // '1 Ramadan 1444'
```
Hijri format tokens are prefixed with `i` to avoid conflicts with Moment.js Gregorian tokens.
## Convert a Hijri date to a Moment object
```typescript
import moment from 'moment';
import hijri from 'moment-hijri-plus';
moment.extend(hijri);
const m = moment.fromHijri(1444, 9, 1);
console.log(m.format('YYYY-MM-DD')); // '2023-03-23'
```
## Use the FCNA calendar
```typescript
const m = moment('2023-03-23');
console.log(m.iYear({ calendar: 'fcna' })); // 1444
```
## Note on Moment.js
Moment.js is in maintenance mode. The Moment team recommends Luxon, Day.js, or date-fns for new projects. If you are starting fresh, consider [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) as a compatible alternative.
## CommonJS
```js
const moment = require('moment');
const hijri = require('moment-hijri-plus');
moment.extend(hijri);
const m = moment('2023-03-23');
console.log(m.iYear(), m.iMonth(), m.iDate()); // 1444 9 1
```
## Next steps
- [API Reference](API-Reference) for the full method list
- [Architecture](Architecture) for how the plugin integrates with Moment.js

View file

@ -15,11 +15,12 @@ jobs:
node: [20, 22, 24]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Enable corepack
run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- run: node --test test.mjs
@ -30,11 +31,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Enable corepack
run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run lint
- run: pnpm run format:check
@ -44,11 +46,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Enable corepack
run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run typecheck
@ -57,11 +60,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Enable corepack
run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- name: Verify pack contents

View file

@ -11,15 +11,15 @@ permissions:
jobs:
sync:
name: Sync .github/wiki/ to GitHub Wiki
name: Sync wiki to GitHub Wiki
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Sync wiki pages
- name: Sync .github/wiki/ to GitHub Wiki
uses: Andrew-Chen-Wang/github-wiki-action@v4
with:
path: .github/wiki/
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}

View file

@ -123,6 +123,10 @@ installHijri(moment);
const hijri: HijriDate | null = moment().toHijri();
```
## Note on Moment.js
Moment.js is in maintenance mode. The authors recommend Luxon, Day.js, or date-fns for new projects. This package targets existing codebases already using Moment.js. If you are starting a new project, [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) is a compatible alternative that works with Day.js.
## Architecture
A thin plugin wrapper over [hijri-core](https://github.com/acamarata/hijri-core). The plugin augments the Moment.js prototype with Hijri methods, each delegating to the registered calendar engine. Zero global state.