wlbot 1.1.3 → 1.1.5

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/LICENSE.txt CHANGED
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -12,6 +12,7 @@
12
12
  - [Install npm Packages](#install-npm-packages)
13
13
  - [Installing CLI Globally](#installing-cli-globally)
14
14
  - [Calling the CLI](#calling-the-cli)
15
+ - [Upgrading `wlbot` via `npm`](#upgrading-wlbot-via-npm)
15
16
  - [Contributing](#contributing)
16
17
  - [License](#license)
17
18
  - [References](#references)
@@ -95,8 +96,25 @@ There are several commands that you will probably call more frequently than othe
95
96
  - `wlbot metadata mine`: This command will return an array of `station-id` numbers for the weather station's that your WeatherLink account has permission to view data for.
96
97
  - `wlbot weather current <station-id>`: This command will return the current weather record for the station whose station id matches `<station-id>`.
97
98
 
99
+ ## Upgrading `wlbot` via `npm`
100
+ If you installed `wlbot` via `npm`, you can quickly get an updated version via the command line.
101
+
102
+ To see what version of `wlbot` you currently have installed:
103
+ 1. Open a command line window and run `wlbot -V`.
104
+
105
+ To upgrade your installed version of `wlbot`:
106
+ 1. Open a command line window and run `npm update -g wlbot`.
107
+
108
+ Downloading the upgrade may take a minute or two based on your internet connection.
109
+
98
110
  ## Contributing
99
- All contributions are welcome! First, search open issues to see if a ticket has already been created for the issue or feature request that you have. If a ticket does not already exist, open an issue to discuss what contributions you would like to make. All contributions should be developed in a `feature/` branch as a PR will be required before any changes are merged into the `main` branch.
111
+ All contributions are welcome!
112
+
113
+ First, search open issues to see if a ticket has already been created for the issue or feature request that you have. If a ticket does not already exist, open an issue to discuss what contributions you would like to make.
114
+
115
+ **All contributions should be developed in a `feature/` branch off of the `development` branch as a PR will be required before any changes are merged back into the `development` branch.**
116
+
117
+ If you are introducing new functionality, please add unit tests (UTs) to ensure the functionality of your contribution and its backwards compatibility. All UTs should be able to run locally. You will need to install the `devDependencies` to run the UTs.
100
118
 
101
119
  ## License
102
120
  Distributed under the MIT License. See `LICENSE.txt` for more information.
@@ -105,4 +123,4 @@ Distributed under the MIT License. See `LICENSE.txt` for more information.
105
123
  Below are several references that were used to help find inspiration for this project, get a starting point for the CLI, and serve as a resource for the WeatherLink API.
106
124
  - [WeatherLink Developer Portal](https://weatherlink.github.io)
107
125
  - [WeatherLink Portal](https://www.weatherlink.com)
108
- - [How to Build a Command Line Interface (CLI) Using Node.js](https://cheatcode.co/tutorials/how-to-build-a-command-line-interface-cli-using-node-js)
126
+ - [How to Build a Command Line Interface (CLI) Using Node.js](https://cheatcode.co/tutorials/how-to-build-a-command-line-interface-cli-using-node-js)
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
 
4
4
  export default (options) => {
5
- const spinner = !options.raw ? ora('Reading Environment Variables').start() : undefined;
5
+ const spinner = ora('Reading Environment Variables').start();
6
6
 
7
7
  let evValues = {
8
8
  "WEATHER_LINK_API_KEY": process.env.WEATHER_LINK_API_KEY || "",
@@ -17,5 +17,6 @@ export default (options) => {
17
17
  }
18
18
 
19
19
  spinner.succeed(chalk.green.bold(`Environment Variables Retrieved`));
20
- return console.log(JSON.stringify(evValues, undefined, 2))
21
- };
20
+ console.log(JSON.stringify(evValues, undefined, 2));
21
+ return;
22
+ };
@@ -4,41 +4,52 @@ import ora from 'ora';
4
4
 
5
5
  import { buildWeatherLinkApiUrl, checkForRequired } from '../../lib/utils.js';
6
6
 
7
- export default (options) => {
8
- const spinner = !options.raw ? ora('Retrieving Sensor Catalog').start() : undefined;
7
+ export default async (options) => {
8
+ const spinner = !options.raw && !options.dryRun ? ora('Retrieving Sensor Catalog').start() : undefined;
9
9
 
10
10
  const envVars = checkForRequired(["WEATHER_LINK_API_KEY", "WEATHER_LINK_API_SECRET", "WEATHER_LINK_BASE_API_URL"])
11
11
  if (!envVars.exist) {
12
- if (spinner) {
13
- spinner.fail('Failed to Retrieve Catalog')
12
+ if (spinner) {
13
+ spinner.fail('Failed to Retrieve Catalog');
14
14
  }
15
-
16
- return console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
15
+
16
+ console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
17
+ return;
17
18
  }
18
19
 
19
20
  const API_KEY = process.env.WEATHER_LINK_API_KEY;
20
21
 
21
- axios.get(
22
- buildWeatherLinkApiUrl(
23
- `sensor-catalog`,
24
- { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) },
25
- { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) }
26
- )
27
- )
28
- .then((response) => {
29
- if (options.raw) {
30
- return console.log(JSON.stringify(response.data));
31
- }
32
-
33
- spinner.succeed(chalk.green.bold(`Catalog Retrieved`));
34
- return console.dir(response.data, { depth: null })
35
- })
36
- .catch((error) => {
37
- if (options.raw) {
38
- return console.log(JSON.stringify(error.response.data));
39
- }
22
+ const urlToQuery = buildWeatherLinkApiUrl(
23
+ `sensor-catalog`,
24
+ { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) },
25
+ { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) }
26
+ );
40
27
 
41
- spinner.fail('Failed to Retrieve Catalog');
42
- return console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
43
- })
44
- };
28
+ if (options.dryRun) {
29
+ console.log(urlToQuery);
30
+ return;
31
+ }
32
+
33
+ try {
34
+ const response = await axios.get(urlToQuery);
35
+
36
+ if (options.raw) {
37
+ console.log(JSON.stringify(response.data));
38
+ return response;
39
+ }
40
+
41
+ spinner.succeed(chalk.green.bold(`Catalog Retrieved`));
42
+ console.dir(response.data, { depth: null });
43
+ return response;
44
+
45
+ } catch (error) {
46
+ if (options.raw) {
47
+ console.log(JSON.stringify(error.response.data));
48
+ throw (error);
49
+ }
50
+
51
+ spinner.fail('Failed to Retrieve Catalog');
52
+ console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
53
+ throw (error);
54
+ }
55
+ };
@@ -4,47 +4,58 @@ import ora from 'ora';
4
4
 
5
5
  import { buildWeatherLinkApiUrl, checkForRequired } from '../../lib/utils.js';
6
6
 
7
- export default (options) => {
8
- const spinner = !options.raw ? ora('Searching for Stations').start() : undefined;
7
+ export default async (options) => {
8
+ const spinner = !options.raw && !options.dryRun ? ora('Searching for Stations').start() : undefined;
9
9
 
10
10
  const envVars = checkForRequired(["WEATHER_LINK_API_KEY", "WEATHER_LINK_API_SECRET", "WEATHER_LINK_BASE_API_URL"])
11
11
  if (!envVars.exist) {
12
- if (spinner) {
13
- spinner.fail('Failed to Retrieve Stations')
12
+ if (spinner) {
13
+ spinner.fail('Failed to Retrieve Stations');
14
14
  }
15
-
16
- return console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
15
+
16
+ console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
17
+ return;
17
18
  }
18
19
 
19
20
  const API_KEY = process.env.WEATHER_LINK_API_KEY;
20
21
 
21
- axios.get(
22
- buildWeatherLinkApiUrl(
23
- 'stations',
24
- { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) },
25
- { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) }
26
- )
27
- )
28
- .then((response) => {
29
-
30
- if (options.raw) {
31
- return console.log(JSON.stringify(response.data));
32
- }
33
-
34
- let stationIdList = [];
35
- response.data.stations.forEach(station => {
36
- stationIdList.push(station.station_id);
37
- });
38
-
39
- spinner.succeed(chalk.green.bold(`${stationIdList.length} Station(s) Found`));
40
- return console.log(stationIdList);
41
- })
42
- .catch((error) => {
43
- if (options.raw) {
44
- return console.log(JSON.stringify(error.response.data));
45
- }
46
-
47
- spinner.fail('Unable to Find Stations');
48
- return console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
49
- })
50
- };
22
+ const urlToQuery = buildWeatherLinkApiUrl(
23
+ 'stations',
24
+ { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) },
25
+ { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) }
26
+ );
27
+
28
+ if (options.dryRun) {
29
+ console.log(urlToQuery);
30
+ return;
31
+ }
32
+
33
+ try {
34
+ const response = await axios.get(urlToQuery);
35
+
36
+ if (options.raw) {
37
+ console.log(JSON.stringify(response.data));
38
+ return;
39
+ }
40
+
41
+ let stationIdList = [];
42
+ response.data.stations.forEach(station => {
43
+ stationIdList.push(station.station_id);
44
+ });
45
+
46
+ spinner.succeed(chalk.green.bold(`${stationIdList.length} Station(s) Found`));
47
+ console.dir(stationIdList, { depth: null });
48
+ return stationIdList;
49
+
50
+ } catch (error) {
51
+
52
+ if (options.raw) {
53
+ console.log(JSON.stringify(error.response.data));
54
+ throw (error);
55
+ }
56
+
57
+ spinner.fail('Unable to Find Stations');
58
+ console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
59
+ throw (error);
60
+ }
61
+ };
@@ -4,41 +4,53 @@ import ora from 'ora';
4
4
 
5
5
  import { buildWeatherLinkApiUrl, checkForRequired } from '../../lib/utils.js';
6
6
 
7
- export default (stationIds, options) => {
8
- const spinner = !options.raw ? ora('Searching for Stations').start() : undefined;
7
+ export default async (stationIds, options) => {
8
+ const spinner = !options.raw && !options.dryRun ? ora('Searching for Stations').start() : undefined;
9
9
 
10
10
  const envVars = checkForRequired(["WEATHER_LINK_API_KEY", "WEATHER_LINK_API_SECRET", "WEATHER_LINK_BASE_API_URL"])
11
11
  if (!envVars.exist) {
12
- if (spinner) {
13
- spinner.fail('Failed to Retrieve Stations')
12
+ if (spinner) {
13
+ spinner.fail('Failed to Retrieve Stations');
14
14
  }
15
15
 
16
- return console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
16
+ console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
17
+ return;
17
18
  }
18
19
 
19
20
  const API_KEY = process.env.WEATHER_LINK_API_KEY;
20
21
 
21
- axios.get(
22
- buildWeatherLinkApiUrl(
23
- `stations/${stationIds}`,
24
- { "api-key": API_KEY, "station-ids": String(stationIds), "t": String(Math.round(Date.now() / 1000)) },
25
- { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) }
26
- )
27
- )
28
- .then((response) => {
29
- if (options.raw) {
30
- return console.log(JSON.stringify(response.data));
31
- }
32
-
33
- spinner.succeed(chalk.green.bold(`${response.data.stations.length} Station(s) Found`));
34
- return console.log(response.data);
35
- })
36
- .catch((error) => {
37
- if (options.raw) {
38
- return console.log(JSON.stringify(error.response.data));
39
- }
40
-
41
- spinner.fail('Unable to Find Stations');
42
- return console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
43
- })
44
- };
22
+ const urlToQuery = buildWeatherLinkApiUrl(
23
+ `stations/${stationIds}`,
24
+ { "api-key": API_KEY, "station-ids": String(stationIds), "t": String(Math.round(Date.now() / 1000)) },
25
+ { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) }
26
+ );
27
+
28
+ if (options.dryRun) {
29
+ console.log(urlToQuery);
30
+ return;
31
+ }
32
+
33
+ try {
34
+ const response = await axios.get(urlToQuery);
35
+
36
+ if (options.raw) {
37
+ console.log(JSON.stringify(response.data));
38
+ return response;
39
+ }
40
+
41
+ spinner.succeed(chalk.green.bold(`${response.data.stations.length} Station(s) Found`));
42
+ console.dir(response.data, { depth: null });
43
+ return response;
44
+
45
+ } catch (error) {
46
+
47
+ if (options.raw) {
48
+ console.log(JSON.stringify(error.response.data));
49
+ throw (error);
50
+ }
51
+
52
+ spinner.fail('Unable to Find Stations');
53
+ console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
54
+ throw (error);
55
+ }
56
+ };
@@ -1,39 +1,44 @@
1
1
  import axios from 'axios';
2
2
 
3
- export default function(service, options) {
3
+ export default async function (service, _) {
4
4
 
5
5
  // Mapping of wlbot's shorthand for service names to Davis Instrument's official names.
6
6
  const fullServiceName = {
7
- 'api' : 'APIs and Data Feeds',
8
- 'dataingest' : 'Data Ingestion',
9
- 'mobile' : 'Mobile Applications',
10
- 'syscomms' : 'System Communication',
11
- 'website' : 'WeatherLink Website'
7
+ 'api': 'APIs and Data Feeds',
8
+ 'dataingest': 'Data Ingestion',
9
+ 'mobile': 'Mobile Applications',
10
+ 'syscomms': 'System Communication',
11
+ 'website': 'WeatherLink Website'
12
12
  };
13
13
 
14
- return (
15
- axios.get(
16
- 'https://0886445102835570.hostedstatus.com/1.0/status/600712dea9c1290530967bc6'
17
- )
18
- .then((response) => {
19
-
20
- var requestedDavisServices = service === 'all' ?
21
- response.data.result.status
22
- :
23
- response.data.result.status.filter(davisService => {
24
- return davisService.name === fullServiceName[service];
25
- });
26
-
27
- return requestedDavisServices;
28
-
29
- })
30
- .catch((error) => {
31
- return ({
32
- 'error': {
33
- 'msg': `${error.response.data.message}`,
34
- 'status': `${error.response.status}`
35
- }
14
+ try {
15
+ const response = await axios.get('https://0886445102835570.hostedstatus.com/1.0/status/600712dea9c1290530967bc6');
16
+
17
+ if (!Array.isArray(response.data.result.status)) {
18
+ throw ({ response: { data: { message: 'Hosted Status returned in an unexpected format.' }, status: 999 } })
19
+ }
20
+
21
+ var requestedDavisServices = service === 'all' ?
22
+ response.data.result.status
23
+ :
24
+ response.data.result.status.filter(davisService => {
25
+ return davisService.name === fullServiceName[service];
36
26
  });
37
- })
38
- );
39
- };
27
+
28
+ requestedDavisServices.forEach((requestedService) => {
29
+ console.log(requestedService.name + ' is ' + requestedService.status + ' (Status Code: ' + requestedService.status_code + ')');
30
+ });
31
+
32
+ return requestedDavisServices;
33
+
34
+ } catch (error) {
35
+ console.log('Error: ' + error);
36
+
37
+ return ({
38
+ 'error': {
39
+ 'msg': `${error.response.data.message}`,
40
+ 'status': `${error.response.status}`
41
+ }
42
+ });
43
+ }
44
+ };
@@ -4,41 +4,51 @@ import ora from 'ora';
4
4
 
5
5
  import { buildWeatherLinkApiUrl, checkForRequired } from '../../lib/utils.js';
6
6
 
7
- export default (stationId, options) => {
8
- const spinner = !options.raw ? ora('Retrieving Current Weather Data').start() : undefined;
7
+ export default async (stationId, options) => {
8
+ const spinner = !options.raw && !options.dryRun ? ora('Retrieving Current Weather Data').start() : undefined;
9
9
 
10
10
  const envVars = checkForRequired(["WEATHER_LINK_API_KEY", "WEATHER_LINK_API_SECRET", "WEATHER_LINK_BASE_API_URL"])
11
11
  if (!envVars.exist) {
12
- if (spinner) {
13
- spinner.fail('Failed to Retrieve Current Weather Data')
12
+ if (spinner) {
13
+ spinner.fail('Failed to Retrieve Current Weather Data');
14
14
  }
15
15
 
16
- return console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
16
+ console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
17
+ return;
17
18
  }
18
19
 
19
20
  const API_KEY = process.env.WEATHER_LINK_API_KEY;
20
21
 
21
- axios.get(
22
- buildWeatherLinkApiUrl(
23
- `current/${stationId}`,
24
- { "api-key": API_KEY, "station-id": String(stationId), "t": String(Math.round(Date.now() / 1000)) },
25
- { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) }
26
- )
27
- )
28
- .then((response) => {
29
- if (options.raw) {
30
- return console.log(JSON.stringify(response.data));
31
- }
32
-
33
- spinner.succeed(chalk.green.bold(`Current Weather Data Retrieved`));
34
- return console.dir(response.data, { depth: null })
35
- })
36
- .catch((error) => {
37
- if (options.raw) {
38
- return console.log(JSON.stringify(error.response.data));
39
- }
22
+ const urlToQuery = buildWeatherLinkApiUrl(
23
+ `current/${stationId}`,
24
+ { "api-key": API_KEY, "station-id": String(stationId), "t": String(Math.round(Date.now() / 1000)) },
25
+ { "api-key": API_KEY, "t": String(Math.round(Date.now() / 1000)) }
26
+ );
40
27
 
41
- spinner.fail('Failed to Retrieve Current Weather Data');
42
- return console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
43
- })
44
- };
28
+ if (options.dryRun) {
29
+ console.log(urlToQuery);
30
+ return;
31
+ }
32
+
33
+ try {
34
+ const response = await axios.get(urlToQuery);
35
+ if (options.raw) {
36
+ console.log(JSON.stringify(response.data));
37
+ return response;
38
+ }
39
+
40
+ spinner.succeed(chalk.green.bold(`Current Weather Data Retrieved`));
41
+ console.dir(response.data, { depth: null });
42
+ return response;
43
+
44
+ } catch (error) {
45
+ if (options.raw) {
46
+ console.log(JSON.stringify(error.response.data));
47
+ throw error;
48
+ }
49
+
50
+ spinner.fail('Failed to Retrieve Current Weather Data');
51
+ console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
52
+ throw error;
53
+ }
54
+ };
@@ -4,47 +4,63 @@ import ora from 'ora';
4
4
 
5
5
  import { buildWeatherLinkApiUrl, checkForRequired, dateRangeIsValid } from '../../lib/utils.js';
6
6
 
7
- export default (stationId, startTimestamp, endTimestamp, options) => {
8
- const spinner = !options.raw ? ora('Retrieving Historical Weather Data').start() : undefined;
7
+ export default async (stationId, startTimestamp, endTimestamp, options) => {
8
+ const spinner = !options.raw && !options.dryRun ? ora('Retrieving Historical Weather Data').start() : undefined;
9
9
 
10
10
  const envVars = checkForRequired(["WEATHER_LINK_API_KEY", "WEATHER_LINK_API_SECRET", "WEATHER_LINK_BASE_API_URL"])
11
11
  if (!envVars.exist) {
12
- if (spinner) {
13
- spinner.fail('Failed to Retrieve Historic Weather Data')
12
+ if (spinner) {
13
+ spinner.fail('Failed to Retrieve Historic Weather Data');
14
14
  }
15
15
 
16
- return console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
16
+ console.log(`${chalk.red.bold(`Missing Environment Variable(s):`)} ${envVars.missing.join(", ")}`);
17
+ return;
17
18
  }
18
19
 
19
20
  const resultsOfDateRangeCheck = dateRangeIsValid(startTimestamp, endTimestamp);
20
21
 
21
22
  if (!resultsOfDateRangeCheck.isValid) {
22
- return console.log(`${chalk.red.bold(`Error:`)} ${resultsOfDateRangeCheck.msg}`);
23
+ if (spinner) {
24
+ spinner.fail(`${resultsOfDateRangeCheck.msg}`);
25
+ }
26
+
27
+ console.log(`${chalk.red.bold(`Error:`)} ${resultsOfDateRangeCheck.msg}`);
28
+ return;
23
29
  }
24
30
 
25
31
  const API_KEY = process.env.WEATHER_LINK_API_KEY;
26
32
 
27
- axios.get(
28
- buildWeatherLinkApiUrl(
29
- `historic/${stationId}`,
30
- { "api-key": API_KEY, "end-timestamp": endTimestamp, "start-timestamp": startTimestamp, "station-id": String(stationId), "t": String(Math.round(Date.now() / 1000)) },
31
- { "api-key": API_KEY, "end-timestamp": endTimestamp, "start-timestamp": startTimestamp, "t": String(Math.round(Date.now() / 1000)) }
32
- )
33
- )
34
- .then((response) => {
35
- if (options.raw) {
36
- return console.log(JSON.stringify(response.data));
37
- }
38
-
39
- spinner.succeed(chalk.green.bold(`Historical Weather Data Retrieved`));
40
- return console.dir(response.data, { depth: null })
41
- })
42
- .catch((error) => {
43
- if (options.raw) {
44
- return console.log(JSON.stringify(error.response.data));
45
- }
46
-
47
- spinner.fail('Failed to Retrieve Historical Weather Data');
48
- return console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
49
- })
50
- };
33
+ const urlToQuery = buildWeatherLinkApiUrl(
34
+ `historic/${stationId}`,
35
+ { "api-key": API_KEY, "end-timestamp": endTimestamp, "start-timestamp": startTimestamp, "station-id": String(stationId), "t": String(Math.round(Date.now() / 1000)) },
36
+ { "api-key": API_KEY, "end-timestamp": endTimestamp, "start-timestamp": startTimestamp, "t": String(Math.round(Date.now() / 1000)) }
37
+ );
38
+
39
+ if (options.dryRun) {
40
+ console.log(urlToQuery);
41
+ return
42
+ }
43
+
44
+ try {
45
+ const response = await axios.get(urlToQuery);
46
+
47
+ if (options.raw) {
48
+ console.log(JSON.stringify(response.data));
49
+ return response;
50
+ }
51
+
52
+ spinner.succeed(chalk.green.bold(`Historical Weather Data Retrieved`));
53
+ console.dir(response.data, { depth: null });
54
+ return response;
55
+
56
+ } catch (error) {
57
+ if (options.raw) {
58
+ console.log(JSON.stringify(error.response.data));
59
+ throw error;
60
+ }
61
+
62
+ spinner.fail('Failed to Retrieve Historical Weather Data');
63
+ console.log(`${chalk.red.bold(`Error ${error.response.status}:`)} ${error.response.data.message}`);
64
+ throw error;
65
+ }
66
+ };
package/index.js CHANGED
@@ -18,7 +18,7 @@ const program = new Command();
18
18
  program
19
19
  .description("A CLI for the WeatherLink Live API.")
20
20
  .name("wlbot")
21
- .version('1.1.3')
21
+ .version('1.1.5')
22
22
  .usage('<command>');
23
23
 
24
24
  const metadata = program.command("metadata")
@@ -26,17 +26,20 @@ const metadata = program.command("metadata")
26
26
 
27
27
  metadata.command("catalog")
28
28
  .description("Get a catalog of all available sensor types and the data reported by each sensor.")
29
+ .option("-d, --dry-run", "Checks for the necessary environmental variables and outputs the URL that would be queried.")
29
30
  .option("-r, --raw", "Display the raw response from the WeatherLink API.")
30
31
  .action(catalog);
31
32
 
32
33
  metadata.command("mine")
33
34
  .description("Returns an array of Weather Station Id(s) that are associated with your WeatherLink API Key.")
35
+ .option("-d, --dry-run", "Checks for the necessary environmental variables and outputs the URL that would be queried.")
34
36
  .option("-r, --raw", "Display the raw response from the WeatherLink API.")
35
37
  .action(mine);
36
38
 
37
39
  metadata.command("stations")
38
40
  .description("Returns all available information about 1 or more weather stations associated with your WeatherLink API Key.")
39
41
  .argument('<station-ids>', 'A comma-separated list of Weather Station Id(s) that you want information about.')
42
+ .option("-d, --dry-run", "Checks for the necessary environmental variables and outputs the URL that would be queried.")
40
43
  .option("-r, --raw", "Display the raw response from the WeatherLink API.")
41
44
  .action(stations);
42
45
 
@@ -46,6 +49,7 @@ const weather = program.command("weather")
46
49
  weather.command("current")
47
50
  .description("Get the current weather data for 1 weather station associated with your WeatherLink API Key.")
48
51
  .argument('<station-id>', 'The Station ID of the weather station that you want current weather data for.')
52
+ .option("-d, --dry-run", "Checks for the necessary environmental variables and outputs the URL that would be queried.")
49
53
  .option("-r, --raw", "Display the raw response from the WeatherLink API.")
50
54
  .action(current);
51
55
 
@@ -54,6 +58,7 @@ weather.command("historic")
54
58
  .argument('<station-id>', 'The Station ID of the weather station that you want current weather data for.')
55
59
  .argument('<start-timestamp>', 'A Unix timestamp marking the beginning of the historical period (must be earlier than end-timestamp but not more than 24 hours earlier).')
56
60
  .argument('<end-timestamp>', 'A Unix timestamp marking the end of the historical period (must be later than start-timestamp but not more than 24 hours later).')
61
+ .option("-d, --dry-run", "Checks for the necessary environmental variables and outputs the URL that would be queried.")
57
62
  .option("-r, --raw", "Display the raw response from the WeatherLink API.")
58
63
  .action(historic);
59
64
 
@@ -65,17 +70,7 @@ program.command("config")
65
70
  program.command("status")
66
71
  .description("Retrieves the operational status(es) of Davis Instrument's services.")
67
72
  .addArgument(new Argument('[service]', 'The Davis Instrument service that you want to obtain the operational status of.').choices(['all', 'api', 'dataingest', 'mobile', 'syscomms', 'website']).default('all'))
68
- .action((service, options) => {
69
- status(service, options).then((result) => {
70
- if (!Array.isArray(result)) {
71
- console.log('Error: ' + result.error.msg);
72
- } else {
73
- result.forEach((requestedService) => {
74
- console.log(requestedService.name + ' is ' + requestedService.status + ' (Status Code: ' + requestedService.status_code + ')');
75
- });
76
- }
77
- });
78
- });
73
+ .action(status);
79
74
 
80
75
  program.commands.sort((a, b) => a._name.localeCompare(b._name));
81
- program.parse(process.argv);
76
+ program.parse(process.argv);
package/lib/utils.js CHANGED
@@ -14,7 +14,7 @@ export function buildWeatherLinkApiUrl(endpoint, signature_parameters, uri_param
14
14
 
15
15
  var apiSignature = "";
16
16
  for (const key in signature_parameters) {
17
- apiSignature = apiSignature + key + signature_parameters[key]
17
+ apiSignature = apiSignature + key + signature_parameters[key];
18
18
  }
19
19
 
20
20
  var hmac = crypto.createHmac('sha256', API_SECRET);
@@ -28,7 +28,7 @@ export function buildWeatherLinkApiUrl(endpoint, signature_parameters, uri_param
28
28
  apiRequestURL = apiRequestURL + "&" + key + "=" + uri_parameters[key];
29
29
  }
30
30
  });
31
-
31
+
32
32
  apiRequestURL = apiRequestURL + "&api-signature=" + hashedData;
33
33
  return apiRequestURL;
34
34
  }
@@ -43,16 +43,16 @@ export function checkForRequired(envVars) {
43
43
  let missingEnvVars = []
44
44
 
45
45
  for (const envVar of envVars) {
46
- if (!process.env[envVar]){
47
- missingEnvVars.push(envVar)
46
+ if (!process.env[envVar]) {
47
+ missingEnvVars.push(envVar);
48
48
  }
49
49
  }
50
50
 
51
51
  if (missingEnvVars.length != 0) {
52
- return {"exist": false, "missing": missingEnvVars};
52
+ return { "exist": false, "missing": missingEnvVars };
53
53
  }
54
54
 
55
- return {"exist": true, "missing": missingEnvVars};
55
+ return { "exist": true, "missing": missingEnvVars };
56
56
  }
57
57
 
58
58
  /**
@@ -63,12 +63,12 @@ export function checkForRequired(envVars) {
63
63
  */
64
64
  export function dateRangeIsValid(startTimestamp, endTimestamp) {
65
65
  if (startTimestamp <= 0 || endTimestamp <= 0) {
66
- return {isValid: false, msg:"start-timestamp and end-timestamp must be greater than 0."};
66
+ return { isValid: false, msg: "start-timestamp and end-timestamp must be greater than 0." };
67
67
  }
68
-
68
+
69
69
  if (startTimestamp >= endTimestamp) {
70
- return {isValid: false, msg:"start-timestamp must be less than end-timestamp."};
70
+ return { isValid: false, msg: "start-timestamp must be less than end-timestamp." };
71
71
  }
72
72
 
73
- return {isValid: true, msg:""};
74
- }
73
+ return { isValid: true, msg: "" };
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wlbot",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "A CLI for the WeatherLink Live API.",
5
5
  "keywords": [
6
6
  "Davis Instruments",
@@ -25,24 +25,26 @@
25
25
  "type": "module",
26
26
  "main": "./index.js",
27
27
  "bin": {
28
- "wlbot": "./index.js"
28
+ "wlbot": "index.js"
29
29
  },
30
30
  "repository": {
31
31
  "type": "git",
32
- "url": "https://github.com/mike-weiner/wlbot.git"
32
+ "url": "git+https://github.com/mike-weiner/wlbot.git"
33
33
  },
34
34
  "scripts": {
35
- "test": "mocha"
35
+ "pack:dry": "npm pack --dry-run",
36
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
36
37
  },
37
38
  "dependencies": {
38
- "axios": "^0.27.2",
39
- "chalk": "^5.0.1",
40
- "commander": "^9.4.0",
41
- "ora": "^6.1.2"
39
+ "axios": "^1.6.3",
40
+ "chalk": "^5.3.0",
41
+ "commander": "^12.0.0",
42
+ "ora": "^8.0.1"
42
43
  },
43
44
  "devDependencies": {
44
- "chai": "^4.3.6",
45
- "mocha": "^10.0.0"
45
+ "@babel/preset-env": "^7.23.7",
46
+ "@types/jest": "^29.5.11",
47
+ "jest": "^29.7.0"
46
48
  },
47
49
  "engines": {
48
50
  "node": ">=16"
@@ -1,108 +0,0 @@
1
- import { buildWeatherLinkApiUrl, checkForRequired, dateRangeIsValid } from '../lib/utils.js';
2
- import chai from 'chai';
3
-
4
- describe('buildWeatherLinkApiUrl', () => {
5
- beforeEach(() => {
6
- // Set the environment variables
7
- process.env.WEATHER_LINK_API_KEY = 'sampleKey';
8
- process.env.WEATHER_LINK_API_SECRET = 'sampleSecret';
9
- process.env.WEATHER_LINK_BASE_API_URL = 'https://api.weatherlink.com/v2/';
10
- });
11
-
12
- it('Endpoint: current/13', () => {
13
- chai.expect(
14
- buildWeatherLinkApiUrl(
15
- `current/13`,
16
- {'api-key': process.env.WEATHER_LINK_API_KEY, 'station-id': String(13), 't': String(1660759469)},
17
- {'api-key': process.env.WEATHER_LINK_API_KEY, 't': String(1660759469)}
18
- )
19
- ).to.equal('https://api.weatherlink.com/v2/current/13?api-key=sampleKey&t=1660759469&api-signature=dcf2f0eafc43ec8b6d6a1a7acfc6faa44f9bc1045124d7d6416f2f486ff72e95')
20
- });
21
-
22
- it('Endpoint: stations', () => {
23
- chai.expect(
24
- buildWeatherLinkApiUrl(
25
- 'stations',
26
- {'api-key': process.env.WEATHER_LINK_API_KEY, 't': String(1660757289)},
27
- {'api-key': process.env.WEATHER_LINK_API_KEY, 't': String(1660757289)}
28
- )
29
- ).to.equal('https://api.weatherlink.com/v2/stations?api-key=sampleKey&t=1660757289&api-signature=1663a50336ae4b7d975e322ad010e297d4ec487c5d3d9d4dad1d8a4a9e53d606')
30
- });
31
-
32
- it('Endpoint: stations/13,14,15', () => {
33
- chai.expect(
34
- buildWeatherLinkApiUrl(
35
- `stations/13,14,15`,
36
- {'api-key': process.env.WEATHER_LINK_API_KEY, 'station-ids': '13,14,15', 't': String(1660759608)},
37
- {'api-key': process.env.WEATHER_LINK_API_KEY, 't': String(1660759608)}
38
- )
39
- ).to.equal('https://api.weatherlink.com/v2/stations/13,14,15?api-key=sampleKey&t=1660759608&api-signature=7accbcf70808449f944e5da9e3e06db0aae7362870e7ace8312d5d9e8103d274')
40
- });
41
- });
42
-
43
- describe('checkForRequired', () => {
44
-
45
- beforeEach(() => {
46
- // Set the environment variables
47
- process.env.WEATHER_LINK_API_KEY = 'sampleKey';
48
- process.env.WEATHER_LINK_API_SECRET = 'sampleSecret';
49
- process.env.WEATHER_LINK_BASE_API_URL = 'https://api.weatherlink.com/v2/';
50
- });
51
-
52
- it('Missing All Env Vars', () => {
53
- chai.expect(
54
- checkForRequired(["WEATHER_LINK_VERY_FAKE_NONEXISTENT"])
55
- ).to.deep.equal({"exist": false, "missing": ["WEATHER_LINK_VERY_FAKE_NONEXISTENT"]})
56
- });
57
-
58
- it('All Env Vars Defined', () => {
59
- chai.expect(
60
- checkForRequired(["WEATHER_LINK_API_KEY", "WEATHER_LINK_API_SECRET", "WEATHER_LINK_BASE_API_URL"])
61
- ).to.deep.equal({"exist": true, "missing": []})
62
- });
63
- });
64
-
65
- describe('dateRangeIsValid', () => {
66
-
67
- it('Start Timestamp is Before End Timestamp', () => {
68
- chai.expect(
69
- dateRangeIsValid(1673472399, 1673494000).isValid
70
- ).to.equal(true)
71
- });
72
-
73
- it('Start Timestamp is the Same as End Timestamp', () => {
74
- chai.expect(
75
- dateRangeIsValid(1673494000, 1673494000).isValid
76
- ).to.equal(false)
77
- });
78
-
79
- it('Start Timestamp is After End Timestamp', () => {
80
- chai.expect(
81
- dateRangeIsValid(1673494010, 1673494000).isValid
82
- ).to.equal(false)
83
- });
84
-
85
- it('Start Timestamp is 0', () => {
86
- chai.expect(
87
- dateRangeIsValid(0, 1673494000).isValid
88
- ).to.equal(false)
89
- });
90
-
91
- it('Start Timestamp is Negative', () => {
92
- chai.expect(
93
- dateRangeIsValid(-10, 1673494000).isValid
94
- ).to.equal(false)
95
- });
96
-
97
- it('End Timestamp is 0', () => {
98
- chai.expect(
99
- dateRangeIsValid(1673494010, 0).isValid
100
- ).to.equal(false)
101
- });
102
-
103
- it('End Timestamp is Negative', () => {
104
- chai.expect(
105
- dateRangeIsValid(1673494010, -10).isValid
106
- ).to.equal(false)
107
- });
108
- });