mirror of
https://github.com/acamarata/moon-cycle.git
synced 2026-06-30 18:54:29 +00:00
feat: v2.0.0 — TypeScript rewrite, npm-publishable, off-by-one fix
Port to TypeScript with dual CJS/ESM build via tsup. Fix off-by-one bug in both cycleMonth and cycleYear (images are 1-indexed; v1 returned 000.webp/0000.webp which don't exist). Add imageFolder and cdnUrl helpers for jsDelivr CDN integration. Export constants and types. Separate the ~438 MB image dataset from the npm package via the files field, making moon-cycle publishable to npm for the first time. Add CI workflow with Node 20/22/24 matrix, typecheck, and pack-check jobs. Add wiki-sync workflow and full .wiki/ documentation (Home, API Reference, Architecture, Migration Guide).
This commit is contained in:
parent
aa80f7c53d
commit
24db90e9f5
31 changed files with 2374 additions and 129 deletions
14
.editorconfig
Normal file
14
.editorconfig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,mjs,cjs,ts,mts,cts,json,yaml,yml,md}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
102
.github/workflows/ci.yml
vendored
Normal file
102
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test (Node ${{ matrix.node-version }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20, 22, 24]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Test (ESM)
|
||||
run: node test.mjs
|
||||
|
||||
- name: Test (CJS)
|
||||
run: node test-cjs.cjs
|
||||
|
||||
typecheck:
|
||||
name: Type Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Type check
|
||||
run: pnpm typecheck
|
||||
|
||||
pack-check:
|
||||
name: Pack Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Check pack contents
|
||||
run: |
|
||||
npm pack --dry-run 2>&1 | tee pack-output.txt
|
||||
# Verify expected files are present
|
||||
grep -q "dist/index.cjs" pack-output.txt
|
||||
grep -q "dist/index.mjs" pack-output.txt
|
||||
grep -q "dist/index.d.ts" pack-output.txt
|
||||
grep -q "dist/index.d.mts" pack-output.txt
|
||||
grep -q "README.md" pack-output.txt
|
||||
grep -q "LICENSE" pack-output.txt
|
||||
grep -q "CHANGELOG.md" pack-output.txt
|
||||
# Verify image folders are NOT in the pack
|
||||
! grep -q "mm-256" pack-output.txt
|
||||
! grep -q "my-256" pack-output.txt
|
||||
echo "Pack check passed"
|
||||
23
.github/workflows/wiki-sync.yml
vendored
Normal file
23
.github/workflows/wiki-sync.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
name: Wiki Sync
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- .wiki/**
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync .wiki/ to GitHub Wiki
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Sync wiki pages
|
||||
uses: Andrew-Chen-Wang/github-wiki-action@v4
|
||||
with:
|
||||
path: .wiki/
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
55
.gitignore
vendored
55
.gitignore
vendored
|
|
@ -1,5 +1,58 @@
|
|||
# ─── Dependencies ───
|
||||
node_modules/
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# ─── Build ───
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
*.tsbuildinfo
|
||||
|
||||
# ─── Environment ───
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# ─── OS ───
|
||||
.DS_Store
|
||||
.vscode
|
||||
Thumbs.db
|
||||
._*
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# ─── IDE / Editor ───
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# ─── Logs ───
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# ─── Testing / Coverage ───
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# ─── Pack artifacts ───
|
||||
*.tgz
|
||||
|
||||
# ─── AI Agents ───
|
||||
.claude/
|
||||
.cursor/
|
||||
.copilot/
|
||||
.github/copilot/
|
||||
.aider*
|
||||
.codeium/
|
||||
.tabnine/
|
||||
.windsurf/
|
||||
.cody/
|
||||
.sourcegraph/
|
||||
|
|
|
|||
0
.npmrc
Normal file
0
.npmrc
Normal file
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
24
|
||||
207
.wiki/API-Reference.md
Normal file
207
.wiki/API-Reference.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
# API Reference
|
||||
|
||||
Complete reference for moon-cycle v2.
|
||||
|
||||
## Functions
|
||||
|
||||
### `cycleMonth(date?)`
|
||||
|
||||
```ts
|
||||
function cycleMonth(date?: Date): string
|
||||
```
|
||||
|
||||
Maps a date to an image filename in the **monthly (synodic) dataset**.
|
||||
|
||||
The 708 images cover one complete synodic month at hourly resolution. The function computes how far into the current synodic cycle the given date falls and maps that fraction to an image index in `[1, 708]`.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `date` | `Date` | `new Date()` | The date to resolve |
|
||||
|
||||
**Returns:** A zero-padded filename string, e.g. `"354.webp"`. Always in the range `"001.webp"` to `"708.webp"`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { cycleMonth } from 'moon-cycle';
|
||||
|
||||
cycleMonth(); // current lunar phase
|
||||
cycleMonth(new Date('2024-01-15')); // specific date
|
||||
cycleMonth(new Date('2020-06-21')); // any past date works
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `cycleYear(date?)`
|
||||
|
||||
```ts
|
||||
function cycleYear(date?: Date): string
|
||||
```
|
||||
|
||||
Maps a date to an image filename in the **yearly dataset**.
|
||||
|
||||
The 8,760 images cover the full calendar year 2023 at hourly resolution. The function extracts the hour-of-year position from the given date, cycling through the same 8,760 images every year.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `date` | `Date` | `new Date()` | The date to resolve |
|
||||
|
||||
**Returns:** A zero-padded filename string, e.g. `"4380.webp"`. Always in the range `"0001.webp"` to `"8760.webp"`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { cycleYear } from 'moon-cycle';
|
||||
|
||||
cycleYear(); // current hour-of-year position
|
||||
cycleYear(new Date('2025-07-04T12:00Z')); // July 4, noon
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `imageFolder(set, size, quality)`
|
||||
|
||||
```ts
|
||||
function imageFolder(set: ImageSet, size: ImageSize, quality: ImageQuality): string
|
||||
```
|
||||
|
||||
Returns the directory name for a given combination of image set, size, and quality.
|
||||
|
||||
Directory names follow the pattern `{set}-{size}-{quality}`, matching the layout in the repository.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `set` | `'mm' \| 'my'` | Monthly or yearly dataset |
|
||||
| `size` | `256 \| 512` | Image dimension in pixels |
|
||||
| `quality` | `75 \| 85` | WebP compression quality |
|
||||
|
||||
**Returns:** A string like `"mm-256-75"`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { imageFolder } from 'moon-cycle';
|
||||
|
||||
imageFolder('mm', 256, 75) // => 'mm-256-75'
|
||||
imageFolder('my', 512, 85) // => 'my-512-85'
|
||||
|
||||
// Use with a local public directory:
|
||||
const filename = cycleMonth();
|
||||
const folder = imageFolder('mm', 512, 85);
|
||||
const localUrl = `/public/${folder}/${filename}`;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `cdnUrl(filename, set, size, quality, ref?)`
|
||||
|
||||
```ts
|
||||
function cdnUrl(
|
||||
filename: string,
|
||||
set: ImageSet,
|
||||
size: ImageSize,
|
||||
quality: ImageQuality,
|
||||
ref?: string
|
||||
): string
|
||||
```
|
||||
|
||||
Returns a jsDelivr CDN URL serving the specified image directly from the GitHub repository. jsDelivr caches GitHub content via a global CDN with no account or configuration required.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `filename` | `string` | — | Filename from `cycleMonth()` or `cycleYear()` |
|
||||
| `set` | `'mm' \| 'my'` | — | Monthly or yearly dataset |
|
||||
| `size` | `256 \| 512` | — | Image dimension in pixels |
|
||||
| `quality` | `75 \| 85` | — | WebP compression quality |
|
||||
| `ref` | `string` | `'main'` | Branch name, git tag, or commit SHA |
|
||||
|
||||
**Returns:** A full URL string, e.g. `"https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@main/mm-256-75/354.webp"`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { cycleMonth, cdnUrl } from 'moon-cycle';
|
||||
|
||||
// Current phase, served via CDN
|
||||
const file = cycleMonth();
|
||||
const url = cdnUrl(file, 'mm', 256, 75);
|
||||
|
||||
// Pin to a specific release for cache stability
|
||||
const stableUrl = cdnUrl(file, 'mm', 256, 75, 'v2.0.0');
|
||||
|
||||
// Use in a React component
|
||||
function MoonImage() {
|
||||
const file = cycleMonth();
|
||||
return <img src={cdnUrl(file, 'mm', 256, 85)} alt="Current moon phase" />;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
| Export | Type | Value | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `SYNODIC_MONTH` | `number` | `29.53058821398858` | IAU mean synodic month in days |
|
||||
| `MONTH_IMAGES` | `number` | `708` | Image count in the monthly dataset |
|
||||
| `YEAR_IMAGES` | `number` | `8760` | Image count in the yearly dataset |
|
||||
| `MONTH_ANCHOR` | `Date` | `2023-11-13T09:27:00Z` | New moon reference for `cycleMonth` |
|
||||
| `YEAR_ANCHOR` | `Date` | `2023-01-01T00:00:00Z` | Year start reference for `cycleYear` |
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { SYNODIC_MONTH, MONTH_ANCHOR } from 'moon-cycle';
|
||||
|
||||
// Compute the next new moon after a given date
|
||||
function nextNewMoon(after: Date): Date {
|
||||
const elapsed = (after.getTime() - MONTH_ANCHOR.getTime()) / (1000 * 60 * 60 * 24);
|
||||
const cycles = Math.ceil(elapsed / SYNODIC_MONTH);
|
||||
return new Date(MONTH_ANCHOR.getTime() + cycles * SYNODIC_MONTH * 86400000);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
```ts
|
||||
/** 'mm' = monthly dataset (synodic cycle, 708 images) */
|
||||
/** 'my' = yearly dataset (calendar year, 8,760 images) */
|
||||
type ImageSet = 'mm' | 'my';
|
||||
|
||||
/** Image dimension in pixels (square). */
|
||||
type ImageSize = 256 | 512;
|
||||
|
||||
/** WebP compression quality level. */
|
||||
type ImageQuality = 75 | 85;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Image Dataset Reference
|
||||
|
||||
| Folder | Set | Images | Resolution | Quality | Approx. Size |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `mm-256-75` | monthly | 708 | 256x256 | 75 | ~4 MB |
|
||||
| `mm-256-85` | monthly | 708 | 256x256 | 85 | ~5 MB |
|
||||
| `mm-512-75` | monthly | 708 | 512x512 | 75 | ~9 MB |
|
||||
| `mm-512-85` | monthly | 708 | 512x512 | 85 | ~14 MB |
|
||||
| `my-256-75` | yearly | 8,760 | 256x256 | 75 | ~51 MB |
|
||||
| `my-256-85` | yearly | 8,760 | 256x256 | 85 | ~66 MB |
|
||||
| `my-512-75` | yearly | 8,760 | 512x512 | 75 | ~113 MB |
|
||||
| `my-512-85` | yearly | 8,760 | 512x512 | 85 | ~176 MB |
|
||||
|
||||
All images are square WebP, transparent background, named with zero-padded indices (`001.webp` to `708.webp` for monthly, `0001.webp` to `8760.webp` for yearly).
|
||||
|
||||
---
|
||||
|
||||
*[Home](Home) | [Architecture](Architecture) | [Migration Guide](Migration)*
|
||||
134
.wiki/Architecture.md
Normal file
134
.wiki/Architecture.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# Architecture
|
||||
|
||||
This page explains how moon-cycle works internally, why two separate algorithms exist, and the tradeoffs involved in each approach.
|
||||
|
||||
## Overview
|
||||
|
||||
The library solves a single problem: given a JavaScript `Date`, which of NASA's moon phase photographs corresponds to that moment in time? There are two conceptually distinct ways to answer that question, and the library provides both.
|
||||
|
||||
## The Image Dataset
|
||||
|
||||
All images originate from NASA's Scientific Visualization Studio visualization "Moon Phase and Libration, 2023" (SVS #5187), produced by Ernie Wright. The visualization renders the moon's appearance once per hour throughout the calendar year 2023, capturing both phase and libration (the apparent wobble caused by the Moon's slightly elliptical orbit and axial tilt).
|
||||
|
||||
The raw frames were converted to WebP and organized into eight folders combining two image sets, two resolutions, and two quality levels:
|
||||
|
||||
- `mm-*` — 708 images covering one synodic month (monthly set)
|
||||
- `my-*` — 8,760 images covering the full year 2023 (yearly set)
|
||||
|
||||
Images are named with zero-padded integers starting at 1 (`001.webp` to `708.webp` for monthly, `0001.webp` to `8760.webp` for yearly).
|
||||
|
||||
## Algorithm 1: Synodic Cycle (`cycleMonth`)
|
||||
|
||||
### Concept
|
||||
|
||||
The synodic month is the time between two identical lunar phases as seen from Earth — new moon to new moon. Its IAU mean value is **29.53058821398858 days**. The 708 images in the monthly set span exactly one such cycle at hourly resolution.
|
||||
|
||||
To find the correct image for any date, the algorithm:
|
||||
|
||||
1. Computes elapsed seconds since the **reference new moon** (`2023-11-13T09:27:00 UTC`, a known new moon moment)
|
||||
2. Divides by the length of one synodic cycle in seconds to get total synodic months elapsed
|
||||
3. Takes the fractional part of that value (always in `[0, 1)`)
|
||||
4. Maps the fraction to an image index in `[1, 708]`
|
||||
|
||||
```
|
||||
elapsed_seconds = (date - ANCHOR) / 1000
|
||||
months = elapsed_seconds / SYNODIC_SECONDS
|
||||
fraction = months - floor(months)
|
||||
index = floor(fraction * 708) + 1
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
- **Accurate lunar phase:** The result corresponds to the real lunar phase on the given date. Near index 354, the moon is near full; near index 1 or 708, it is near new.
|
||||
- **Works for any date:** Past dates (even centuries ago) and future dates all resolve correctly, because the synodic cycle is stable and the modular arithmetic handles negative elapsed times.
|
||||
- **Sub-hour precision:** The index changes every ~2.5 hours (one synodic month / 708 images). Within that window, the same image repeats.
|
||||
|
||||
### Limitation
|
||||
|
||||
The synodic month is not exactly 29.53058821398858 days — that value is the IAU *mean* (averaged over centuries). The actual length of a given synodic month varies by up to ~7 hours depending on the Moon's position in its elliptical orbit. For dates far from the 2023 reference period, accumulated drift means the image may be off by a few frames from the true observed phase. For UI purposes, this is imperceptible.
|
||||
|
||||
## Algorithm 2: Calendar Year (`cycleYear`)
|
||||
|
||||
### Concept
|
||||
|
||||
The yearly set contains 8,760 images — one per hour of the 365-day year 2023. Rather than tracking lunar phase directly, `cycleYear` maps any date to its hour-of-year equivalent and uses that to index into the 2023 imagery.
|
||||
|
||||
The algorithm:
|
||||
|
||||
1. Computes hours elapsed since `2023-01-01T00:00:00 UTC`
|
||||
2. Takes the fractional year (elapsed hours divided by 8760)
|
||||
3. Takes the fractional part (for yearly wrapping)
|
||||
4. Maps to an index in `[1, 8760]`
|
||||
|
||||
```
|
||||
elapsed_hours = (date - ANCHOR) / (1000 * 3600)
|
||||
years = elapsed_hours / 8760
|
||||
fraction = years - floor(years)
|
||||
index = floor(fraction * 8760) + 1
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
- **Seasonal consistency:** January always maps to images from early 2023, July to mid-2023. The visual result is the same season-of-year appearance regardless of which year the input date is in.
|
||||
- **Smooth progression:** The index advances every hour, giving the smoothest possible visual change when animating over time.
|
||||
- **Non-lunar:** The result does not reflect the actual lunar phase. It reflects what the moon looked like at this time of year in 2023.
|
||||
|
||||
### Limitation
|
||||
|
||||
The year 2023 had 365 days (not a leap year), so the 8,760 images correspond exactly to one non-leap year. For years with 366 days, there is a subtle 24-image offset in the second half of the year — the hour-of-year for December 31 in a leap year wraps around slightly differently than in 2023. This is a cosmetic artifact, not a functional error.
|
||||
|
||||
## Choosing Between the Two
|
||||
|
||||
| Scenario | Recommendation |
|
||||
| --- | --- |
|
||||
| Show the actual current moon phase | `cycleMonth` |
|
||||
| Animate through a year of moon phases for a calendar app | `cycleYear` |
|
||||
| Show a consistent seasonal moon appearance | `cycleYear` |
|
||||
| Compute when the next full moon occurs | `cycleMonth` + `SYNODIC_MONTH` |
|
||||
| Display a decorative moon that changes daily | Either — `cycleYear` has smoother hourly progression |
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
moon-cycle/
|
||||
├── src/
|
||||
│ ├── index.ts # Public API — re-exports all named exports
|
||||
│ ├── types.ts # Types, constants, anchor dates
|
||||
│ ├── cycleMonth.ts # Synodic algorithm
|
||||
│ ├── cycleYear.ts # Calendar-year algorithm
|
||||
│ └── helpers.ts # imageFolder, cdnUrl
|
||||
├── dist/ # Built output (not in git, shipped to npm)
|
||||
│ ├── index.cjs # CommonJS build
|
||||
│ ├── index.mjs # ESM build
|
||||
│ ├── index.d.ts # CJS type definitions
|
||||
│ └── index.d.mts # ESM type definitions
|
||||
├── mm-256-75/ … my-512-85/ # Image folders (in git, not in npm)
|
||||
├── test.mjs # ESM test suite
|
||||
├── test-cjs.cjs # CJS compatibility test
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## Build Pipeline
|
||||
|
||||
The source is TypeScript, compiled by [tsup](https://tsup.egoist.dev/) (esbuild-based). tsup produces four output files from `src/index.ts`:
|
||||
|
||||
- `dist/index.cjs` — CommonJS for `require()`
|
||||
- `dist/index.mjs` — ESM for `import`
|
||||
- `dist/index.d.ts` — type definitions for CJS consumers
|
||||
- `dist/index.d.mts` — type definitions for ESM consumers
|
||||
|
||||
The `exports` field in `package.json` uses types-first conditional exports so TypeScript resolves the correct declaration file regardless of `moduleResolution` setting.
|
||||
|
||||
## Off-by-One Fix (v2)
|
||||
|
||||
In v1, both algorithms produced 0-indexed filenames (`000.webp` to `707.webp`, `0000.webp` to `8759.webp`). The actual images in the dataset are 1-indexed (`001.webp` to `708.webp`, `0001.webp` to `8760.webp`). v2 corrects this by adding 1 to the computed index before formatting:
|
||||
|
||||
```ts
|
||||
const index = Math.floor(fraction * MONTH_IMAGES) + 1;
|
||||
```
|
||||
|
||||
The probability of hitting the exact boundary where `000.webp` would have been requested was extremely low (it required a date within one minute of the anchor moment), but it was a latent correctness bug.
|
||||
|
||||
---
|
||||
|
||||
*[Home](Home) | [API Reference](API-Reference) | [Migration Guide](Migration)*
|
||||
60
.wiki/Home.md
Normal file
60
.wiki/Home.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# moon-cycle
|
||||
|
||||
Maps any JavaScript `Date` to the correct NASA moon phase image filename. Built on two independent algorithms, the package ships ~2 KB of code to npm while the ~438 MB image dataset stays in the GitHub repository, served via CDN on demand.
|
||||
|
||||
## What it does
|
||||
|
||||
Given any date, you get a filename like `354.webp` or `4380.webp` that corresponds to an hourly NASA moon photograph. Pair it with `cdnUrl()` to construct a ready-to-use image URL, or host the image folders yourself.
|
||||
|
||||
Two algorithms are provided because they answer different questions:
|
||||
|
||||
| Function | Algorithm | Use when |
|
||||
| --- | --- | --- |
|
||||
| `cycleMonth` | Synodic (lunar) cycle | You want the actual lunar phase for the date |
|
||||
| `cycleYear` | Calendar year (2023) | You want a consistent annual visual rhythm |
|
||||
|
||||
See [Architecture](Architecture) for a full explanation of each approach.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install moon-cycle
|
||||
pnpm add moon-cycle
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { cycleMonth, cdnUrl } from 'moon-cycle';
|
||||
|
||||
const file = cycleMonth(); // e.g. "354.webp" — current lunar phase
|
||||
const url = cdnUrl(file, 'mm', 256, 75);
|
||||
// => 'https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@main/mm-256-75/354.webp'
|
||||
```
|
||||
|
||||
## Image options
|
||||
|
||||
Pick the folder that fits your use case:
|
||||
|
||||
| Folder | Images | Size |
|
||||
| --- | --- | --- |
|
||||
| `mm-256-75` | 708 (monthly) | ~4 MB |
|
||||
| `mm-512-85` | 708 (monthly) | ~14 MB |
|
||||
| `my-256-75` | 8,760 (yearly) | ~51 MB |
|
||||
| `my-512-85` | 8,760 (yearly) | ~176 MB |
|
||||
|
||||
All eight combinations (`mm`/`my` × `256`/`512` × `75`/`85`) are available. Use `imageFolder()` to construct the directory name programmatically.
|
||||
|
||||
## Pages
|
||||
|
||||
- [API Reference](API-Reference) — full function and type documentation
|
||||
- [Architecture](Architecture) — algorithm design, dataset description, tradeoffs
|
||||
- [Migration Guide](Migration) — upgrading from v1
|
||||
|
||||
## Source
|
||||
|
||||
[github.com/acamarata/moon-cycle](https://github.com/acamarata/moon-cycle)
|
||||
|
||||
---
|
||||
|
||||
*Part of the [acamarata](https://github.com/acamarata) astronomical computing stack.*
|
||||
78
.wiki/Migration.md
Normal file
78
.wiki/Migration.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Migration Guide
|
||||
|
||||
Upgrading from moon-cycle v1 to v2.
|
||||
|
||||
## Summary of Breaking Changes
|
||||
|
||||
1. **Off-by-one fix:** `cycleMonth` and `cycleYear` now return 1-indexed filenames
|
||||
2. **No default export:** Functions are now named exports only (this was always the case — `require('moon-cycle')` still works, but `require('moon-cycle').default` does not exist)
|
||||
|
||||
## 1. Off-by-one correction
|
||||
|
||||
v1 produced filenames in the range `000.webp` to `707.webp` (monthly) and `0000.webp` to `8759.webp` (yearly). The actual image files in the repository start at `001.webp` and `0001.webp`. v1 was therefore referencing non-existent filenames.
|
||||
|
||||
v2 corrects this. The returned filenames now match the actual files in the dataset.
|
||||
|
||||
**What this means for you:**
|
||||
|
||||
- If you used the npm package with the standard image folders from this repository, your images were silently not found for the `000.webp` and `0000.webp` edge case, and all other images were effectively one frame off from the v2 behavior. Updating to v2 with no other changes will produce accurate results.
|
||||
|
||||
- If you created your own image folders starting at `000.webp` to match v1's output, you need to either rename your images to start at `001.webp` or keep using v1. v2 will no longer produce `000.webp`.
|
||||
|
||||
## 2. TypeScript types
|
||||
|
||||
v1 shipped a hand-written `index.d.ts` that incorrectly declared both functions as returning `{ result: string }`:
|
||||
|
||||
```ts
|
||||
// v1 — incorrect
|
||||
export function cycleMonth(date: Date): MonthResult;
|
||||
interface MonthResult { result: string }
|
||||
```
|
||||
|
||||
The actual runtime behavior in v1 was to return a plain `string`, not an object. v2 types match the implementation:
|
||||
|
||||
```ts
|
||||
// v2 — correct
|
||||
export function cycleMonth(date?: Date): string;
|
||||
```
|
||||
|
||||
If your TypeScript code destructured the (nonexistent) `.result` property, remove that:
|
||||
|
||||
```ts
|
||||
// v1 code (incorrect runtime behavior, wrong types)
|
||||
const { result } = cycleMonth(date);
|
||||
const src = `/mm-256-75/${result}`;
|
||||
|
||||
// v2 code (correct)
|
||||
const filename = cycleMonth(date);
|
||||
const src = `/mm-256-75/${filename}`;
|
||||
```
|
||||
|
||||
## 3. New exports
|
||||
|
||||
v2 adds several exports that did not exist in v1. These are additive and do not break existing code:
|
||||
|
||||
- `imageFolder(set, size, quality)` — constructs folder name strings
|
||||
- `cdnUrl(filename, set, size, quality, ref?)` — constructs jsDelivr CDN URLs
|
||||
- `SYNODIC_MONTH`, `MONTH_IMAGES`, `YEAR_IMAGES`, `MONTH_ANCHOR`, `YEAR_ANCHOR` — constants
|
||||
- `ImageSet`, `ImageSize`, `ImageQuality` — TypeScript types
|
||||
|
||||
## 4. Optional `date` parameter
|
||||
|
||||
In v1, `cycleMonth` and `cycleYear` accepted an optional `date` parameter with `date = new Date()` as default, but this was not reflected in the type definitions. v2 types both parameters as `date?: Date`, which matches the runtime behavior.
|
||||
|
||||
## 5. Package format
|
||||
|
||||
v2 ships dual CJS and ESM builds. If you use a bundler, it will now automatically pick up the ESM version for better tree-shaking. `require('moon-cycle')` continues to work unchanged.
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
- [ ] Update to `moon-cycle@2.0.0`
|
||||
- [ ] Verify image filenames — if you referenced `000.webp` or `0000.webp` directly, rename to `001.webp` / `0001.webp`
|
||||
- [ ] Remove any `.result` property access — both functions return `string` directly
|
||||
- [ ] Update TypeScript types if you had manual overrides working around the incorrect v1 declarations
|
||||
- [ ] Consider using `cdnUrl()` if you were constructing CDN URLs manually
|
||||
|
||||
---
|
||||
|
||||
*[Home](Home) | [API Reference](API-Reference) | [Architecture](Architecture)*
|
||||
42
CHANGELOG.md
42
CHANGELOG.md
|
|
@ -1,7 +1,45 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
All notable changes to this project will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [2.0.0] - 2025-02-25
|
||||
|
||||
### Added
|
||||
|
||||
- TypeScript source (`src/`) with full type definitions — dual CJS and ESM builds via tsup
|
||||
- `imageFolder(set, size, quality)` helper to construct image directory names
|
||||
- `cdnUrl(filename, set, size, quality, ref?)` helper to generate jsDelivr CDN URLs, enabling image serving without self-hosting the ~438 MB dataset
|
||||
- Exported constants: `SYNODIC_MONTH`, `MONTH_IMAGES`, `YEAR_IMAGES`, `MONTH_ANCHOR`, `YEAR_ANCHOR`
|
||||
- Exported types: `ImageSet`, `ImageSize`, `ImageQuality`
|
||||
- Dual ESM and CJS builds with `.mjs` / `.cjs` extensions and matching `.d.ts` / `.d.mts` type definitions
|
||||
- Proper `exports` map in `package.json` (types-first conditional exports)
|
||||
- `test.mjs` and `test-cjs.cjs` — full assertion-based test suites covering bounds, anchor dates, edge cases, and all exported functions
|
||||
- GitHub Actions CI workflow: Node 20/22/24 test matrix, typecheck job, pack-check job
|
||||
- GitHub Actions wiki-sync workflow: syncs `.wiki/` to GitHub Wiki on push to `main`
|
||||
- `.wiki/` documentation: Home, API Reference, Architecture, Migration Guide
|
||||
- `.nvmrc`, `.editorconfig`, `.npmrc`, `pnpm-workspace.yaml`
|
||||
|
||||
### Changed
|
||||
|
||||
- Package is now npm-publishable: `files` field restricts the npm package to `dist/`, README, LICENSE, and CHANGELOG — images are excluded
|
||||
- `package.json` fully updated: correct author name, accurate description, `engines`, `sideEffects`, `publishConfig`, `repository.url` with `git+https://` prefix, expanded keywords
|
||||
- `repository.url` corrected to use `git+https://` prefix per npm convention
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Off-by-one bug in both algorithms.** The v1 implementation mapped dates to 0-indexed filenames (`000.webp` to `707.webp` monthly, `0000.webp` to `8759.webp` yearly). The image dataset is 1-indexed (`001.webp` to `708.webp`, `0001.webp` to `8760.webp`). Both functions now return the correct 1-indexed filename. This is a breaking change for anyone who was working around the bug or had a local image set starting at `000.webp`.
|
||||
- TypeScript definitions in `index.d.ts` were incorrect — both functions were typed as returning `{ result: string }` instead of `string`. The new generated types are accurate.
|
||||
|
||||
### Removed
|
||||
|
||||
- `index.js`, `cycleMonth.js`, `cycleYear.js` — replaced by `src/` TypeScript source and `dist/` build output
|
||||
- `index.d.ts` — replaced by generated `dist/index.d.ts` and `dist/index.d.mts`
|
||||
- `test.js` — replaced by `test.mjs` and `test-cjs.cjs`
|
||||
|
||||
## [1.0.1] - 2023-11-14
|
||||
|
||||
- Minor repository metadata updates
|
||||
|
||||
## [1.0.0] - 2023-11-14
|
||||
|
||||
- Initial release
|
||||
- Initial release: `cycleMonth` and `cycleYear` functions, 708 monthly and 8,760 yearly NASA moon phase images in WebP format
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Aric Camarata
|
||||
Copyright (c) 2023-2025 Aric Camarata
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
197
README.md
197
README.md
|
|
@ -1,48 +1,193 @@
|
|||
|
||||
# moon-cycle
|
||||
|
||||
Simple functions to map a Date() to the current lunar phase and map that to hourly photos of the moon during that phase from NASA.
|
||||
[](https://www.npmjs.com/package/moon-cycle)
|
||||
[](https://github.com/acamarata/moon-cycle/actions/workflows/ci.yml)
|
||||
[](LICENSE)
|
||||
|
||||
Yearly takes the full photos for all of 2023 (8760 images) while Monthly take only the cycle starting from November 13 (708 images).
|
||||
Maps any JavaScript `Date` to the correct NASA moon phase image filename. Two algorithms: synodic-cycle mapping (monthly, 708 images) and calendar-year mapping (yearly, 8,760 images).
|
||||
|
||||
The images have been organized, named, converted to webp, and optimized according to the following structure:
|
||||
- "mm" (moon monthly) vs "my" (moon yearly)
|
||||
- "256" (256x256 size) vs "512" (512x512 size)
|
||||
- "75" (lower quality) vs "85" (higher quality)
|
||||
The image dataset (~438 MB of hourly WebP photos from NASA's Scientific Visualization Studio) lives in this repository. The npm package ships only the code. Serve images via CDN (jsDelivr, see below) or by cloning the repo and hosting the folders yourself.
|
||||
|
||||
## Installation
|
||||
|
||||
The images in this project make it too big for npm, so recommended usage is below.
|
||||
```bash
|
||||
npm install moon-cycle
|
||||
# or
|
||||
pnpm add moon-cycle
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Quick Start
|
||||
|
||||
Copy any of the mm or my folders (or both) to your projects "public" directory. Then use as follows:
|
||||
|
||||
```js
|
||||
const { cycleMonth, cycleYear } = require('moon-cycle');
|
||||
```ts
|
||||
import { cycleMonth, cycleYear, cdnUrl } from 'moon-cycle';
|
||||
|
||||
const date = new Date();
|
||||
|
||||
// Get results
|
||||
const mm = cycleMonth(date)
|
||||
const my = cycleYear(date)
|
||||
// Get the current moon phase filename
|
||||
const monthlyFile = cycleMonth(date); // e.g. "354.webp"
|
||||
const yearlyFile = cycleYear(date); // e.g. "4380.webp"
|
||||
|
||||
// Print results
|
||||
console.log(`\nDate = ${date.toISOString()}\nMatch Moon Image URLs to Date..\n`)
|
||||
console.log(`MM = /mm/${mm}`);
|
||||
console.log(`MY = /my/${my}`);
|
||||
// Construct a CDN URL served from GitHub via jsDelivr
|
||||
const url = cdnUrl(monthlyFile, 'mm', 256, 75);
|
||||
// => 'https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@main/mm-256-75/354.webp'
|
||||
```
|
||||
|
||||
Exported functions include cycleMonth and cycleYear. Please note that the directory name is not returned by the function to keep this part flexible depending on what you want to actually name the directories or if you want low and high resolution folders like this repository offers. The function only returns the actual file to use.
|
||||
CommonJS:
|
||||
|
||||
### Parameters:
|
||||
```js
|
||||
const { cycleMonth, cycleYear, cdnUrl } = require('moon-cycle');
|
||||
```
|
||||
|
||||
- result: name of the monthly or yearly webp file
|
||||
## API
|
||||
|
||||
## Contributing
|
||||
### `cycleMonth(date?: Date): string`
|
||||
|
||||
Contributions are welcome!
|
||||
Maps a date to an image filename in the monthly (synodic) dataset.
|
||||
|
||||
Uses the IAU mean synodic month (29.530588 days) and a 2023-11-13 new moon anchor. The 708 hourly images span one complete synodic cycle. The result wraps continuously — any past or future date resolves to a valid image.
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `date` | `Date` | `new Date()` | The date to resolve |
|
||||
|
||||
Returns a zero-padded filename string in the range `"001.webp"` to `"708.webp"`.
|
||||
|
||||
### `cycleYear(date?: Date): string`
|
||||
|
||||
Maps a date to an image filename in the yearly dataset.
|
||||
|
||||
The 8,760 images are hourly photographs from the full calendar year 2023 (365 days). The result maps to the equivalent hour-of-year position and repeats annually.
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `date` | `Date` | `new Date()` | The date to resolve |
|
||||
|
||||
Returns a zero-padded filename string in the range `"0001.webp"` to `"8760.webp"`.
|
||||
|
||||
### `imageFolder(set, size, quality): string`
|
||||
|
||||
Returns the directory name for a given image set and quality level.
|
||||
|
||||
```ts
|
||||
imageFolder('mm', 256, 75) // => 'mm-256-75'
|
||||
imageFolder('my', 512, 85) // => 'my-512-85'
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `set` | `'mm' \| 'my'` | Monthly or yearly dataset |
|
||||
| `size` | `256 \| 512` | Image dimension in pixels |
|
||||
| `quality` | `75 \| 85` | WebP compression quality |
|
||||
|
||||
### `cdnUrl(filename, set, size, quality, ref?): string`
|
||||
|
||||
Returns a jsDelivr CDN URL for a specific image, served directly from this GitHub repository.
|
||||
|
||||
```ts
|
||||
const file = cycleMonth();
|
||||
const url = cdnUrl(file, 'mm', 256, 75);
|
||||
// => 'https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@main/mm-256-75/354.webp'
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `filename` | `string` | — | Result from `cycleMonth` or `cycleYear` |
|
||||
| `set` | `'mm' \| 'my'` | — | Monthly or yearly dataset |
|
||||
| `size` | `256 \| 512` | — | Image dimension |
|
||||
| `quality` | `75 \| 85` | — | WebP quality |
|
||||
| `ref` | `string` | `'main'` | Branch, tag, or commit SHA |
|
||||
|
||||
### Constants
|
||||
|
||||
| Export | Value | Description |
|
||||
| --- | --- | --- |
|
||||
| `SYNODIC_MONTH` | `29.53058821398858` | IAU mean synodic month in days |
|
||||
| `MONTH_IMAGES` | `708` | Image count in the monthly dataset |
|
||||
| `YEAR_IMAGES` | `8760` | Image count in the yearly dataset |
|
||||
| `MONTH_ANCHOR` | `Date('2023-11-13T09:27:00Z')` | Reference new moon for `cycleMonth` |
|
||||
| `YEAR_ANCHOR` | `Date('2023-01-01T00:00:00Z')` | Reference start date for `cycleYear` |
|
||||
|
||||
### Types
|
||||
|
||||
```ts
|
||||
type ImageSet = 'mm' | 'my';
|
||||
type ImageSize = 256 | 512;
|
||||
type ImageQuality = 75 | 85;
|
||||
```
|
||||
|
||||
## Image Dataset
|
||||
|
||||
The repository contains eight image folders, each a complete set of hourly NASA moon photos in WebP format:
|
||||
|
||||
| Folder | Images | Resolution | Quality | Size |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `mm-256-75` | 708 | 256x256 | 75 | ~4 MB |
|
||||
| `mm-256-85` | 708 | 256x256 | 85 | ~5 MB |
|
||||
| `mm-512-75` | 708 | 512x512 | 75 | ~9 MB |
|
||||
| `mm-512-85` | 708 | 512x512 | 85 | ~14 MB |
|
||||
| `my-256-75` | 8,760 | 256x256 | 75 | ~51 MB |
|
||||
| `my-256-85` | 8,760 | 256x256 | 85 | ~66 MB |
|
||||
| `my-512-75` | 8,760 | 512x512 | 75 | ~113 MB |
|
||||
| `my-512-85` | 8,760 | 512x512 | 85 | ~176 MB |
|
||||
|
||||
Images are not included in the npm package. Options for serving them:
|
||||
|
||||
1. **CDN (recommended for web apps):** Use `cdnUrl()` to serve from jsDelivr, which caches GitHub content globally at no cost.
|
||||
2. **Self-hosted:** Clone the repo and copy the relevant folder(s) into your `public/` directory.
|
||||
3. **Pinned version:** Pass a specific git tag as `ref` to `cdnUrl()` to lock to a stable image set.
|
||||
|
||||
## TypeScript
|
||||
|
||||
The package ships dual CJS and ESM builds with full type definitions. No `@types/` package is needed.
|
||||
|
||||
```ts
|
||||
import { cycleMonth, cycleYear, cdnUrl } from 'moon-cycle';
|
||||
import type { ImageSet, ImageSize, ImageQuality } from 'moon-cycle';
|
||||
|
||||
function getMoonUrl(date: Date, set: ImageSet, size: ImageSize, quality: ImageQuality): string {
|
||||
const filename = set === 'mm' ? cycleMonth(date) : cycleYear(date);
|
||||
return cdnUrl(filename, set, size, quality);
|
||||
}
|
||||
```
|
||||
|
||||
## Compatibility
|
||||
|
||||
| Runtime | Support |
|
||||
| --- | --- |
|
||||
| Node.js | >= 20 |
|
||||
| Browser | Yes (ESM) |
|
||||
| Bundlers | Vite, webpack, esbuild, Rollup |
|
||||
| React / Next.js | Yes |
|
||||
| Deno | Yes |
|
||||
|
||||
## Architecture
|
||||
|
||||
Two distinct algorithms, one shared image source. See the [Architecture wiki page](https://github.com/acamarata/moon-cycle/wiki/Architecture) for analysis of the synodic and calendar-year mapping approaches, how they differ, and when to prefer each.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full reference: [GitHub Wiki](https://github.com/acamarata/moon-cycle/wiki)
|
||||
|
||||
- [Home](https://github.com/acamarata/moon-cycle/wiki/Home)
|
||||
- [API Reference](https://github.com/acamarata/moon-cycle/wiki/API-Reference)
|
||||
- [Architecture](https://github.com/acamarata/moon-cycle/wiki/Architecture)
|
||||
- [Migration Guide (v1 to v2)](https://github.com/acamarata/moon-cycle/wiki/Migration)
|
||||
|
||||
## Related
|
||||
|
||||
- [nrel-spa](https://github.com/acamarata/nrel-spa) — Pure JS NREL Solar Position Algorithm, zero dependencies
|
||||
- [pray-calc](https://github.com/acamarata/pray-calc) — Islamic prayer times with dynamic angle algorithm
|
||||
- [luxon-hijri](https://github.com/acamarata/luxon-hijri) — Hijri/Gregorian calendar conversion
|
||||
- [moon-calc](https://github.com/acamarata/moon-calc) — Lunar crescent visibility using Yallop and Odeh criteria
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Moon phase imagery from NASA's Scientific Visualization Studio:
|
||||
|
||||
> Ernie Wright (2023). *Moon Phase and Libration, 2023* [Data set]. NASA Scientific Visualization Studio. [https://svs.gsfc.nasa.gov/5187](https://svs.gsfc.nasa.gov/5187)
|
||||
|
||||
Images are in the public domain per NASA's [media usage guidelines](https://www.nasa.gov/nasa-brand-center/images-and-media/).
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
MIT. See [LICENSE](LICENSE) for the full text.
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
function cycleMonth(date = new Date()) {
|
||||
/**
|
||||
* There are 708 images from 001.webp to 708.webp in
|
||||
* the moon monthly (mm) folder. These are hourly
|
||||
* photos from NASA's 2023 collection which directly
|
||||
* relate to one per hour. The below function takes
|
||||
* seconds since the last known new moon the figures
|
||||
* number of Synodic months since then to find the
|
||||
* current "hourly phase" for the current cycle of
|
||||
* the moon. This relates R as always 1-708.
|
||||
*/
|
||||
|
||||
const startDate = new Date("2023-11-13T09:27:00Z");
|
||||
const synDays = 29.53058821398858;
|
||||
const synSecs = synDays * 24 * 60 * 60;
|
||||
|
||||
// X = number of seconds since 2023-11-13T09:27:00Z
|
||||
const X = Math.floor((date.getTime() - startDate.getTime()) / 1000);
|
||||
|
||||
// Y = get the number of months since
|
||||
const Y = Math.floor(X / synSecs);
|
||||
|
||||
// R = decimal time (minus years) * number of images
|
||||
const R = Math.floor(((X / synSecs) - Y) * 708);
|
||||
|
||||
let result = R.toString().padStart(3, '0')+'.webp';
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = { cycleMonth };
|
||||
30
cycleYear.js
30
cycleYear.js
|
|
@ -1,30 +0,0 @@
|
|||
function cycleYear(date = new Date()) {
|
||||
/**
|
||||
* There are 8760 images from 0001.webp to 8760.webp
|
||||
* in the moon yearly (my) folder. These are hourly
|
||||
* photos from NASA's 2023 collection which directly
|
||||
* relate to one per hour. The below function takes
|
||||
* hours (seconds / 3600) and maps directly to these
|
||||
* 8760 images in the folders. The last calculation
|
||||
* below rounds the cycle so R is always 1-8760.
|
||||
*/
|
||||
|
||||
const startDate = new Date("2023-01-01T00:00:00Z");
|
||||
|
||||
// X = number of seconds since 2023-01-01T00:00:00Z
|
||||
const X = Math.floor((date.getTime() - startDate.getTime()) / 1000);
|
||||
|
||||
// Y = convert to number of hours since
|
||||
const Y = Math.floor(X / 3600);
|
||||
|
||||
// Z = get the number of years since
|
||||
const Z = Math.floor(Y / 8760);
|
||||
|
||||
// R = decimal time (minus years) * number of images
|
||||
const R = Math.floor(((Y / 8760) - Z) * 8760);
|
||||
|
||||
let result = R.toString().padStart(4, '0')+'.webp';
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = { cycleYear };
|
||||
12
index.d.ts
vendored
12
index.d.ts
vendored
|
|
@ -1,12 +0,0 @@
|
|||
// index.d.ts
|
||||
declare module 'moon-cycle' {
|
||||
export function cycleMonth(date: Date): MonthResult;
|
||||
export function cycleYear(date: Date): YearResult;
|
||||
|
||||
interface MonthResult {
|
||||
result: string;
|
||||
}
|
||||
interface YearResult {
|
||||
result: string;
|
||||
}
|
||||
}
|
||||
7
index.js
7
index.js
|
|
@ -1,7 +0,0 @@
|
|||
const { cycleMonth } = require('./cycleMonth');
|
||||
const { cycleYear } = require('./cycleYear');
|
||||
|
||||
module.exports = {
|
||||
cycleMonth,
|
||||
cycleYear
|
||||
};
|
||||
BIN
moon.webp
BIN
moon.webp
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
60
package.json
60
package.json
|
|
@ -1,25 +1,69 @@
|
|||
{
|
||||
"name": "moon-cycle",
|
||||
"version": "1.0.1",
|
||||
"description": "NREL SPA native implementation in JS",
|
||||
"main": "index.js",
|
||||
"version": "2.0.0",
|
||||
"description": "Maps any date to NASA moon phase imagery via synodic and calendar-year cycles. Lightweight npm package — images served via CDN or self-hosted from the GitHub repository.",
|
||||
"author": "Aric Camarata",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"test": "node test.js"
|
||||
"build": "tsup",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"pretest": "tsup",
|
||||
"test": "node test.mjs && node test-cjs.cjs",
|
||||
"prepublishOnly": "tsup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"keywords": [
|
||||
"moon",
|
||||
"lunar",
|
||||
"phase",
|
||||
"cycle",
|
||||
"nasa",
|
||||
"synodic",
|
||||
"images",
|
||||
"nasa"
|
||||
"webp",
|
||||
"astronomy",
|
||||
"calendar",
|
||||
"islamic"
|
||||
],
|
||||
"author": "Ali Camarata",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/acamarata/moon-cycle.git"
|
||||
"url": "git+https://github.com/acamarata/moon-cycle.git"
|
||||
},
|
||||
"homepage": "https://github.com/acamarata/moon-cycle#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/acamarata/moon-cycle/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
925
pnpm-lock.yaml
Normal file
925
pnpm-lock.yaml
Normal file
|
|
@ -0,0 +1,925 @@
|
|||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.19.11
|
||||
tsup:
|
||||
specifier: ^8.0.0
|
||||
version: 8.5.1(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.3':
|
||||
resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.3':
|
||||
resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.3':
|
||||
resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.3':
|
||||
resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.3':
|
||||
resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.3':
|
||||
resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.3':
|
||||
resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.3':
|
||||
resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.3':
|
||||
resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.3':
|
||||
resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.3':
|
||||
resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.3':
|
||||
resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.3':
|
||||
resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.3':
|
||||
resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.3':
|
||||
resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.3':
|
||||
resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.3':
|
||||
resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.3':
|
||||
resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.59.0':
|
||||
resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||
resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/node@22.19.11':
|
||||
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
|
||||
|
||||
acorn@8.16.0:
|
||||
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
bundle-require@5.1.0:
|
||||
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
peerDependencies:
|
||||
esbuild: '>=0.18'
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
commander@4.1.1:
|
||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
consola@3.4.2:
|
||||
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
|
||||
engines: {node: ^14.18.0 || >=16.10.0}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
esbuild@0.27.3:
|
||||
resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
joycon@3.1.1:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lilconfig@3.1.3:
|
||||
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
load-tsconfig@0.2.5:
|
||||
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
mlly@1.8.0:
|
||||
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pirates@4.0.7:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||
|
||||
postcss-load-config@6.0.1:
|
||||
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
jiti: '>=1.21.0'
|
||||
postcss: '>=8.0.9'
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
jiti:
|
||||
optional: true
|
||||
postcss:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
resolve-from@5.0.0:
|
||||
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
rollup@4.59.0:
|
||||
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
source-map@0.7.6:
|
||||
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
sucrase@3.35.1:
|
||||
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
|
||||
thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
tinyexec@0.3.2:
|
||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
tsup@8.5.1:
|
||||
resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@microsoft/api-extractor': ^7.36.0
|
||||
'@swc/core': ^1
|
||||
postcss: ^8.4.12
|
||||
typescript: '>=4.5.0'
|
||||
peerDependenciesMeta:
|
||||
'@microsoft/api-extractor':
|
||||
optional: true
|
||||
'@swc/core':
|
||||
optional: true
|
||||
postcss:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/node@22.19.11':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
bundle-require@5.1.0(esbuild@0.27.3):
|
||||
dependencies:
|
||||
esbuild: 0.27.3
|
||||
load-tsconfig: 0.2.5
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
commander@4.1.1: {}
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
consola@3.4.2: {}
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
esbuild@0.27.3:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.3
|
||||
'@esbuild/android-arm': 0.27.3
|
||||
'@esbuild/android-arm64': 0.27.3
|
||||
'@esbuild/android-x64': 0.27.3
|
||||
'@esbuild/darwin-arm64': 0.27.3
|
||||
'@esbuild/darwin-x64': 0.27.3
|
||||
'@esbuild/freebsd-arm64': 0.27.3
|
||||
'@esbuild/freebsd-x64': 0.27.3
|
||||
'@esbuild/linux-arm': 0.27.3
|
||||
'@esbuild/linux-arm64': 0.27.3
|
||||
'@esbuild/linux-ia32': 0.27.3
|
||||
'@esbuild/linux-loong64': 0.27.3
|
||||
'@esbuild/linux-mips64el': 0.27.3
|
||||
'@esbuild/linux-ppc64': 0.27.3
|
||||
'@esbuild/linux-riscv64': 0.27.3
|
||||
'@esbuild/linux-s390x': 0.27.3
|
||||
'@esbuild/linux-x64': 0.27.3
|
||||
'@esbuild/netbsd-arm64': 0.27.3
|
||||
'@esbuild/netbsd-x64': 0.27.3
|
||||
'@esbuild/openbsd-arm64': 0.27.3
|
||||
'@esbuild/openbsd-x64': 0.27.3
|
||||
'@esbuild/openharmony-arm64': 0.27.3
|
||||
'@esbuild/sunos-x64': 0.27.3
|
||||
'@esbuild/win32-arm64': 0.27.3
|
||||
'@esbuild/win32-ia32': 0.27.3
|
||||
'@esbuild/win32-x64': 0.27.3
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
dependencies:
|
||||
magic-string: 0.30.21
|
||||
mlly: 1.8.0
|
||||
rollup: 4.59.0
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
lilconfig@3.1.3: {}
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
load-tsconfig@0.2.5: {}
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
mlly@1.8.0:
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
pathe: 2.0.3
|
||||
pkg-types: 1.3.1
|
||||
ufo: 1.6.3
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
mz@2.7.0:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
object-assign: 4.1.1
|
||||
thenify-all: 1.6.0
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
dependencies:
|
||||
confbox: 0.1.8
|
||||
mlly: 1.8.0
|
||||
pathe: 2.0.3
|
||||
|
||||
postcss-load-config@6.0.1:
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
|
||||
rollup@4.59.0:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.59.0
|
||||
'@rollup/rollup-android-arm64': 4.59.0
|
||||
'@rollup/rollup-darwin-arm64': 4.59.0
|
||||
'@rollup/rollup-darwin-x64': 4.59.0
|
||||
'@rollup/rollup-freebsd-arm64': 4.59.0
|
||||
'@rollup/rollup-freebsd-x64': 4.59.0
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.59.0
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.59.0
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-arm64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-loong64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-loong64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-ppc64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-ppc64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-x64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-x64-musl': 4.59.0
|
||||
'@rollup/rollup-openbsd-x64': 4.59.0
|
||||
'@rollup/rollup-openharmony-arm64': 4.59.0
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.59.0
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.59.0
|
||||
'@rollup/rollup-win32-x64-gnu': 4.59.0
|
||||
'@rollup/rollup-win32-x64-msvc': 4.59.0
|
||||
fsevents: 2.3.3
|
||||
|
||||
source-map@0.7.6: {}
|
||||
|
||||
sucrase@3.35.1:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
commander: 4.1.1
|
||||
lines-and-columns: 1.2.4
|
||||
mz: 2.7.0
|
||||
pirates: 4.0.7
|
||||
tinyglobby: 0.2.15
|
||||
ts-interface-checker: 0.1.13
|
||||
|
||||
thenify-all@1.6.0:
|
||||
dependencies:
|
||||
thenify: 3.3.1
|
||||
|
||||
thenify@3.3.1:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
tsup@8.5.1(typescript@5.9.3):
|
||||
dependencies:
|
||||
bundle-require: 5.1.0(esbuild@0.27.3)
|
||||
cac: 6.7.14
|
||||
chokidar: 4.0.3
|
||||
consola: 3.4.2
|
||||
debug: 4.4.3
|
||||
esbuild: 0.27.3
|
||||
fix-dts-default-cjs-exports: 1.0.1
|
||||
joycon: 3.1.1
|
||||
picocolors: 1.1.1
|
||||
postcss-load-config: 6.0.1
|
||||
resolve-from: 5.0.0
|
||||
rollup: 4.59.0
|
||||
source-map: 0.7.6
|
||||
sucrase: 3.35.1
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.15
|
||||
tree-kill: 1.2.2
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- supports-color
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
30
src/cycleMonth.ts
Normal file
30
src/cycleMonth.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { SYNODIC_MONTH, MONTH_IMAGES, MONTH_ANCHOR } from './types.js';
|
||||
|
||||
const SYNODIC_SECONDS = SYNODIC_MONTH * 24 * 60 * 60;
|
||||
|
||||
/**
|
||||
* Maps a date to the corresponding NASA moon phase image for the monthly cycle.
|
||||
*
|
||||
* The monthly dataset contains 708 hourly images spanning exactly one synodic
|
||||
* month (29.53 days). This function computes the fractional position within
|
||||
* the current synodic month relative to the 2023-11-13 new moon anchor and
|
||||
* maps that fraction to an image index in [1, 708].
|
||||
*
|
||||
* @param date - The date to resolve. Defaults to the current time.
|
||||
* @returns A zero-padded filename string, e.g. `"354.webp"`.
|
||||
*/
|
||||
export function cycleMonth(date: Date = new Date()): string {
|
||||
// Seconds elapsed since the known new moon anchor
|
||||
const elapsed = (date.getTime() - MONTH_ANCHOR.getTime()) / 1000;
|
||||
|
||||
// Total synodic months elapsed (may be negative for past dates)
|
||||
const months = elapsed / SYNODIC_SECONDS;
|
||||
|
||||
// Fractional part of the current month: always in [0, 1)
|
||||
const fraction = months - Math.floor(months);
|
||||
|
||||
// Map to 1-indexed image number: 1 to MONTH_IMAGES
|
||||
const index = Math.floor(fraction * MONTH_IMAGES) + 1;
|
||||
|
||||
return index.toString().padStart(3, '0') + '.webp';
|
||||
}
|
||||
31
src/cycleYear.ts
Normal file
31
src/cycleYear.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { YEAR_IMAGES, YEAR_ANCHOR } from './types.js';
|
||||
|
||||
/**
|
||||
* Maps a date to the corresponding NASA moon image for the yearly cycle.
|
||||
*
|
||||
* The yearly dataset contains 8,760 hourly images covering the full calendar
|
||||
* year 2023 (365 days × 24 hours). This function computes the fractional
|
||||
* position within a 365-day year relative to 2023-01-01 and maps that
|
||||
* fraction to an image index in [1, 8760].
|
||||
*
|
||||
* The cycle repeats annually, so dates in any year resolve to the equivalent
|
||||
* hour-of-year position in the 2023 imagery.
|
||||
*
|
||||
* @param date - The date to resolve. Defaults to the current time.
|
||||
* @returns A zero-padded filename string, e.g. `"4380.webp"`.
|
||||
*/
|
||||
export function cycleYear(date: Date = new Date()): string {
|
||||
// Hours elapsed since 2023-01-01T00:00:00Z
|
||||
const elapsed_hours = (date.getTime() - YEAR_ANCHOR.getTime()) / (1000 * 3600);
|
||||
|
||||
// Total years elapsed (may be negative for past dates)
|
||||
const years = elapsed_hours / YEAR_IMAGES;
|
||||
|
||||
// Fractional part of the current year: always in [0, 1)
|
||||
const fraction = years - Math.floor(years);
|
||||
|
||||
// Map to 1-indexed image number: 1 to YEAR_IMAGES
|
||||
const index = Math.floor(fraction * YEAR_IMAGES) + 1;
|
||||
|
||||
return index.toString().padStart(4, '0') + '.webp';
|
||||
}
|
||||
45
src/helpers.ts
Normal file
45
src/helpers.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { ImageSet, ImageSize, ImageQuality } from './types.js';
|
||||
|
||||
/**
|
||||
* Returns the folder name for a given image set, size, and quality.
|
||||
*
|
||||
* Folder names follow the pattern `{set}-{size}-{quality}`, matching the
|
||||
* directory layout in the moon-cycle repository.
|
||||
*
|
||||
* @example
|
||||
* imageFolder('mm', 256, 75) // => 'mm-256-75'
|
||||
* imageFolder('my', 512, 85) // => 'my-512-85'
|
||||
*/
|
||||
export function imageFolder(set: ImageSet, size: ImageSize, quality: ImageQuality): string {
|
||||
return `${set}-${size}-${quality}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a jsDelivr CDN URL for a specific moon image.
|
||||
*
|
||||
* jsDelivr serves files directly from GitHub repositories, making it a
|
||||
* practical option for web applications that need the images without
|
||||
* bundling ~438 MB of assets locally.
|
||||
*
|
||||
* @param filename - Filename returned by `cycleMonth` or `cycleYear`, e.g. `"354.webp"`.
|
||||
* @param set - Image set: `'mm'` (monthly) or `'my'` (yearly).
|
||||
* @param size - Image dimension: `256` or `512`.
|
||||
* @param quality - WebP quality: `75` or `85`.
|
||||
* @param ref - Git ref (branch, tag, or commit SHA). Defaults to `'main'`.
|
||||
* @returns A full CDN URL string.
|
||||
*
|
||||
* @example
|
||||
* const file = cycleMonth();
|
||||
* const url = cdnUrl(file, 'mm', 256, 75);
|
||||
* // => 'https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@main/mm-256-75/354.webp'
|
||||
*/
|
||||
export function cdnUrl(
|
||||
filename: string,
|
||||
set: ImageSet,
|
||||
size: ImageSize,
|
||||
quality: ImageQuality,
|
||||
ref: string = 'main'
|
||||
): string {
|
||||
const folder = imageFolder(set, size, quality);
|
||||
return `https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@${ref}/${folder}/${filename}`;
|
||||
}
|
||||
13
src/index.ts
Normal file
13
src/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export { cycleMonth } from './cycleMonth.js';
|
||||
export { cycleYear } from './cycleYear.js';
|
||||
export {
|
||||
ImageSet,
|
||||
ImageSize,
|
||||
ImageQuality,
|
||||
SYNODIC_MONTH,
|
||||
MONTH_IMAGES,
|
||||
YEAR_IMAGES,
|
||||
MONTH_ANCHOR,
|
||||
YEAR_ANCHOR,
|
||||
} from './types.js';
|
||||
export { imageFolder, cdnUrl } from './helpers.js';
|
||||
40
src/types.ts
Normal file
40
src/types.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Image set identifier.
|
||||
* - 'mm' = moon monthly (synodic cycle, 708 images)
|
||||
* - 'my' = moon yearly (calendar year, 8,760 images)
|
||||
*/
|
||||
export type ImageSet = 'mm' | 'my';
|
||||
|
||||
/**
|
||||
* Image dimension in pixels (square).
|
||||
*/
|
||||
export type ImageSize = 256 | 512;
|
||||
|
||||
/**
|
||||
* WebP compression quality level.
|
||||
*/
|
||||
export type ImageQuality = 75 | 85;
|
||||
|
||||
/**
|
||||
* Length of one synodic month in days.
|
||||
* Source: IAU mean value (J2000.0 epoch).
|
||||
*/
|
||||
export const SYNODIC_MONTH = 29.53058821398858;
|
||||
|
||||
/** Number of images in the monthly (mm) dataset. */
|
||||
export const MONTH_IMAGES = 708;
|
||||
|
||||
/** Number of images in the yearly (my) dataset. */
|
||||
export const YEAR_IMAGES = 8760;
|
||||
|
||||
/**
|
||||
* Anchor date for the monthly cycle: the 2023-11-13 new moon (UTC).
|
||||
* All synodic phase calculations are derived from this reference point.
|
||||
*/
|
||||
export const MONTH_ANCHOR = new Date('2023-11-13T09:27:00Z');
|
||||
|
||||
/**
|
||||
* Anchor date for the yearly cycle: start of the 2023 NASA image collection.
|
||||
* The 8,760 images correspond to one per hour for the full calendar year 2023.
|
||||
*/
|
||||
export const YEAR_ANCHOR = new Date('2023-01-01T00:00:00Z');
|
||||
79
test-cjs.cjs
Normal file
79
test-cjs.cjs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* CJS test suite for moon-cycle v2.
|
||||
* Verifies CommonJS compatibility of the built package.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const {
|
||||
cycleMonth,
|
||||
cycleYear,
|
||||
imageFolder,
|
||||
cdnUrl,
|
||||
SYNODIC_MONTH,
|
||||
MONTH_IMAGES,
|
||||
YEAR_IMAGES,
|
||||
MONTH_ANCHOR,
|
||||
YEAR_ANCHOR,
|
||||
} = require('./dist/index.cjs');
|
||||
|
||||
let passed = 0;
|
||||
let total = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
total++;
|
||||
try {
|
||||
fn();
|
||||
console.log(`[${name}]... PASS`);
|
||||
passed++;
|
||||
} catch (err) {
|
||||
console.error(`[${name}]... FAIL: ${err.message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
test('exports are available via require()', () => {
|
||||
assert.strictEqual(typeof cycleMonth, 'function');
|
||||
assert.strictEqual(typeof cycleYear, 'function');
|
||||
assert.strictEqual(typeof imageFolder, 'function');
|
||||
assert.strictEqual(typeof cdnUrl, 'function');
|
||||
assert.strictEqual(typeof SYNODIC_MONTH, 'number');
|
||||
assert.strictEqual(typeof MONTH_IMAGES, 'number');
|
||||
assert.strictEqual(typeof YEAR_IMAGES, 'number');
|
||||
assert(MONTH_ANCHOR instanceof Date);
|
||||
assert(YEAR_ANCHOR instanceof Date);
|
||||
});
|
||||
|
||||
test('cycleMonth at anchor returns 001.webp', () => {
|
||||
assert.strictEqual(cycleMonth(MONTH_ANCHOR), '001.webp');
|
||||
});
|
||||
|
||||
test('cycleYear at anchor returns 0001.webp', () => {
|
||||
assert.strictEqual(cycleYear(YEAR_ANCHOR), '0001.webp');
|
||||
});
|
||||
|
||||
test('cycleMonth result format is correct', () => {
|
||||
const result = cycleMonth();
|
||||
assert.match(result, /^\d{3}\.webp$/);
|
||||
});
|
||||
|
||||
test('cycleYear result format is correct', () => {
|
||||
const result = cycleYear();
|
||||
assert.match(result, /^\d{4}\.webp$/);
|
||||
});
|
||||
|
||||
test('imageFolder constructs correct path', () => {
|
||||
assert.strictEqual(imageFolder('mm', 256, 75), 'mm-256-75');
|
||||
});
|
||||
|
||||
test('cdnUrl returns expected jsDelivr URL', () => {
|
||||
const url = cdnUrl('001.webp', 'mm', 256, 75);
|
||||
assert.strictEqual(
|
||||
url,
|
||||
'https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@main/mm-256-75/001.webp'
|
||||
);
|
||||
});
|
||||
|
||||
console.log(`\n${passed}/${total} tests passed`);
|
||||
if (passed < total) process.exit(1);
|
||||
12
test.js
12
test.js
|
|
@ -1,12 +0,0 @@
|
|||
const { cycleMonth, cycleYear } = require('./index');
|
||||
|
||||
const date = new Date();
|
||||
|
||||
// Get results
|
||||
const mm = cycleMonth(date)
|
||||
const my = cycleYear(date)
|
||||
|
||||
// Print results
|
||||
console.log(`\nDate = ${date.toISOString()}\nMatch Moon Image URLs to Date..\n`)
|
||||
console.log(`MM = /mm/${mm}`);
|
||||
console.log(`MY = /my/${my}`);
|
||||
240
test.mjs
Normal file
240
test.mjs
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
/**
|
||||
* ESM test suite for moon-cycle v2.
|
||||
* Uses Node.js built-in assert — no test framework required.
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
cycleMonth,
|
||||
cycleYear,
|
||||
imageFolder,
|
||||
cdnUrl,
|
||||
SYNODIC_MONTH,
|
||||
MONTH_IMAGES,
|
||||
YEAR_IMAGES,
|
||||
MONTH_ANCHOR,
|
||||
YEAR_ANCHOR,
|
||||
} from './dist/index.mjs';
|
||||
|
||||
let passed = 0;
|
||||
let total = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
total++;
|
||||
try {
|
||||
fn();
|
||||
console.log(`[${name}]... PASS`);
|
||||
passed++;
|
||||
} catch (err) {
|
||||
console.error(`[${name}]... FAIL: ${err.message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
test('SYNODIC_MONTH is correct IAU value', () => {
|
||||
assert.strictEqual(SYNODIC_MONTH, 29.53058821398858);
|
||||
});
|
||||
|
||||
test('MONTH_IMAGES is 708', () => {
|
||||
assert.strictEqual(MONTH_IMAGES, 708);
|
||||
});
|
||||
|
||||
test('YEAR_IMAGES is 8760', () => {
|
||||
assert.strictEqual(YEAR_IMAGES, 8760);
|
||||
});
|
||||
|
||||
test('MONTH_ANCHOR is 2023-11-13T09:27:00Z', () => {
|
||||
assert.strictEqual(MONTH_ANCHOR.toISOString(), '2023-11-13T09:27:00.000Z');
|
||||
});
|
||||
|
||||
test('YEAR_ANCHOR is 2023-01-01T00:00:00Z', () => {
|
||||
assert.strictEqual(YEAR_ANCHOR.toISOString(), '2023-01-01T00:00:00.000Z');
|
||||
});
|
||||
|
||||
// ─── cycleMonth: return format ────────────────────────────────────────────────
|
||||
|
||||
test('cycleMonth returns a string', () => {
|
||||
assert.strictEqual(typeof cycleMonth(), 'string');
|
||||
});
|
||||
|
||||
test('cycleMonth result matches /^\\d{3}\\.webp$/', () => {
|
||||
const result = cycleMonth();
|
||||
assert.match(result, /^\d{3}\.webp$/);
|
||||
});
|
||||
|
||||
test('cycleMonth result is never 000.webp (images are 1-indexed)', () => {
|
||||
// Test many dates across different lunar phases
|
||||
const anchor = MONTH_ANCHOR.getTime();
|
||||
const synodic_ms = SYNODIC_MONTH * 24 * 60 * 60 * 1000;
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const date = new Date(anchor + i * (synodic_ms / 100));
|
||||
const result = cycleMonth(date);
|
||||
assert.notStrictEqual(result, '000.webp', `Got 000.webp for offset ${i}`);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── cycleMonth: anchor date ──────────────────────────────────────────────────
|
||||
|
||||
test('cycleMonth at anchor date returns 001.webp (start of synodic cycle)', () => {
|
||||
assert.strictEqual(cycleMonth(MONTH_ANCHOR), '001.webp');
|
||||
});
|
||||
|
||||
test('cycleMonth one synodic month + 1 min after anchor returns near start of next cycle', () => {
|
||||
// Adding exactly one synodic month in floating-point can land on either side of the
|
||||
// cycle boundary (001 or 708) due to IEEE 754 rounding. Adding 1 minute steps past it.
|
||||
const oneMonthPlus = new Date(
|
||||
MONTH_ANCHOR.getTime() + SYNODIC_MONTH * 24 * 60 * 60 * 1000 + 60_000
|
||||
);
|
||||
const result = cycleMonth(oneMonthPlus);
|
||||
const index = parseInt(result.replace('.webp', ''), 10);
|
||||
assert(index <= 3, `Expected near start of next cycle (index <= 3), got ${index}`);
|
||||
});
|
||||
|
||||
test('cycleMonth at halfway through synodic month returns ~354.webp', () => {
|
||||
const half = new Date(
|
||||
MONTH_ANCHOR.getTime() + (SYNODIC_MONTH / 2) * 24 * 60 * 60 * 1000
|
||||
);
|
||||
const result = cycleMonth(half);
|
||||
const index = parseInt(result.replace('.webp', ''), 10);
|
||||
// Allow ±1 for rounding
|
||||
assert(index >= 353 && index <= 355, `Expected ~354, got ${index}`);
|
||||
});
|
||||
|
||||
test('cycleMonth result is always in range [001, 708]', () => {
|
||||
// Test dates spanning 5 years
|
||||
const start = new Date('2020-01-01T00:00:00Z');
|
||||
const step = 24 * 60 * 60 * 1000 * 7; // weekly
|
||||
for (let i = 0; i < 260; i++) {
|
||||
const date = new Date(start.getTime() + i * step);
|
||||
const result = cycleMonth(date);
|
||||
const index = parseInt(result.replace('.webp', ''), 10);
|
||||
assert(index >= 1, `Index ${index} below minimum (date: ${date.toISOString()})`);
|
||||
assert(index <= 708, `Index ${index} above maximum (date: ${date.toISOString()})`);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── cycleMonth: past dates ───────────────────────────────────────────────────
|
||||
|
||||
test('cycleMonth handles dates before the anchor (pre-2023)', () => {
|
||||
const past = new Date('2020-06-15T00:00:00Z');
|
||||
const result = cycleMonth(past);
|
||||
assert.match(result, /^\d{3}\.webp$/);
|
||||
const index = parseInt(result.replace('.webp', ''), 10);
|
||||
assert(index >= 1 && index <= 708);
|
||||
});
|
||||
|
||||
// ─── cycleYear: return format ─────────────────────────────────────────────────
|
||||
|
||||
test('cycleYear returns a string', () => {
|
||||
assert.strictEqual(typeof cycleYear(), 'string');
|
||||
});
|
||||
|
||||
test('cycleYear result matches /^\\d{4}\\.webp$/', () => {
|
||||
const result = cycleYear();
|
||||
assert.match(result, /^\d{4}\.webp$/);
|
||||
});
|
||||
|
||||
test('cycleYear result is never 0000.webp (images are 1-indexed)', () => {
|
||||
const anchor = YEAR_ANCHOR.getTime();
|
||||
const year_ms = YEAR_IMAGES * 60 * 60 * 1000;
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const date = new Date(anchor + i * (year_ms / 100));
|
||||
const result = cycleYear(date);
|
||||
assert.notStrictEqual(result, '0000.webp', `Got 0000.webp for offset ${i}`);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── cycleYear: anchor date ───────────────────────────────────────────────────
|
||||
|
||||
test('cycleYear at anchor date returns 0001.webp (start of year)', () => {
|
||||
assert.strictEqual(cycleYear(YEAR_ANCHOR), '0001.webp');
|
||||
});
|
||||
|
||||
test('cycleYear at exactly one year after anchor returns 0001.webp', () => {
|
||||
const oneYear = new Date(
|
||||
YEAR_ANCHOR.getTime() + YEAR_IMAGES * 60 * 60 * 1000
|
||||
);
|
||||
assert.strictEqual(cycleYear(oneYear), '0001.webp');
|
||||
});
|
||||
|
||||
test('cycleYear at halfway through year returns ~4380.webp', () => {
|
||||
const half = new Date(
|
||||
YEAR_ANCHOR.getTime() + (YEAR_IMAGES / 2) * 60 * 60 * 1000
|
||||
);
|
||||
const result = cycleYear(half);
|
||||
const index = parseInt(result.replace('.webp', ''), 10);
|
||||
assert(index >= 4379 && index <= 4381, `Expected ~4380, got ${index}`);
|
||||
});
|
||||
|
||||
test('cycleYear result is always in range [0001, 8760]', () => {
|
||||
const start = new Date('2020-01-01T00:00:00Z');
|
||||
const step = 24 * 60 * 60 * 1000 * 7;
|
||||
for (let i = 0; i < 260; i++) {
|
||||
const date = new Date(start.getTime() + i * step);
|
||||
const result = cycleYear(date);
|
||||
const index = parseInt(result.replace('.webp', ''), 10);
|
||||
assert(index >= 1, `Index ${index} below minimum`);
|
||||
assert(index <= 8760, `Index ${index} above maximum`);
|
||||
}
|
||||
});
|
||||
|
||||
test('cycleYear handles dates before 2023', () => {
|
||||
const past = new Date('2021-06-15T00:00:00Z');
|
||||
const result = cycleYear(past);
|
||||
assert.match(result, /^\d{4}\.webp$/);
|
||||
const index = parseInt(result.replace('.webp', ''), 10);
|
||||
assert(index >= 1 && index <= 8760);
|
||||
});
|
||||
|
||||
// ─── cycleMonth/cycleYear default parameter ───────────────────────────────────
|
||||
|
||||
test('cycleMonth() with no args returns a valid result', () => {
|
||||
const result = cycleMonth();
|
||||
assert.match(result, /^\d{3}\.webp$/);
|
||||
});
|
||||
|
||||
test('cycleYear() with no args returns a valid result', () => {
|
||||
const result = cycleYear();
|
||||
assert.match(result, /^\d{4}\.webp$/);
|
||||
});
|
||||
|
||||
// ─── imageFolder ──────────────────────────────────────────────────────────────
|
||||
|
||||
test('imageFolder returns correct folder name', () => {
|
||||
assert.strictEqual(imageFolder('mm', 256, 75), 'mm-256-75');
|
||||
assert.strictEqual(imageFolder('mm', 512, 85), 'mm-512-85');
|
||||
assert.strictEqual(imageFolder('my', 256, 75), 'my-256-75');
|
||||
assert.strictEqual(imageFolder('my', 512, 85), 'my-512-85');
|
||||
});
|
||||
|
||||
// ─── cdnUrl ───────────────────────────────────────────────────────────────────
|
||||
|
||||
test('cdnUrl returns a valid jsDelivr URL', () => {
|
||||
const url = cdnUrl('354.webp', 'mm', 256, 75);
|
||||
assert.strictEqual(
|
||||
url,
|
||||
'https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@main/mm-256-75/354.webp'
|
||||
);
|
||||
});
|
||||
|
||||
test('cdnUrl respects custom ref parameter', () => {
|
||||
const url = cdnUrl('001.webp', 'my', 512, 85, 'v2.0.0');
|
||||
assert.strictEqual(
|
||||
url,
|
||||
'https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@v2.0.0/my-512-85/001.webp'
|
||||
);
|
||||
});
|
||||
|
||||
test('cdnUrl integrates with cycleMonth output', () => {
|
||||
const filename = cycleMonth(MONTH_ANCHOR);
|
||||
const url = cdnUrl(filename, 'mm', 256, 75);
|
||||
assert(url.startsWith('https://cdn.jsdelivr.net/gh/acamarata/moon-cycle@main/mm-256-75/'));
|
||||
assert(url.endsWith('.webp'));
|
||||
});
|
||||
|
||||
// ─── Summary ─────────────────────────────────────────────────────────────────
|
||||
|
||||
console.log(`\n${passed}/${total} tests passed`);
|
||||
if (passed < total) process.exit(1);
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
14
tsup.config.ts
Normal file
14
tsup.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
outDir: 'dist',
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
target: 'es2020',
|
||||
platform: 'neutral',
|
||||
outExtension: ({ format }) => ({ js: format === 'cjs' ? '.cjs' : '.mjs' }),
|
||||
});
|
||||
Loading…
Reference in a new issue