salat 4.3.0 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,275 +1,94 @@
1
- # Salat [![npm version](https://badge.fury.io/js/salat.svg)](https://badge.fury.io/js/salat)
1
+ # 🧭 salat-cli
2
2
 
3
- PLEASE SUPPORT THIS REPO WITH A STAR ⭐🌟💫
3
+ > Daily Moroccan prayers time, right in your console, at the tip of your fingers. ✨
4
4
 
5
- ## Description
5
+ A modern, visually rich CLI for checking prayer times in Morocco, built with **React** and **Ink**.
6
6
 
7
- > Daily prayer time in all the cities in [Morocco](https://www.google.com/search?q=morocco) , directly in your terminal, at the tip of your fingers
7
+ [![npm version](https://img.shields.io/npm/v/salat.svg)](https://www.npmjs.com/package/salat)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
9
 
9
- **A stupid simple Command line utility to get the daily prayers time for all the citiy in Morocco**
10
+ ---
10
11
 
11
- The source of the data is [the Moroccan Ministery Website](http://www.habous.gov.ma)
12
+ ## 🚀 Features
12
13
 
13
- ## Getting started
14
+ - **Live Countdown**: "Remaining" time updates every second in real-time.
15
+ - **Rich UI**: Beautiful terminal interface with colors and borders.
16
+ - **Morocco Focused**: Supports 190+ cities across Morocco.
17
+ - **Smart Caching**: Local storage caching to minimize API calls.
18
+ - **Developer Friendly**: Built with TypeScript and Commander.js.
14
19
 
15
- ```bash
16
- npx salat [cityName]
17
- ```
18
-
19
- Please note that if the cityName contains space of `'` you need to use quotes, example
20
+ ## 📦 Installation
20
21
 
21
22
  ```bash
22
- npx salat "el jadida"
23
- npx salat "L'msid"
23
+ npm install -g salat
24
24
  ```
25
25
 
26
- ## City List
27
-
28
- This is an exhaustive list of the supported cities :
29
-
30
- - Agadir
31
- - Ahfir
32
- - Ain Aouda
33
- - Aïn Chaïr
34
- - Ait El Kak
35
- - Ait Ourir
36
- - Akayouar
37
- - Akdal Amelchil
38
- - Akdez
39
- - Akhefnir
40
- - Akka
41
- - Aknoul
42
- - Akoudal Amelchil Midelt
43
- - Amkala
44
- - Amsmrir
45
- - Araich
46
- - Arbaoua
47
- - Arfoud
48
- - Asa
49
- - Askin
50
- - Asoul
51
- - Assila
52
- - Azemmour
53
- - Azilal
54
- - Azrou
55
- - Bab Bared
56
- - Ben Ahmed
57
- - Ben Slimane
58
- - Bengrir
59
- - Beni Adrar
60
- - Beni Ansar
61
- - Beni Mellal
62
- - Beni Tejit
63
- - Berkane
64
- - Berrchid
65
- - Bir Anzaran
66
- - Bir Kandour
67
- - Bouanan
68
- - Bouarfa
69
- - Bouikra
70
- - Bouizkaren
71
- - Boujdour
72
- - Boukrae
73
- - Boulmane
74
- - Boumalen Dadas
75
- - Bourd
76
- - Bouskoura
77
- - Bouznika
78
- - Casablanca
79
- - Chefchaouan
80
- - Chichaoua
81
- - Dakhla
82
- - Debdou
83
- - Demnat
84
- - Deryouche
85
- - El Brouj
86
- - El Gara
87
- - El Hajeb
88
- - El Jabha
89
- - El Jadida
90
- - El Kasba
91
- - El Mahbes
92
- - El Menzel
93
- - Enif
94
- - Erich
95
- - Errachidia
96
- - Essaouira
97
- - Ezak
98
- - Fem Lehsan
99
- - Fem Zkid
100
- - Ferkhana
101
- - Fes
102
- - Fezouane
103
- - Figuig
104
- - Fnideq
105
- - Fquih Ben Salah
106
- - Geltat Zamour
107
- - Guelmim
108
- - Guercif
109
- - Hoceima
110
- - Igherem
111
- - Ikes
112
- - Imelchil
113
- - Imin Telat
114
- - Imntanout
115
- - Imouzzer Kandar
116
- - Jerada
117
- - Kalaat Megouna
118
- - Kalaat Sraghna
119
- - Kares
120
- - Kariat Ba Mohammed
121
- - Kasbah Tadla
122
- - Katara
123
- - Kelmima
124
- - Kénitra
125
- - Ketama
126
- - Khemis Sidi Abd Jelil
127
- - Khemis Zemamra
128
- - Khémissat
129
- - Khenifra
130
- - Khouribga
131
- - Ksar El Kebir
132
- - Ksar El Sghir
133
- - Ksar Ich
134
- - L'msid
135
- - Laayoune
136
- - Laayoune Sidi Mellouk
137
- - Lagouira
138
- - Marrakech
139
- - Martil
140
- - Mediek
141
- - Meknes
142
- - Melilla
143
- - Meskoura
144
- - Metmata
145
- - Midelt
146
- - Misour
147
- - Mohammedia
148
- - Moulay Bouaza
149
- - Moulay Bousselham
150
- - Moulay Yacoub
151
- - Nador
152
- - Oualidia
153
- - Ouazane
154
- - Oued Amlil
155
- - Oued Law
156
- - Oued Zam
157
- - Oujda
158
- - Oulad Ayad
159
- - Oulad Tayma
160
- - Oulmes
161
- - Ourzazate
162
- - Ousered
163
- - Rabat
164
- - Ras Alma
165
- - Remani
166
- - Rhamna
167
- - Ribate El Kheir
168
- - Risani
169
- - Safi
170
- - Saïdia
171
- - Sebta
172
- - Sefrou
173
- - Selouane
174
- - Settat
175
- - Sidi Benour
176
- - Sidi Ghanem
177
- - Sidi Ifni
178
- - Sidi Kacém
179
- - Sidi Slimane
180
- - Sidi Yahya Gharb
181
- - Smara
182
- - Souq Arbiâ Gharb
183
- - Tafntan
184
- - Tafougalt
185
- - Tafraouet
186
- - Tahla
187
- - Tahla
188
- - Talouine
189
- - Talsint
190
- - Tamslouhet
191
- - Tanger
192
- - Tantan
193
- - Taounat
194
- - Taourirt
195
- - Tarfaya
196
- - Taroudant
197
- - Tasltanet
198
- - Tata
199
- - Taza
200
- - Tazarin
201
- - Tefariti
202
- - Temnar
203
- - Tendrara
204
- - Tenghir
205
- - Tenjedad
206
- - Tetouan
207
- - Tiflet
208
- - Tindit
209
- - Tisa
210
- - Tisenet
211
- - Tizi Ousli
212
- - Tiznit
213
- - Toulkoult
214
- - Yefrin
215
- - Yousofia
216
- - Zagoura
217
- - Zaouiat Ahansal
218
- - Zaouiat Moulay Ibrahim
219
- - Zayou
220
- - Zerhoune
26
+ ## 🛠 Usage
221
27
 
222
- Alternatively You can also install it globally
28
+ Simply run `salat` to see prayer times for the default city (Marrakech), or specify a city.
223
29
 
224
30
  ```bash
225
- # Install
31
+ # Get prayer times for the default city
32
+ salat
226
33
 
227
- $ npm i -g salat
34
+ # Get prayer times for a specific city
35
+ salat times Rabat
228
36
 
229
- # Run with default city
37
+ # Run once and exit (no live timer)
38
+ salat -1
230
39
 
231
- $ salat
40
+ # Show a rich visual guide
41
+ salat guide
232
42
 
233
- # Run with custom city
234
-
235
- $ salat [cityName]
43
+ # List all available cities
44
+ salat cities
236
45
  ```
237
46
 
238
- ## Output
47
+ ### CLI Help
239
48
 
240
- ```bash
241
- # The programs prints to the console the prayers' time for the current day in the default city as shown bellow:
242
- ```
49
+ ```text
50
+ Usage: salat [options] [command]
243
51
 
244
- ![screen 1](images/screenShot1.png)
245
- ![screen 2](images/screenShot2.png)
246
- ![screen 3](images/screenShot3.png)
52
+ Daily Moroccan prayers time, right in your console
247
53
 
248
- ## Change the default city
54
+ Options:
55
+ -V, --version output the version number
56
+ -h, --help display help for command
57
+
58
+ Commands:
59
+ times [options] [city] Get prayer times for a city
60
+ guide Show a rich visual guide to using salat-cli
61
+ cities Display the list of available city names
62
+ help [command] display help for command
63
+ ```
249
64
 
250
- - The default city is :heart: [Marrakech](https://www.google.com/search?q=marrakech) :heart:, set as a value for the `DEFAULT_CITY` variable in `./constants.js`
65
+ ## 🏗 Dependencies
251
66
 
252
- - You can change it by replacing `Marrakech` by your city name according to the values from `./data/cities.json`
67
+ This project is built on the shoulders of giants:
253
68
 
254
- ## Help
69
+ - [**Ink**](https://github.com/vadimdemedes/ink) - React for interactive command-line apps.
70
+ - [**Commander.js**](https://github.com/tj/commander) - The complete solution for node.js command-line interfaces.
71
+ - [**date-fns**](https://date-fns.org/) - Modern JavaScript date utility library.
72
+ - [**node-fetch**](https://github.com/node-fetch/node-fetch) - A light-weight module that brings `window.fetch` to Node.js.
73
+ - [**domino**](https://github.com/fent/domino) - Server-side DOM implementation for parsing API responses.
74
+ - [**node-localstorage**](https://github.com/lmaccherone/node-localstorage) - LocalStorage implementation for Node.js.
255
75
 
256
- - Please keep in mind that this is a work in progress in a very early stages, any help is appreciated and more than welcome.
76
+ ## 🤝 Contributing
257
77
 
258
- - If you think this piece of code is anyhow useful, please feel free to `contribute`, `star` :star::star: and `share` 🙏 🙏
78
+ Contributions are welcome! Whether it's a bug fix, a new feature, or better documentation.
259
79
 
260
- ## Todo
80
+ 1. **Fork** the project.
81
+ 2. **Clone** your fork: `git clone https://github.com/kafiln/salat-cli.git`
82
+ 3. **Install** dependencies: `npm install`
83
+ 4. **Live Development**: `npm run dev`
84
+ 5. **Build**: `npm run build`
85
+ 6. **Test**: `npm test`
86
+ 7. **Submit** a Pull Request.
261
87
 
262
- - [x] Use a default city
263
- - [x] Use localstorage-like api for caching purposes
264
- - [ ] Improve performance
265
- - [ ] Add unit tests
266
- - [x] Add a documentation site
267
- - [ ] Command to set the default city
268
- - [ ] Command to display the list of available cities
269
- - [ ] Command to display the time table for the whole month
88
+ ## Show your support
270
89
 
271
- ## License
90
+ If this project helped you, please consider giving it a **Star** on GitHub! It means a lot.
272
91
 
273
- This project is under the MIT license.
92
+ ---
274
93
 
275
- ### Built With 💖 in Ramadan
94
+ Built with ❤️ by [Kafil NASDAMI](https://github.com/kafiln)
package/dist/app.js CHANGED
@@ -1,19 +1,16 @@
1
1
  #!/usr/bin/env node
2
- // Project's dependencies
3
- import { render } from "ink";
4
- import React from "react";
5
- import App from "./ui.js"; // Note the .js extension for ESM imports
6
- import { Command } from "commander";
7
2
  // Project's setup
8
3
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
4
+ import { citiesCommand } from "#commands/cities";
5
+ import { guideCommand } from "#commands/guide";
6
+ import { timesCommand } from "#commands/times";
7
+ import { Command } from "commander";
9
8
  const program = new Command();
10
9
  program
11
10
  .name("salat")
12
11
  .description("Daily Moroccan prayers time, right in your console")
13
- .version("4.2.0")
14
- .argument("[city]", "City name")
15
- .option("-1, --once", "Run once and exit", false)
16
- .action((city, options) => {
17
- render(React.createElement(App, { cityNameArg: city, once: options.once }));
18
- });
12
+ .version("4.6.0")
13
+ .addCommand(timesCommand, { isDefault: true })
14
+ .addCommand(guideCommand)
15
+ .addCommand(citiesCommand);
19
16
  program.parse();
@@ -0,0 +1,11 @@
1
+ import CitiesApp from "#components/CitiesApp";
2
+ import { Command } from "commander";
3
+ import { render } from "ink";
4
+ import React from "react";
5
+ export const citiesCommand = new Command("cities")
6
+ .description("Display the list of available city names")
7
+ .option("-1, --once", "Run once and exit", true) // Default to true for a static list
8
+ .action(() => {
9
+ // We always run this "once" because it's just a list
10
+ render(React.createElement(CitiesApp));
11
+ });
@@ -0,0 +1,10 @@
1
+ import HelpApp from "#components/HelpApp";
2
+ import { Command } from "commander";
3
+ import { render } from "ink";
4
+ import React from "react";
5
+ export const guideCommand = new Command("guide")
6
+ .description("Show a rich visual guide to using salat-cli")
7
+ .option("-1, --once", "Run once and exit", false)
8
+ .action(() => {
9
+ render(React.createElement(HelpApp));
10
+ });
@@ -0,0 +1,11 @@
1
+ import App from "#components/TimesApp";
2
+ import { Command } from "commander";
3
+ import { render } from "ink";
4
+ import React from "react";
5
+ export const timesCommand = new Command("times")
6
+ .description("Get prayer times for a city")
7
+ .argument("[city]", "City name")
8
+ .option("-1, --once", "Run once and exit", false)
9
+ .action((city, options) => {
10
+ render(React.createElement(App, { cityNameArg: city, once: options.once }));
11
+ });
@@ -0,0 +1,10 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import citiesData from "../data/cities.json" with { type: "json" };
4
+ const CitiesApp = () => {
5
+ const cities = citiesData;
6
+ // Sort cities alphabetically
7
+ const sortedCities = [...cities].sort((a, b) => a.name.localeCompare(b.name));
8
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: ["\uD83C\uDF0D Available Cities in Morocco (", cities.length, ")"] }) }), _jsx(Box, { flexDirection: "row", flexWrap: "wrap", children: sortedCities.map((city) => (_jsxs(Box, { width: 25, marginBottom: 0, children: [_jsx(Text, { color: "gray", children: "- " }), _jsx(Text, { children: city.name })] }, city.id))) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", children: ["Tip: Use these names with the 'times' command, e.g., 'salat times ", sortedCities[0]?.name, "'"] }) })] }));
9
+ };
10
+ export default CitiesApp;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ const HelpApp = () => {
4
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan", children: [_jsx(Box, { marginBottom: 1, justifyContent: "center", children: _jsx(Text, { bold: true, color: "cyan", children: "\u2728 SALAT CLI GUIDE \u2728" }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Usage:" }), _jsx(Text, { children: " $ salat [command] [options]" })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Commands:" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: "times [city]" }) }), _jsx(Text, { children: "Show prayer times (default)" })] }), _jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: "guide" }) }), _jsx(Text, { children: "Show this rich help page" })] }), _jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: "cities" }) }), _jsx(Text, { children: "Display the list of available city names" })] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Options:" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "green", children: "-1, --once" }) }), _jsx(Text, { children: "Run once and exit" })] }), _jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "green", children: "-v, --version" }) }), _jsx(Text, { children: "Show version" })] }), _jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "green", children: "-h, --help" }) }), _jsx(Text, { children: "Show standard text help" })] })] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { italic: true, color: "gray", children: "Example:" }), _jsx(Text, { color: "white", children: " $ salat Rabat --once" }), _jsx(Text, { color: "white", children: " $ salat guide" })] }), _jsx(Box, { marginTop: 1, justifyContent: "flex-end", children: _jsx(Text, { color: "gray", children: "Tip: Press Ctrl+C to exit the live timer." }) })] }));
5
+ };
6
+ export default HelpApp;
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { LOCAL_STORAGE_PATH } from "#constants";
3
- import { getCityId, getCityName, getData, getNextPrayer, parsePrayerTimesFromResponse, tConv24, } from "#utils";
2
+ import { LOCAL_STORAGE_PATH } from "#services/constants";
3
+ import { getCityId, getCityName, getData, getNextPrayer, parsePrayerTimesFromResponse, tConv24, } from "#services/utils";
4
4
  import { Box, Text, useApp } from "ink";
5
5
  import { useEffect, useState } from "react";
6
6
  // @ts-ignore
7
7
  import { format } from "date-fns";
8
8
  import { LocalStorage } from "node-localstorage";
9
- import citiesData from "./data/cities.json" with { type: "json" };
9
+ import citiesData from "../data/cities.json" with { type: "json" };
10
10
  const cities = citiesData;
11
11
  const localStorage = new LocalStorage(LOCAL_STORAGE_PATH);
12
12
  const App = ({ cityNameArg, once }) => {
@@ -1,8 +1,8 @@
1
- import { API_URL, DEFAULT_CITY, NOT_FOUND_ERROR } from "#constants";
1
+ import { API_URL, DEFAULT_CITY, NOT_FOUND_ERROR } from "#services/constants";
2
2
  import { addDays, differenceInSeconds, format, parse } from "date-fns";
3
3
  import domino from "domino";
4
4
  import fetch from "node-fetch";
5
- import prayersData from "./data/prayers.json" with { type: "json" };
5
+ import prayersData from "../data/prayers.json" with { type: "json" };
6
6
  export const getCityName = (arg, cities) => {
7
7
  if (arg == null)
8
8
  return DEFAULT_CITY;
@@ -1,5 +1,5 @@
1
- import * as constants from '#constants';
2
- import { getCityId, getCityName, getNextPrayer, parsePrayerTimesFromResponse } from '#utils';
1
+ import * as constants from '#services/constants';
2
+ import { getCityId, getCityName, getNextPrayer, parsePrayerTimesFromResponse } from '#services/utils';
3
3
  import { parseISO } from 'date-fns';
4
4
  import { describe, expect, it, vi } from 'vitest';
5
5
  // Mock cities data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salat",
3
- "version": "4.3.0",
3
+ "version": "4.6.1",
4
4
  "imports": {
5
5
  "#*": "./dist/*.js"
6
6
  },
@@ -11,6 +11,9 @@
11
11
  "salat": "dist/app.js"
12
12
  },
13
13
  "type": "module",
14
+ "files": [
15
+ "dist"
16
+ ],
14
17
  "dependencies": {
15
18
  "commander": "^14.0.3",
16
19
  "date-fns": "^4.1.0",
@@ -22,7 +25,7 @@
22
25
  },
23
26
  "scripts": {
24
27
  "dev": "node --no-warnings --loader ts-node/esm src/app.ts",
25
- "build": "tsc",
28
+ "build": "tsc && chmod +x dist/app.js",
26
29
  "start": "node dist/app.js",
27
30
  "prestart": "npm run build",
28
31
  "prepublishOnly": "npm run build",
package/src/app.ts DELETED
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // Project's dependencies
4
- import { render } from "ink";
5
- import React from "react";
6
- import App from "./ui.js"; // Note the .js extension for ESM imports
7
-
8
- import { Command } from "commander";
9
-
10
- // Project's setup
11
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
12
-
13
- const program = new Command();
14
-
15
- program
16
- .name("salat")
17
- .description("Daily Moroccan prayers time, right in your console")
18
- .version("4.2.0")
19
- .argument("[city]", "City name")
20
- .option("-1, --once", "Run once and exit", false)
21
- .action((city, options) => {
22
- render(React.createElement(App, { cityNameArg: city, once: options.once }));
23
- });
24
-
25
- program.parse();
package/src/constants.ts DELETED
@@ -1,13 +0,0 @@
1
- export const API_URL = "https://www.habous.gov.ma/prieres/horaire-api.php";
2
-
3
- export const BANNER = ``;
4
-
5
- export const NOT_FOUND_ERROR = `
6
- Your city was not found in the list
7
- Using the default city
8
- ----------------------
9
- You may need to check the spelling
10
- `;
11
-
12
- export const DEFAULT_CITY = "Marrakech";
13
- export const LOCAL_STORAGE_PATH = "./storage";