Description: CLI for getting weather
- Инициализация репозитория Git в проекте.
- Добавление изменений и выполнение первого коммита.
- Переименование основной ветки в main.
- Подключение внешнего репозитория origin.
- Отправить во внешний репозиторий origin ветку main.
git init
git add .
git commit -m "Create NodeJS BIN-Weather Project with README.md + package.json"
git branch -M main
git remote add origin https://github.com/biouri/bin-weather.git
git push -u origin maingit commit -m "Configure package.json + Parsing command line arguments: args.js"
git commit -m "Add Log Service + Console Output + chalk + dedent-js"
git commit -m "Add OS + path + Example storage-service.js"
git commit -m "Add File system operations with async promises from fs"
git commit -m "Add API Service Example api-service.js"
git commit -m "Add Environment Variables process.env"
git commit -m "Add Error Handling in getForcast"
git commit -m "Add Global Installation + npm publish + npx + npm exec"- Приложение на Node.js.
- Установка как глобальный npm-пакет.
- Общий вид приложения с параметрами запуска.
- Функционал приложения
- Возможность запуска без параметров для получения погоды.
Команды для настройки:
Без параметров: вывод текущей погоды.--help: информация и подсказки по командам.-s <город>: выбор и валидация города.-t <токен>: токен для API погоды.
- Ключевые компоненты приложения
- Получение аргументов: обработка команд пользователя.
- Работа с файловой системой: сохранение настроек и токена.
- Интеграция с внешним API (
OpenWeather) для получения данных о погоде. - Стилизация вывода в консоль: визуальное оформление информации.
- Использование библиотеки OS из стандарта
Node.js.
- Интеграция с API
OpenWeather
- Бесплатный тариф: до 60 запросов в минуту и 1 миллион запросов в месяц.
- Возможности масштабирования приложения.
Основной файл - weather.js.
Обновим package.json для указания точки входа и настройки бинарного вызова.
- Указываем
mainфайл:weather.js(как точка входа приложения). - Настроим бинарник:
- Название:
weather. - Файл:
weather.js.
...
"main": "weather.js",
"bin": {
"weather": "weather.js"
},
"type": "module",
...Такая настройка bin-секции означает, что при установке приложения глобально будет активна команда weather по которой запустится weather.js (в этом случае нужно добавить в начале файла weather.js строку #! /usr/bin/env node). Можно также указать "weather": "node weather.js". Вместо common.js модулей будем использовать импорт ES6 модулей, для этого добавляем в конфигурацию: "type": "module", без этой строки необходимо называть файлы-модули с расширением *.mjs.
Добавляем в начале файла weather.js строку #! /usr/bin/env node для указания окружения выполнения.
- Инициализация приложения
- Вывод стартового сообщения в консоль.
// Функция, которую будем вызывать в рамках запуска CLI
const initCLI = () => {
// process - глобальная переменная с информацией о процессе
// process. просмотр доступных методов и переменных
console.log(process.argv);
const args = getArgs(process.argv);
console.log(args);
// Вывести погоду
console.log("Weather APP");
};
initCLI();- Разбор аргументов командной строки с использованием
process.argv.
- Аргументы передаются в виде массива. Например, первый аргумент: чем выполняется, второй аргумент: точка входа.
node weather.js -h -s Moscow -t AB4434553450982340598[
'C:\\Program Files\\nodejs\\node.exe',
'D:\\Projects\\node_abc\\bin-weather\\weather.js',
'-h',
'-s',
'Moscow',
'-t',
'AB4434553450982340598'
]
- Обработка и вывод дополнительных аргументов.
- Цель: упростить аргументы в объект для легкой работы.
- Создание хелпера в папке
helpersдля разбора аргументов (args.js).
const getArgs = (args) => {
const res = {};
// Игнорировать первые два аргумента
// Используется rest синтаксис для массивов
// rest это массив без первых 2-х аргументов
const [executer, file, ...rest] = args;
rest.forEach((value, index, array) => {
if (value.charAt(0) == "-") {
if (index == array.length - 1) {
// Это последний элемент, true = значит мы его нашли
res[value.substring(1)] = true;
} else if (array[index + 1].charAt(0) != "-") {
// Значение элемента берем из следующего элемента
res[value.substring(1)] = array[index + 1];
} else {
// Это элемент за которым есть параметр с дефисом -
res[value.substring(1)] = true;
}
}
});
return res;
};
export { getArgs };- Пропускаем первые два аргумента массива (исполняемый файл и файл скрипта).
- Обрабатываем остальные аргументы:
- Определение ключей и значений.
- Обработка ключей с
-. - Присвоение
trueдля одиночных флагов.
weather.js
#!/usr/bin/env node
import { getArgs } from "./helpers/args.js";
// Функция, которую будем вызывать в рамках запуска CLI
const initCLI = () => {
// process - глобальная переменная с информацией о процессе
// process. просмотр доступных методов и переменных
console.log(process.argv);
const args = getArgs(process.argv);
console.log(args);
if (args.h) {
// Вывод help
}
if (args.s) {
// Сохранить город
}
if (args.t) {
// Сохранить токен
}
// Вывести погоду
console.log("Weather APP");
};
initCLI();node weather.js -h -s Moscow -t AB4434553450982340598{ h: true, s: 'Moscow', t: 'AB4434553450982340598' }
- Получаем объект с аргументами для удобной работы.
- Можно использовать для выполнения соответствующих команд в приложении (например, вывод помощи, сохранение города, токена).
- Была реализована простая система разбора аргументов CLI без использования сторонних библиотек.
- Для продвинутой работы с аргументами рекомендуется использовать готовые решения, например, библиотеку
yargs.
Цель: создать отдельный сервис для стилизованного вывода в консоль.
- Создать папку
services. - В папке создать файл
log-service.js.
Методы:
printError(text): выводит сообщение об ошибке.printSuccess(message): выводит успешное сообщение.printHelp(): выводит справку по командам.
- Установка:
npm install chalk. - Позволяет стилизовать текст в консоли (цвета, фон).
- Примеры:
bg-redдля ошибок,bg-greenдля успеха,bg-cyanдля справки.
package.json
"dependencies": {
"axios": "^1.8.4",
"chalk": "^5.4.1"
}
Common.jsvsES модули:Chalk v4.xявляется библиотекойCommon.js, в этом случае импорт должен быть выполнен соответствующим образом.- Отступы в многострочных строках: Для устранения нежелательных отступов используется библиотека
dedent-js.
- Установка:
npm install dedent-js. - Применение: оборачивание строки функцией для удаления отступов.
services\log-service.js
// До пятой версии библиотеки chalk использовался CommonJS импорт
// const chalk = require('chalk');
// ES6 импорт для chalk > v.5.0.0 (CommonJS больше не используется)
import chalk from "chalk";
import dedent from "dedent-js";
// Методы для вывода в консоль
// Вывод в консоль сообщения об ошибке
const printError = (error) => {
console.log(chalk.bgRed(" ERROR ") + " " + error);
};
// Стандартный вывод сообщения
const printSuccess = (message) => {
console.log(chalk.bgGreen(" SUCCESS ") + " " + message);
};
// Подсказка
const printHelp = () => {
// Используется краткая запись передачи строки в фукнцию dedent()
// dedent`...` можно применять чтобы убрать лишние отступы
console.log(
dedent`${chalk.bgCyan(" HELP ")}
Без параметров - вывод погоды
-s [CITY] для установки города
-h для вывода помощи
-t [API_KEY] для сохранения токена
`
);
};
export { printError, printSuccess, printHelp };bin-weather\weather.js
import { printHelp } from './services/log-service.js';
...
if (args.h) {
// Вывод help
printHelp();
}Библиотека Chalk v5.0.0 была официально выпущена 18 июня 2022 года.
- Полный отказ от CommonJS:
require('chalk')больше не работает. - Chalk 5 стал только ESM (ES Modules).
- Улучшения производительности и типизации (особенно для TypeScript).
- Чистый минимализм: удалено всё лишнее, упор только на ESM и core-функции.
✅ Поддержка import:
Используется современный синтаксис:
import chalk from "chalk";Необходимо использовать Chalk v4.x, например:
npm install chalk@4Тогда можно использовать:
const chalk = require("chalk");
console.log(chalk.green("CommonJS still works in v4!"));✅ Правильный способ использовать Chalk 5:
Chalk использует цепочки стилей. Вместо прямых импортов нужно использовать методы объекта chalk.
import chalk from "chalk";
console.log(chalk.bgCyan("Фон Cyan"));❌ Неправильный способ использовать Chalk 5:
// ❌ Неправильно: bgCyan не экспортируется как отдельный модуль
import chalk, { bgCyan } from "chalk";Почему bgCyan нельзя импортировать в фигурных скобках? Потому что Chalk не экспортирует bgCyan как отдельный элемент. Весь API Chalk реализован как один объект, экспортированный по умолчанию:
// Псевдокод внутренностей chalk:
export default {
bgCyan: function() { ... },
red: function() { ... },
bold: { ... },
// и т.д.
}Т.е. bgCyan — это свойство объекта, а не отдельный экспорт, поэтому возникает ошибка:
SyntaxError: The requested module 'chalk' does not provide an export named 'bgCyan'import chalk from "chalk";
console.log(chalk.green("Зелёный текст"));
console.log(chalk.red.bold("Жирный красный"));
console.log(chalk.blue.italic("Синий курсив"));
console.log(chalk.bgYellow.black("Чёрный текст на жёлтом фоне"));
// 🟢 Цвета текста
chalk.red("Ошибка");
chalk.green("Успешно");
chalk.blue("Информация");
chalk.yellow("Предупреждение");
chalk.cyan("Лог");
chalk.white("Обычный текст");
chalk.gray("Заметка");
// 🎨 Цвета фона
chalk.bgRed("Фон красный");
chalk.bgGreen("Фон зелёный");
chalk.bgBlue("Фон синий");
chalk.bgYellow("Фон жёлтый");
chalk.bgCyan("Фон голубой");
chalk.bgWhite("Фон белый");
// 💅 Стили текста
chalk.bold("Жирный");
chalk.italic("Курсив");
chalk.underline("Подчёркнутый");
chalk.strikethrough("Зачёркнутый");
// 🔗 Комбинирование
console.log(chalk.bgGreen.black.bold(" Успешно "));
console.log(chalk.red.underline("Ошибка!"));
console.log(chalk.yellow.bold.italic("Внимание"));
// 🧠 Множественные строки
// Chalk можно применять к каждой строке отдельно:
console.log(`
${chalk.green("✓")} Всё готово!
${chalk.yellow("⚠")} Осторожно!
${chalk.red("✗")} Ошибка!
`);
// Chalk работает с ESM (import chalk from 'chalk')
// Chalk 5 не поддерживает require — только import
// Можно применять стили к переменным:
const success = chalk.green("Успешно");
console.log(success);Подготовка сервиса для работы с хранилищем данных.
Обзор работы с библиотеками Path и OS в Node.js.
-
Выбор места хранения: Данные будем хранить в домашней директории пользователя в виде JSON-файла. Это позволяет избежать проблем с обновлением и запуском приложения из разных мест. Многие приложения размещают свои конфигурационные файлы в домашней директории пользователя.
-
Создание сервиса хранения: Создаем
storage-service.jsс методомsaveKeyValueдля универсального сохранения данных по ключу. -
Использование библиотеки
OS: Для работы с путями и директориями в разных операционных системах используем стандартную библиотекуOS, что упрощает получение домашней директории пользователя. -
Применение библиотеки
Path:
Join: Объединение частей пути в один, учитывая особенности операционной системы.BaseName,DirName,ExtName: Получение имени файла, директории и расширения файла.Relative: Определение относительного пути между двумя путями.isAbsolute: Проверка на абсолютный путь.Resolve: Разрешение абсолютного пути относительно текущего местоположения.sep: Получение системного разделителя путей.
Подготовка services\storage-service.js и тестирование стандартных библиотек.
// Стандартная библиотека OS может использоваться для получения домашнего каталога
import { homedir } from "os";
// join - объединение частей пути, учитывая особенности операционной системы
// basename - получение имени файла или вложения последней папки
// dirname - директория, где находится указанный путь
// extname - расширение файла
// Какой путь нам нужен относительно одного и второго
// relative - определение относительного пути между двумя путями
// isAbsolute - проверка на абсолютный путь
// resolve - разрешение абсолютного пути относительно текущего местоположения
// sep - получение системного разделителя путей (separator)
import {
join,
basename,
dirname,
extname,
relative,
isAbsolute,
resolve,
sep,
} from "path";
// join() использует особенности ОС при конкатенации
// join() умеет корректно обрабатывать переходы в каталоги '../weather-data.json'
const filePath = join(homedir(), "weather-data.json");
// Универсальный метод сохранения "ключ: значение"
const saveKeyValue = (key, value) => {
// Тестирование функций из стандартных библиотек "os" и "path"
// Для разных ОС homedir() и др. будут отличаться
// Пример для Windows:
// filePath: C:\Users\belok\weather-data.json
// homedir(): C:\Users\belok
// basename(): weather-data.json
// dirname(): C:\Users\belok
// extname(): .json
// relative(): ..
// isAbsolute(): true
// resolve(): D:\Projects\node_abc
// sep: \
console.log(`filePath: ${filePath}`);
console.log(`homedir(): ${homedir()}`);
console.log(`basename(): ${basename(filePath)}`);
console.log(`dirname(): ${dirname(filePath)}`);
console.log(`extname(): ${extname(filePath)}`);
console.log(`relative(): ${relative(filePath, dirname(filePath))}`);
console.log(`isAbsolute(): ${isAbsolute(filePath)}`);
console.log(`resolve(): ${resolve("..")}`);
console.log(`sep: ${sep}`);
// Код для сохранения данных ...
};
export { saveKeyValue };Разработка функции saveKeyValue для сохранения параметров (например, токен или город) с использованием файловой системы.
Модуль fs в Node.js для работы с файловой системой.
writeFileSync: Блокирующий (синхронный) метод.writeFile: Асинхронный метод на основе колбэков.- Предпочтительный метод: Использование
fs.promisesдля работы с промисами, избегая"callback hell".
- Создание базового объекта для хранения пар ключ-значение.
- Преобразование JavaScript объекта в строку JSON для сохранения файла с помощью
JSON.stringify. - Использование асинхронного метода
fs.promises.writeFileдля записи файла. - Проверка существования файла перед записью, чтение и обновление содержимого если файл существует.
exist: Проверка наличия файла.getKey: Извлечение значения по ключу из сохранённого файла.
- Простая обработка ошибок с акцентом на верхнеуровневые обработчики.
- Избежание включения логики вывода в консоль в сервис работы с файловой системой для разделения ответственности.
Создание отдельных функций для сохранения токена и других параметров с обработкой ошибок и информированием пользователя о результате с помощью printSuccess и printError.
services\storage-service.js
// Стандартная библиотека OS может использоваться для получения домашнего каталога
import { homedir } from "os";
import {
join,
basename,
dirname,
extname,
relative,
isAbsolute,
resolve,
sep,
} from "path";
// Чтение/Запись файлов (в данном случае используем promises)
// writeFileSync - синхронно записать данные в файл (используется редко)
// writeFile - асинхронная запись данных в файл
// promises - современный метод для получения информации об ОС, читать, записывать.
import { promises } from "fs";
// join() использует особенности ОС при конкатенации
const filePath = join(homedir(), "weather-data.json");
// Универсальный метод сохранения "ключ: значение"
// В этом методе нет обработки ошибок
const saveKeyValue = async (key, value) => {
// Код для сохранения данных ...
let data = {};
// Проверить наличие файла и загрузить все данные (включая другие ключи)
if (await isExist(filePath)) {
const file = await promises.readFile(filePath);
data = JSON.parse(file);
}
// Добавить или модифицировать ключ
data[key] = value;
// Преобразование JavaScript объекта в строку JSON и сохранение
// Если файл отсутствует, он будет создан
await promises.writeFile(filePath, JSON.stringify(data));
};
const getKeyValue = async (key) => {
if (await isExist(filePath)) {
const file = await promises.readFile(filePath);
const data = JSON.parse(file);
return data[key];
}
// Если нет данных, возвращаем undefined
return undefined;
};
// Проверка наличия файла
const isExist = async (path) => {
try {
// stat(путь) возвращает статистику по файлу
// если файла нет, то статистика отсутствует и возникает исключение
await promises.stat(path);
return true;
} catch (e) {
return false;
}
};
export { saveKeyValue, getKeyValue };bin-weather\weather.js
#!/usr/bin/env node
import { getArgs } from "./helpers/args.js";
import { printHelp, printSuccess, printError } from "./services/log-service.js";
import { saveKeyValue } from "./services/storage-service.js";
// Обработка ошибок try-catch на уровне изолированной функции сохранения
const saveToken = async (token) => {
try {
await saveKeyValue("token", token);
printSuccess("Токен сохранён");
} catch (e) {
printError(e.message);
}
};
// Функция, которую будем вызывать в рамках запуска CLI
// Можно условно сказать, что в данной функции выполняется роутинг
// Для каждого роута (условия) есть своя изолированная функция-обработчик
const initCLI = () => {
// process - глобальная переменная с информацией о процессе
// process. просмотр доступных методов и переменных
console.log(process.argv);
const args = getArgs(process.argv);
console.log(args);
if (args.h) {
// Вывод help
printHelp();
}
if (args.s) {
// Сохранить город
}
if (args.t) {
// Сохранить токен
// Вызов отдельной изолированной функции (с обработкой ошибок)
return saveToken(args.t);
}
// Вывести погоду
console.log("Weather APP");
};
initCLI();-
Получение API-ключа на сайте:
openweathermap.org. Зарегистрируйтесь и подтвердите электронную почту. Получите ключ API в личном кабинете. -
Лимиты и тарифы: 60 запросов в минуту, 1 млн в месяц бесплатно.
-
Варианты использования API: Текущая погода, прогноз на 4 и 30 дней, массовые скачивания.
-
Создание запроса: Основные параметры:
Q(город),AppID(ключ API),Mode(формат ответа, предпочтительно JSON),Units(единицы измерения, метрическая система),Language(язык ответа).
// Пример URL для запроса в API
const url = await axios.get(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}`
);- Разработка сервиса для запросов:
Создание функции
getWeatherдля получения погоды по городу. Реализация через стандартную библиотеку HTTPS и использование Axios для упрощения запросов.
import https from "https";
import { getKeyValue, TOKEN_DICTIONARY } from "./storage-service.js";
// Классический способ выполнения запроса при помощи https используется редко
const getWeatherHTTPS = async (city) => {
// Хорошо бы обернуть весь код ниже в Promise и использовать resolve/reject
// Но это все не очень удобно и сложно
// return new Promise(...);
// Ниже код, который желательно оборачивать в Promise
const token = await getKeyValue(TOKEN_DICTIONARY.token);
if (!token) {
throw new Error(
"Не задан ключ API, задайте его через команду -t [API_KEY]"
);
}
// Формирование URL с добавлением параметров
const url = new URL("https://api.openweathermap.org/data/2.5/weather");
url.searchParams.append("q", city);
url.searchParams.append("appid", token);
url.searchParams.append("lang", "ru");
url.searchParams.append("units", "metric");
// Запрос и получение результата response
https.get(url, (response) => {
let res = "";
// Подписаться на получение данных
// Обычно кусочек получаемых данных называют chunk
response.on("data", (chunk) => {
res += chunk;
});
// Подписка на завершение
// Вывести результат
response.on("end", () => {
console.log(`Ответ строка: ${res}`);
// Парсим строку в объект
// JSON.parse(jsonString) — превращает строку в объект.
// JSON.stringify(..., null, 2) — превращает объект обратно в строку, но с отступами в 2 пробела.
console.log(`JSON: ${JSON.stringify(JSON.parse(res), null, 2)}`);
});
// Аналогично можно подписаться на ошибки, на паузу, на закрытие канала
response.on("error", (error) => {
// Обработка ошибки
console.log(`Error: ${error}`);
return;
});
});
};
export { getWeatherHTTPS };-
Ошибки и проверки: Добавление проверки на наличие токена. Обработка ошибок запроса и отсутствия ключа API.
-
Рефакторинг и улучшения: Переход на более простой и безопасный способ построения URL. Использование
Axiosдля более удобной работы с HTTP-запросами.
import axios from "axios";
const getWeather = async (city) => {
const token = await getKeyValue(TOKEN_DICTIONARY.token);
if (!token) {
throw new Error(
"Не задан ключ API, задайте его через команду -t [API_KEY]"
);
}
// В Axios удобный способ конструирования запроса с дополнительной опцией params
// В ответе содержатся данные data и др. информация: headers, response ...
const { data } = await axios.get(
"https://api.openweathermap.org/data/2.5/weather",
{
params: {
q: city,
appid: token,
lang: "ru",
units: "metric",
},
}
);
return data;
};
export { getWeather };- Практическое использование сервиса: Пример запроса погоды для города (Москва). Анализ полученных данных и обработка результатов.
bin-weather\weather.js
import { saveKeyValue, TOKEN_DICTIONARY } from "./services/storage-service.js";
import { getWeather, getWeatherHTTPS } from "./services/api-service.js";
// Обработка ошибок try-catch на уровне изолированной функции сохранения
const saveToken = async (token) => {
if (!token.length) {
printError("Не передан token");
return;
}
try {
await saveKeyValue(TOKEN_DICTIONARY.token, token);
printSuccess("Токен сохранён");
} catch (e) {
printError(e.message);
}
};
// Если используется асинхронная функция getWeather возвращающая данные
const printWeather = async (city) => {
const data = await getWeather(city);
console.log(data);
};
// Вывести погоду
console.log("Weather:");
// Обратиться к API для получения информации о погоде по городу
getWeatherHTTPS("Moscow");
// printWeather('London');node weather.js -t 0123456789abcd...
node weather.jsПеременные окружения оказываются полезным инструментом для настройки и тестирования приложений, позволяя легко модифицировать параметры без изменения кода.
- Переменные окружения являются глобальными настройками для операционной системы или отдельного приложения.
- Используются для настройки различных параметров окружения, например, указания того, что приложение запущено в
developmentокружении вместоproduction.
Обычно токены и подобные данные хранятся в JSON файлах, но для глобального доступа удобнее использовать переменные окружения.
- Для отображения текущих переменных окружения используется
console.log(process.env);. process.envпозволяет получить доступ к переменным окружения вашей операционной системы.
-
Временное добавление происходит через командную строку перед запуском приложения, например,
test=1 node weather.js. Такая команда используется в Linux и Unix подобных ОС. -
В Windows необходимо использовать:
set "TEST=1" && node weather.js. Кавычки гарантируют, что значение будет установлено ровно как нужно, без пробелов. -
Через .env файл (универсальный способ)
Создать файл .env:
TEST=1
И использовать библиотеку вроде dotenv в Node.js:
require("dotenv").config();
console.log(process.env.TEST); // '1'- Аналогично, для постоянного добавления переменных окружения используется конфигурация Shell или Bash.
- Передача отладочного токена через переменную окружения для использования в API запросах.
- Покрытие случаев, когда переменная окружения может быть или не быть установлена, и выбор фолбэка для токена.
// Если задана переменная окружения TOKEN, иначе из файла-настроек
const token = process.env.TOKEN ?? (await getKeyValue(TOKEN_DICTIONARY.token));Пример использования в Windows:
set "TOKEN=04350c0ABCDa4cd3456da7124aeABC28" && node weather.js- Типы ошибок:
- Ошибка 401: Неправильно указан токен.
- Ошибка 404: Неправильно указан город.
-
Проверка ошибок: Можно запустить запрос с неверным городом или без токена для получения ошибки 404 или 401 соответственно.
-
Создание метода
getForecast:
getForecastявляется частьюgetWeather.- Позволяет вызывать погоду для заданного города.
- Требует добавления
asyncдля работы с асинхронными запросами.
- Обработка ошибок:
- Использование
catchдля перехвата ошибок. - Проверка типа ошибки через статус код.
- Вывод сообщения в зависимости от типа ошибки.
- 404: Неверно указан город.
- 401: Неверно указан токен.
- Прочие ошибки: Вывод стандартного сообщения об ошибке.
const getForcast = async () => {
try {
// Город читаем из переменной окружения CITY
const weather = await getWeather(process.env.CITY);
console.log(weather); // Красивый вывод погоды (необходимо реализовать)
} catch (e) {
// Ошибка Axios может содержать status код
if (e?.response?.status == 404) {
printError("Неверно указан город");
} else if (e?.response?.status == 401) {
printError("Неверно указан токен");
} else {
// Любая другая ошибка
printError(e.message);
}
}
};
const initCLI = () => {
...
// Обратиться к API для получения информации о погоде по городу
getForcast();
}
initCLI();Задать дополнительно переменную CITY
set "TOKEN=04350c0b914a4cd01967da724ae05828" && set "CITY=London" && node weather.jsВозможны ошибки:
- первые две: Axios дополнительно обработаны;
- третья: Любая другая ошибка.
ERROR Неверно указан токен
ERROR Неверно указан город
ERROR Request failed with status code 400В cmd.exe нет прямого аналога VAR=value VAR2=value2 node app.js, но можно использовать cmd /C с объединением:
cmd /C "set TOKEN=04350c0b914a4cd01967da724ae05828 && set CITY=London && node weather.js"Но обычно первый способ с несколькими set и && — проще и понятнее.
Если установлены переменные с помощью set, то можно удалить их так:
set CITY=
set TOKEN=Реализовать вывод текущей погоды и функционал сохранения выбранного города.
Шаги Реализации:
- Добавить Сохранение Города:
- Используйте аналогию сохранения токена для реализации сохранения города.
- Добавьте проверку корректности введённого города через запрос к API. Если погода возвращается, город сохраняется.
- Красивый Вывод Погоды:
- Элементы оформления (иконки, эмоджи, цвета) остаются на ваш выбор.
- Реализуйте функционал красиво отформатированного вывода данных о погоде.
- Создаем метод
saveCityдля сохранения города.
weather.js
const saveCity = async (city) => {
if (!city.length) {
printError("Не передан город");
return;
}
try {
await saveKeyValue(TOKEN_DICTIONARY.city, city);
printSuccess("Город сохранён");
} catch (e) {
printError(e.message);
}
};- Реализуем метод
printWeatherдля вывода информации о погоде:
- Используем эмоджи для визуализации погодных условий.
- Выводим температуру, влажность, скорость ветра и другие параметры.
services\api-service.js
const getIcon = (icon) => {
switch (icon.slice(0, -1)) {
case "01":
return "☀️";
case "02":
return "🌤️";
case "03":
return "☁️";
case "04":
return "☁️";
case "09":
return "🌧️";
case "10":
return "🌦️";
case "11":
return "🌩️";
case "13":
return "❄️";
case "50":
return "🌫️";
}
};services\log-service.js
const printWeather = (res, icon) => {
console.log(
dedent`${chalk.bgYellow(" WEATHER ")} Погода в городе ${res.name}
${icon} ${res.weather[0].description}
Температура: ${res.main.temp} (ощущается как ${res.main.feels_like})
Влажность: ${res.main.humidity}%
Скорость ветра: ${res.wind.speed}
`
);
};- Интегрируем проверку и сохранение города, а также получение и вывод погоды в клиентскую часть приложения.
const getForcast = async () => {
try {
// Город читаем из переменной окружения CITY
// Если задана переменная окружения CITY, иначе из файла-настроек
const city = process.env.CITY ?? (await getKeyValue(TOKEN_DICTIONARY.city));
const weather = await getWeather(city);
// Красивый вывод погоды
printWeather(weather, getIcon(weather.weather[0].icon));
} catch (e) {
// Ошибка Axios может содержать status код
if (e?.response?.status == 404) {
printError("Неверно указан город");
} else if (e?.response?.status == 401) {
printError("Неверно указан токен");
} else {
// Любая другая ошибка
printError(e.message);
}
}
};
// Функция, которую будем вызывать в рамках запуска CLI
// Можно условно сказать, что в данной функции выполняется роутинг
// Для каждого роута (условия) есть своя изолированная функция-обработчик
const initCLI = () => {
// process - глобальная переменная с информацией о процессе
const args = getArgs(process.argv);
if (args.h) {
// Вывод help
return printHelp();
}
if (args.s) {
// Сохранить город
// Вызов отдельной изолированной функции (с обработкой ошибок)
return saveCity(args.s);
}
if (args.t) {
// Сохранить токен
// Вызов отдельной изолированной функции (с обработкой ошибок)
return saveToken(args.t);
}
// Вывести погоду
// Обратиться к API для получения информации о погоде по городу
return getForcast();
};
initCLI();Публикация CLI для глобального использования.
Проверка Авторизации: Используем npm whoami для проверки того, под каким пользователем мы залогинены в NPM.
Имя Пакета: Изменяем имя пакета (пример на bin-weather), так как оригинальное имя уже может быть занято.
Внесение Изменений: Меняем название пакета и, при необходимости, другие поля в файле package.json вроде home-page, repository.
Точка Входа: Настраиваем точку входа через определение команды и файла выполнения (weather.js).
NPM Publish: Применяем команду npm publish для публикации пакета в NPM.
Игнорирование файлов: Убедитесь, что в файле .npmignore указаны все ненужные для публикации файлы или папки, например, node_modules.
npm publishИсправление ошибок в файле конфигураций package.json согласно рекомендациям:
npm pkg fixВариант лога после первоначальной публикации npm publish до исправления ошибок конфигурации, для повторной публикации потребуется изменить версию:
npm warn publish npm auto-corrected some errors in your package.json when publishing. Please run "npm pkg fix" to address these errors.
npm warn publish errors corrected:
npm warn publish "repository.url" was normalized to "git+https://github.com/biouri/bin-weather.git"
npm notice
npm notice 📦 bin-weather@1.0.0
npm notice Tarball Contents
npm notice 48.9kB README.md
npm notice 955B helpers/args.js
npm notice 776B package.json
npm notice 3.9kB services/api-service.js
npm notice 1.7kB services/log-service.js
npm notice 3.8kB services/storage-service.js
npm notice 3.3kB weather.js
npm notice Tarball Details
npm notice name: bin-weather
npm notice version: 1.0.0
npm notice filename: bin-weather-1.0.0.tgz
npm notice package size: 16.2 kB
npm notice unpacked size: 63.3 kB
npm notice shasum: 95f24a590ddaee4c3085b4728f680da586c23383
npm notice integrity: sha512-Pmi/Y6KuqS188[...]PP/0zjcwCkepw==
npm notice total files: 7
npm notice
npm notice Publishing to https://registry.npmjs.org/ with tag latest and default access
+ bin-weather@1.0.0
Скрипты Сборки: Если необходимо, можно настроить автоматическую сборку перед публикацией через package.json, используя поля вроде pre-publish (старый способ) и prepare (новый способ). Например, необходима предварительная сборка (или минификация) перед публикацией при помощи tsc:
package.json
"scripts": {
"prepare": "tsc",
...
},Установка: Устанавливаем пакет глобально с помощью npm install -g bin-weather.
Тестирование функциональности: Проверяем работу утилиты через выполнение команды weather.
npm install -g bin-weather
weatherНапример, после глобальной установки пакет будет находиться в каталоге:
C:\Program Files\nodejs\node_modules\bin-weather
В каталоге C:\Program Files\nodejs будут расположены файлы скриптов для запуска.
weather
weather.cmd
weather.ps1
В этом каталоге также находятся node.exe и другие глобальные скрипты: npm, npm-check, npx, rollup, live-server и др.
- npm и npx:
npmявляется пакетным менеджером, который позволяет устанавливать зависимости для проекта.npxвключен в npm с версии 5.2.0, предоставляет возможность запускать пакеты не устанавливая их глобально.
- Передача аргументов в npm-команды:
При использовании команды
npm startдля запуска скрипта, аргументы передаются через двойные черты (--).
package.json
"scripts": {
"start": "node weather.js",
"test": "echo \"Error: no test specified\" && exit 1"
},Пример: если необходимо передать параметр -s moscow в скрипт weather.js, команда будет выглядеть так: npm start -- -s moscow.
Двойные черты говорят npm передать следующие за ними аргументы непосредственно запускаемому скрипту, а не обрабатывать их как параметры самого npm.
- Использование
npxдля исполнения пакетов:npxпозволяет временно скачать и запустить пакет без его установки в систему. Пример: удаление установленного пакетаbin-weatherи последующий его запуск черезnpx(npx bin-weather). Запуск возможен без предварительной установки пакета,npxзагрузит и исполнит его из кэша.
npx bin-weather- Введение в
npm exec(альтернативаnpx): С последних версийnpm,npxполучил альтернативу в виде командыnpm exec.npm execработает аналогичноnpx- позволяет исполнять пакеты, временно загружая их в кэш. Также требуется использование двойных черт для передачи аргументов непосредственно в исполняемый пакет.
npm exec bin-weather
npm exec bin-weather -- -s Bratislava