Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 37 additions & 26 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ Built for humans and agents.

[![ramadan-cli](https://raw.githubusercontent.com/ahmadawais/ramadan-cli/refs/heads/main/.github/ramadan.gif)](https://x.com/MrAhmadAwais/)


## Install

```sh
Expand All @@ -41,7 +40,6 @@ npm install -g ramadan-cli@latest
roza
```


## Agent Usage

Install this repo as an agent skill package:
Expand Down Expand Up @@ -89,6 +87,10 @@ roza reset
ramadan-cli config --city "San Francisco" --country "United States" --method 2 --school 0 --timezone "America/Los_Angeles"
ramadan-cli config --show
ramadan-cli config --clear

# Check for updates.
roza update
ramadan-cli update
```

## CLI Surface
Expand All @@ -97,6 +99,7 @@ ramadan-cli config --clear
ramadan-cli [city] [options]
ramadan-cli reset
ramadan-cli config [options]
ramadan-cli update
```

Notes:
Expand All @@ -114,38 +117,45 @@ Notes:

Global/main command flags (`ramadan-cli [city]`):

| Flag | Type | Default | Behavior |
| --- | --- | --- | --- |
| `[city]` | `string` | saved location | One-off lookup; does not overwrite saved default |
| `-c, --city <city>` | `string` | none | Same as city arg |
| `-a, --all` | `boolean` | `false` | Show all Ramadan rows |
| `-n, --number <1-30>` | `number` | none | Show specific roza |
| `-p, --plain` | `boolean` | `false` | Plain text output without ASCII banner |
| `-j, --json` | `boolean` | `false` | JSON-only output for scripts |
| `-s, --status` | `boolean` | `false` | Single-line next event output for status bars and coding agents |
| `--first-roza-date <YYYY-MM-DD>` | `string` | stored/API | Persist custom first roza date |
| `--clear-first-roza-date` | `boolean` | `false` | Clear custom first roza date and use API Ramadan date |
| `-v, --version` | `boolean` | n/a | Print version only |
| `-h, --help` | `boolean` | n/a | Show help |
| Flag | Type | Default | Behavior |
| -------------------------------- | --------- | -------------- | --------------------------------------------------------------- |
| `[city]` | `string` | saved location | One-off lookup; does not overwrite saved default |
| `-c, --city <city>` | `string` | none | Same as city arg |
| `-a, --all` | `boolean` | `false` | Show all Ramadan rows |
| `-n, --number <1-30>` | `number` | none | Show specific roza |
| `-p, --plain` | `boolean` | `false` | Plain text output without ASCII banner |
| `-j, --json` | `boolean` | `false` | JSON-only output for scripts |
| `-s, --status` | `boolean` | `false` | Single-line next event output for status bars and coding agents |
| `--first-roza-date <YYYY-MM-DD>` | `string` | stored/API | Persist custom first roza date |
| `--clear-first-roza-date` | `boolean` | `false` | Clear custom first roza date and use API Ramadan date |
| `-v, --version` | `boolean` | n/a | Print version only |
| `-h, --help` | `boolean` | n/a | Show help |

Config flags (`ramadan-cli config`):

| Flag | Type | Behavior |
| --- | --- | --- |
| `--city <city>` | `string` | Save city |
| `--country <country>` | `string` | Save country |
| `--latitude <latitude>` | `number` | Save latitude (`-90..90`) |
| `--longitude <longitude>` | `number` | Save longitude (`-180..180`) |
| `--method <id>` | `number` | Save method (`0..23`) |
| `--school <id>` | `number` | Save school (`0=Shafi`, `1=Hanafi`) |
| `--timezone <timezone>` | `string` | Save timezone |
| `--show` | `boolean` | Print saved config |
| `--clear` | `boolean` | Clear saved config |
| Flag | Type | Behavior |
| ------------------------- | --------- | ----------------------------------- |
| `--city <city>` | `string` | Save city |
| `--country <country>` | `string` | Save country |
| `--latitude <latitude>` | `number` | Save latitude (`-90..90`) |
| `--longitude <longitude>` | `number` | Save longitude (`-180..180`) |
| `--method <id>` | `number` | Save method (`0..23`) |
| `--school <id>` | `number` | Save school (`0=Shafi`, `1=Hanafi`) |
| `--timezone <timezone>` | `string` | Save timezone |
| `--show` | `boolean` | Print saved config |
| `--clear` | `boolean` | Clear saved config |

Reset command:

- `ramadan-cli reset` clears saved location, method, school, timezone, and custom first roza date.

Update command:

- `ramadan-cli update` checks npm registry for a newer version of ramadan-cli.
- Compares your current version with the latest available version.
- Shows an update prompt with installation instructions if a newer version is available.
- No network errors or failures will crash the CLI; errors are handled gracefully.

## Aliases

- `roza` (same CLI)
Expand Down Expand Up @@ -201,6 +211,7 @@ pnpm test
pnpm build
pnpm lint
```

## API

Powered by [Aladhan Prayer Times API](https://aladhan.com/prayer-times-api)
Expand Down
76 changes: 76 additions & 0 deletions src/__tests__/update.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { updateCommand } from "../commands/update.js";

// Mock fetch globally
const mockFetch = vi.fn();
global.fetch = mockFetch as unknown as typeof fetch;

// Mock console methods
const mockLog = vi.spyOn(console, "log").mockImplementation(() => {});
const mockError = vi.spyOn(console, "error").mockImplementation(() => {});

describe("updateCommand", () => {
beforeEach(() => {
vi.clearAllMocks();
});

afterEach(() => {
vi.clearAllMocks();
});

it("should show message when up to date", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
"dist-tags": {
latest: "6.2.0",
},
}),
});

await updateCommand("6.2.0");

expect(mockLog).toHaveBeenCalledWith(
expect.stringContaining("latest version"),
);
expect(mockError).not.toHaveBeenCalled();
});

it("should show update message when new version available", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
"dist-tags": {
latest: "7.0.0",
},
}),
});

await updateCommand("6.2.0");

expect(mockLog).toHaveBeenCalledWith(
expect.stringContaining("Update available"),
);
expect(mockLog).toHaveBeenCalledWith(
expect.stringContaining("npm install -g ramadan-cli@latest"),
);
});

it("should handle fetch errors gracefully", async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
});

await updateCommand("6.2.0");

expect(mockError).toHaveBeenCalled();
});

it("should handle network errors gracefully", async () => {
mockFetch.mockRejectedValueOnce(new Error("Network error"));

await updateCommand("6.2.0");

expect(mockError).toHaveBeenCalledWith(expect.stringContaining("Error"));
});
});
Loading