diff --git a/.test.env b/.test.env index 7e45006..42f4e24 100644 --- a/.test.env +++ b/.test.env @@ -1 +1,3 @@ -ACCESS_TOKEN=window.app.userSid +EFOOD_ACCESS_TOKEN=window.app.userSid +BOX_ACCESS_TOKEN=window.localStorage.getItem('Box:token') +WOLT_ACCESS_TOKEN=JSON.parse((Object.fromEntries(document.cookie.split('; ').map(v=>v.split(/=(.*)/s).map(decodeURIComponent)))).__wtoken).accessToken diff --git a/README.md b/README.md index abefcb1..0a83d47 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ -# efood-lifetime-expenses +# food-order-lifetime-expenses -A simple node script that calculates the total amount spent on e-food service. +A simple node script that calculates the total amount spent on food ordering services, based in Greece. ## Before you start -At root level, there's a file named `.test.env`. Rename it to just `.env` and replace the value of `ACCESS_TOKEN` to your access token. +At root level, there's a file named `.test.env`. Rename it to just `.env` and replace the values of `*_ACCESS_TOKEN` to your access tokens. -To get your token from e-food, visit [e-food](https://www.e-food.gr/), login (if not already logged-in), open your browser's dev tools, select Console and type `window.app.userSid`. +To get a token from each service, visit the required service, login (if not already logged-in), open your browser's dev tools, select Console +and type: +- [e-food](https://www.e-food.gr/): `window.app.userSid`. +- [box](https://box.gr/): `window.localStorage.getItem('Box:token')` +- [wolt](https://wolt.com/): `JSON.parse((Object.fromEntries(document.cookie.split('; ').map(v=>v.split(/=(.*)/s).map(decodeURIComponent)))).__wtoken).accessToken` ## Installation @@ -24,7 +28,7 @@ If you don't, then advise these files to download and install the correct versio To calculate total expenses, run the following command ```bash -yarn calculate +yarn calculate --service= ``` If for any reason installation failed, run `yarn clean` and start all over again. @@ -36,8 +40,8 @@ If for any reason installation failed, run `yarn clean` and start all over again Number of shops: . Total amount of orders: . -Period from YYYY-MM-DD to YYYY-MM-DD. -Total amount spent in e-food is €. +Period from to . +Total amount spent in is €. ``` ## Roadmap @@ -45,6 +49,7 @@ Total amount spent in e-food is €. - Finish tests - Prettier format for stats - Add more stats +- ~Add more services~ ## License diff --git a/__tests__/config.spec.js b/__tests__/config.spec.js index 9a90f6a..9c357ef 100644 --- a/__tests__/config.spec.js +++ b/__tests__/config.spec.js @@ -3,13 +3,13 @@ const { getConfig, CONFIG_MAPPER } = require('../src/config'); describe('config.js', () => { describe('CONFIG_MAPPER', () => { test('should return an object with configuration keys', () => { - expect(Object.keys(CONFIG_MAPPER).length).toEqual(1); + expect(Object.keys(CONFIG_MAPPER).length).toEqual(3); }); }); describe('getConfig', () => { test('should return the value of an existing configuration key', () => { - expect(getConfig(CONFIG_MAPPER.ACCESS_TOKEN)).toBeTruthy(); + expect(getConfig(CONFIG_MAPPER.EFOOD_ACCESS_TOKEN)).toBeTruthy(); }); test('should return undefined if key does not exist', () => { diff --git a/__tests__/constants.spec.js b/__tests__/constants.spec.js index 58bafbd..0c294a0 100644 --- a/__tests__/constants.spec.js +++ b/__tests__/constants.spec.js @@ -3,7 +3,7 @@ const { ENDPOINTS } = require('../src/constants'); describe('constants.js', () => { describe('ENDPOINTS', () => { test('should return an object with constants', () => { - expect(Object.keys(ENDPOINTS).length).toEqual(2); + expect(Object.keys(ENDPOINTS).length).toEqual(3); }); }); }); diff --git a/__tests__/literals.spec.js b/__tests__/literals.spec.js index 2c71bf6..18cddaf 100644 --- a/__tests__/literals.spec.js +++ b/__tests__/literals.spec.js @@ -3,7 +3,7 @@ const { resolve, LITERALS_MAPPER } = require('../src/literals'); describe('literals.js', () => { describe('LITERALS_MAPPER', () => { test('should return an object with literals', () => { - expect(Object.keys(LITERALS_MAPPER).length).toEqual(6); + expect(Object.keys(LITERALS_MAPPER).length).toEqual(7); }); }); diff --git a/__tests__/parser.spec.js b/__tests__/parser.spec.js index 0e6224d..acde74a 100644 --- a/__tests__/parser.spec.js +++ b/__tests__/parser.spec.js @@ -1,4 +1,4 @@ -const { parseEfoodAsync } = require('../src/parser'); +const { parseFoodOrdersAsync } = require('../src/parser'); const { getOrdersFromEfoodAsync } = require('../src/service'); jest.mock('../src/service.js', () => ({ @@ -16,7 +16,7 @@ describe('parser.js', () => { test('it should exit if orders is empty', async () => { getOrdersFromEfoodAsync.mockReturnValue([]); const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {}); - await parseEfoodAsync(); + await parseFoodOrdersAsync('efood'); expect(exitSpy).toHaveBeenCalledWith(0); }); }); diff --git a/__tests__/printer.spec.js b/__tests__/printer.spec.js index b70ed22..e755dea 100644 --- a/__tests__/printer.spec.js +++ b/__tests__/printer.spec.js @@ -9,7 +9,7 @@ describe('printer.js', () => { sum: 1, }; const log = jest.spyOn(console, 'log'); - printStats(data); + printStats('efood', data); expect(log).toHaveBeenCalledTimes(5); }); }); diff --git a/__tests__/transformer.spec.js b/__tests__/transformer.spec.js index 4c1aabc..ba683e7 100644 --- a/__tests__/transformer.spec.js +++ b/__tests__/transformer.spec.js @@ -3,6 +3,7 @@ const { orderByHigherCost, getTotalExpenses, getOderDateByIndex } = require('../ const data = [ { price: 1, + status: 'accepted', submission_date: '2020-01-01 00:00:00', restaurant: { id: 1, @@ -11,6 +12,7 @@ const data = [ }, { price: 2, + status: 'accepted', submission_date: '2020-01-02 00:00:00', restaurant: { id: 2, @@ -19,6 +21,7 @@ const data = [ }, { price: 4, + status: 'accepted', submission_date: '2020-01-03 00:00:00', restaurant: { id: 1, @@ -30,7 +33,7 @@ const data = [ describe('transformer.js', () => { describe('orderByHigherCost', () => { test('should return an array of objects from another object', () => { - const ordered = orderByHigherCost(data); + const ordered = orderByHigherCost('efood', data); expect(ordered).toEqual([ { id: 1, restaurant: 'name', orders: 2, price: 5 }, { id: 2, restaurant: 'othername', orders: 1, price: 2 }, @@ -40,14 +43,14 @@ describe('transformer.js', () => { describe('getTotalExpenses', () => { test('should return the sum of all price attributes in an object', () => { - const sum = getTotalExpenses(data); + const sum = getTotalExpenses('efood', data); expect(sum).toEqual('7.00'); }); }); describe('getOderDateByIndex', () => { test('should return the date attribute for a given index from an object', () => { - const date = getOderDateByIndex(data, 0); + const date = getOderDateByIndex('efood', data, 0); expect(date).toEqual('2020-01-01'); }); }); diff --git a/index.js b/index.js index 4ea0d30..3d5cc4e 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ -const { parseEfoodAsync } = require('./src/parser'); +const { parseFoodOrdersAsync } = require('./src/parser'); +const { FOOD_SERVICE } = require('./src/constants'); /** * Subscribe to unhandledRejection event. @@ -19,5 +20,13 @@ process.on('uncaughtException', (error) => { * Entry point. */ (async () => { - await parseEfoodAsync(); + const argv = require('minimist')(process.argv.slice(2)); + const { service } = argv; + + if (!Object.values(FOOD_SERVICE).includes(service)) { + process.exit(1); + return; + } + + await parseFoodOrdersAsync(service); })(); diff --git a/package.json b/package.json index f138949..e4bdfa1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "efood-lifetime-expenses", - "version": "1.1.0", - "description": "A simple node script that calculates the total amount spent on e-food service.", + "name": "food-order-lifetime-expenses", + "version": "2.0.0", + "description": "A simple node script that calculates the total amount spent on food ordering services, based in Greece.", "main": "index.js", "scripts": { "node:env": "node -r dotenv/config index.js", @@ -13,12 +13,14 @@ "license": "ISC", "repository": { "type": "git", - "url": "https://github.com/ppapadatis/efood-lifetime-expenses" + "url": "https://github.com/ppapadatis/food-order-lifetime-expenses" }, "dependencies": { "axios": "1.1.3", + "date-fns": "2.29.3", "dotenv": "14.3.0", - "lodash": "4.17.21" + "lodash": "4.17.21", + "minimist": "1.2.7" }, "devDependencies": { "@babel/core": "7.20.2", diff --git a/src/config.js b/src/config.js index b60dc4f..4396b7d 100644 --- a/src/config.js +++ b/src/config.js @@ -1,11 +1,15 @@ const { get } = require('lodash'); const CONFIG = Object.freeze({ - ACCESS_TOKEN: process.env.ACCESS_TOKEN, + EFOOD_ACCESS_TOKEN: process.env.EFOOD_ACCESS_TOKEN, + BOX_ACCESS_TOKEN: process.env.BOX_ACCESS_TOKEN, + WOLT_ACCESS_TOKEN: process.env.WOLT_ACCESS_TOKEN, }); const CONFIG_MAPPER = Object.freeze({ - ACCESS_TOKEN: 'ACCESS_TOKEN', + EFOOD_ACCESS_TOKEN: 'EFOOD_ACCESS_TOKEN', + BOX_ACCESS_TOKEN: 'BOX_ACCESS_TOKEN', + WOLT_ACCESS_TOKEN: 'WOLT_ACCESS_TOKEN', }); /** diff --git a/src/constants.js b/src/constants.js index 5d228a9..4d81a1e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,8 +1,25 @@ +const FOOD_SERVICE = Object.freeze({ + EFOOD: 'efood', + BOX: 'box', + WOLT: 'wolt', +}); + const ENDPOINTS = Object.freeze({ - BASE: 'https://api.e-food.gr/api/v1', - ORDERS: 'user/orders/history?limit=100&offset={{OFST}}&mode=extended', + [FOOD_SERVICE.EFOOD]: { + BASE: 'https://api.e-food.gr/api/v1', + ORDERS: 'user/orders/history?limit=100&offset={{OFST}}&mode=extended', + }, + [FOOD_SERVICE.BOX]: { + BASE: 'https://box-client.wavecxm.com/api', + ORDERS: 'orders/get/userlist', + }, + [FOOD_SERVICE.WOLT]: { + BASE: 'https://restaurant-api.wolt.com/v2', + ORDERS: 'order_details/?limit=100&skip={{OFST}}', + }, }); module.exports = { + FOOD_SERVICE, ENDPOINTS, }; diff --git a/src/literals.js b/src/literals.js index e3d0fab..886edfe 100644 --- a/src/literals.js +++ b/src/literals.js @@ -8,9 +8,10 @@ const DICTIONARY = Object.freeze({ TOTAL_SHOPS: 'Number of shops: {0}.', TOTAL_ORDERS: 'Total amount of orders: {0}.', SPREE_PERIOD: 'Period from {0} to {1}.', - AMOUNT_SPENT: 'Total amount spent in e-food is €{0}.', + AMOUNT_SPENT: 'Total amount spent in {0} is €{1}.', ERRORS: { GENERIC: 'Oops! Something went wrong!', + INVALID_SERVICE: 'Wrong food service entered! Check again your input.', }, }, }); @@ -22,6 +23,7 @@ const LITERALS_MAPPER = Object.freeze({ MESSAGES_SPREE_PERIOD: 'MESSAGES.SPREE_PERIOD', MESSAGES_AMOUNT_SPENT: 'MESSAGES.AMOUNT_SPENT', MESSAGES_ERRORS_GENERIC: 'MESSAGES.ERRORS.GENERIC', + MESSAGES_ERRORS_INVALID_SERVICE: 'MESSAGES.ERRORS.INVALID_SERVICE', }); /** diff --git a/src/parser.js b/src/parser.js index b256586..53a9285 100644 --- a/src/parser.js +++ b/src/parser.js @@ -1,16 +1,17 @@ const { isEmpty } = require('lodash'); const { resolve, LITERALS_MAPPER } = require('./literals'); -const { getOrdersFromEfoodAsync } = require('./service'); +const { getOrdersFromServiceAsync } = require('./service'); const { orderByHigherCost, getTotalExpenses } = require('./transformer'); const { printStats } = require('./printer'); /** * Prints various information on e-food stats. + * @param {string} service * @returns {Promise} */ -const parseEfoodAsync = async () => { +const parseFoodOrdersAsync = async (service) => { try { - const orders = await getOrdersFromEfoodAsync(); + const orders = await getOrdersFromServiceAsync(service); if (isEmpty(orders)) { console.log(resolve(LITERALS_MAPPER.GENERIC_NO_ORDERS)); @@ -18,9 +19,9 @@ const parseEfoodAsync = async () => { return; } - const costs = orderByHigherCost(orders); - const sum = getTotalExpenses(orders); - printStats({ orders, costs, sum }); + const costs = orderByHigherCost(service, orders); + const sum = getTotalExpenses(service, orders); + printStats(service, { orders, costs, sum }); } catch (error) { console.error(resolve(LITERALS_MAPPER.MESSAGES_ERRORS_GENERIC)); console.error(error); @@ -31,5 +32,5 @@ const parseEfoodAsync = async () => { }; module.exports = { - parseEfoodAsync, + parseFoodOrdersAsync, }; diff --git a/src/printer.js b/src/printer.js index 8bf92d1..f45fe6f 100644 --- a/src/printer.js +++ b/src/printer.js @@ -3,22 +3,23 @@ const { getOderDateByIndex } = require('./transformer'); /** * Prints a variaty of stats in console. + * @param {string} service * @param {Array<*>} orders * @param {Array<*>} costs * @param {string|number} sum */ -const printStats = ({ orders, costs, sum }) => { +const printStats = (service, { orders, costs, sum }) => { console.log(costs); console.log(resolve(LITERALS_MAPPER.MESSAGES_TOTAL_SHOPS, costs.length)); console.log(resolve(LITERALS_MAPPER.MESSAGES_TOTAL_ORDERS, orders.length)); console.log( resolve( LITERALS_MAPPER.MESSAGES_SPREE_PERIOD, - getOderDateByIndex(orders, orders.length - 1), - getOderDateByIndex(orders, 0), + getOderDateByIndex(service, orders, orders.length - 1), + getOderDateByIndex(service, orders, 0), ), ); - console.log(resolve(LITERALS_MAPPER.MESSAGES_AMOUNT_SPENT, sum)); + console.log(resolve(LITERALS_MAPPER.MESSAGES_AMOUNT_SPENT, service, sum)); }; module.exports = { diff --git a/src/service.js b/src/service.js index 397e26d..180954b 100644 --- a/src/service.js +++ b/src/service.js @@ -1,11 +1,27 @@ const axios = require('axios'); +const { isEmpty } = require('lodash'); const { getConfig, CONFIG_MAPPER } = require('./config'); -const { ENDPOINTS } = require('./constants'); +const { ENDPOINTS, FOOD_SERVICE } = require('./constants'); const DEFAULT_HEADERS = Object.freeze({ - headers: { - 'Content-Type': 'application/json', - 'x-core-session-id': getConfig(CONFIG_MAPPER.ACCESS_TOKEN), + [FOOD_SERVICE.EFOOD]: { + headers: { + 'Content-Type': 'application/json', + 'x-core-session-id': getConfig(CONFIG_MAPPER.EFOOD_ACCESS_TOKEN), + }, + }, + [FOOD_SERVICE.BOX]: { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getConfig(CONFIG_MAPPER.BOX_ACCESS_TOKEN)}`, + appId: 'box-web', + }, + }, + [FOOD_SERVICE.WOLT]: { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getConfig(CONFIG_MAPPER.WOLT_ACCESS_TOKEN)}`, + }, }, }); @@ -17,13 +33,15 @@ const DEFAULT_HEADERS = Object.freeze({ const getOrdersFromEfoodAsync = async () => { const accountOrders = []; const populateAccountOrdersAsync = async (offset = 0) => { - const requestEndpoint = `${ENDPOINTS.BASE}/${ENDPOINTS.ORDERS}`.replace('{{OFST}}', offset); + const requestEndpoint = `${ENDPOINTS[FOOD_SERVICE.EFOOD].BASE}/${ + ENDPOINTS[FOOD_SERVICE.EFOOD].ORDERS + }`.replace('{{OFST}}', offset); const { data: { data: { hasNext, orders }, }, - } = await axios.get(requestEndpoint, DEFAULT_HEADERS); + } = await axios.get(requestEndpoint, DEFAULT_HEADERS[FOOD_SERVICE.EFOOD]); accountOrders.push(...orders); if (hasNext) { @@ -35,6 +53,60 @@ const getOrdersFromEfoodAsync = async () => { return accountOrders; }; +/** + * Returns all orders for the given user from Box service. + * @returns {Array<*>} + */ +const getOrdersFromBoxAsync = async () => { + const { + data: { + payload: { orders }, + }, + } = await axios.get( + `${ENDPOINTS[FOOD_SERVICE.BOX].BASE}/${ENDPOINTS[FOOD_SERVICE.BOX].ORDERS}`, + DEFAULT_HEADERS[FOOD_SERVICE.BOX], + ); + return orders; +}; + +/** + * Returns all orders for the given user from Wolt service. + * @returns {Array<*>} + */ +const getOrdersFromWoltAsync = async () => { + const accountOrders = []; + const populateAccountOrdersAsync = async (offset = 0) => { + const requestEndpoint = `${ENDPOINTS[FOOD_SERVICE.WOLT].BASE}/${ + ENDPOINTS[FOOD_SERVICE.WOLT].ORDERS + }`.replace('{{OFST}}', offset); + + const { data } = await axios.get(requestEndpoint, DEFAULT_HEADERS[FOOD_SERVICE.WOLT]); + + accountOrders.push(...data); + if (!isEmpty(data)) { + await populateAccountOrdersAsync(offset + data.length); + } + }; + + await populateAccountOrdersAsync(); + return accountOrders; +}; + +/** + * Returns orders specified for a given service. + * @param {string} service + * @returns {Promise<*>} + */ +const getOrdersFromServiceAsync = async (service) => { + const resolveService = { + [FOOD_SERVICE.EFOOD]: getOrdersFromEfoodAsync, + [FOOD_SERVICE.BOX]: getOrdersFromBoxAsync, + [FOOD_SERVICE.WOLT]: getOrdersFromWoltAsync, + default: () => {}, + }; + return (resolveService[service] || resolveService.default)(); +}; + module.exports = { - getOrdersFromEfoodAsync, + getOrdersFromServiceAsync, }; diff --git a/src/transformer.js b/src/transformer.js index 085fe5d..dd079a0 100644 --- a/src/transformer.js +++ b/src/transformer.js @@ -1,37 +1,138 @@ -const { chain, get, sumBy } = require('lodash'); +const { chain, get, sumBy, filter } = require('lodash'); +const { format } = require('date-fns'); +const { resolve, LITERALS_MAPPER } = require('./literals'); +const { FOOD_SERVICE } = require('./constants'); + +const ORDER_STATUS = Object.freeze({ + ACCEPTED: 'accepted', + COMPLETED: 'completed', + DELIVERED: 'delivered', +}); /** - * Returns an array of the total amount of money spent per unique shop. + * Transforms an array of orders, to a group of orders, by their shop. * @param {Array<*>} data - * @returns {Array<{id, name, price}>} + * @param {Function} filterFunc + * @param {string} groupBy + * @param {string} idAlias + * @param {string} restaurantAlias + * @param {string} priceAlias + * @param {boolean} shouldDividePrice - If true, will divice price with 100 + * @returns {Array<*>} */ -const orderByHigherCost = (data) => +const transformToGroupedOrders = ( + data, + { filterFunc, groupBy, idAlias, restaurantAlias, priceAlias, shouldDividePrice = false }, +) => chain(data) - .groupBy('restaurant.id') + .filter(filterFunc) + .groupBy(groupBy) .map((group) => ({ - id: get(group, '0.restaurant.id', 0), - restaurant: get(group, '0.restaurant.name', ''), + id: get(group, idAlias, 0), + restaurant: get(group, restaurantAlias, ''), orders: group.length, - price: sumBy(group, 'price'), + price: sumBy(group, priceAlias), + })) + .map((shop) => ({ + ...shop, + price: parseFloat((shop.price / (shouldDividePrice ? 100 : 1)).toFixed(2)), })) - .map((shop) => ({ ...shop, price: parseFloat(shop.price.toFixed(2)) })) .orderBy('price', 'desc') .value(); /** - * Returns the total sum spent on e-food. + * Returns an array of the total amount of money spent per unique shop. + * @param {string} service + * @param {Array<*>} data + * @returns {Array<{id, name, price}>} + */ +const orderByHigherCost = (service, data) => { + switch (service) { + case FOOD_SERVICE.EFOOD: + return transformToGroupedOrders(data, { + filter: (order) => order?.status?.toLowerCase() === ORDER_STATUS.ACCEPTED, + groupBy: 'restaurant.id', + idAlias: '0.restaurant.id', + restaurantAlias: '0.restaurant.name', + priceAlias: 'price', + }); + case FOOD_SERVICE.BOX: + return transformToGroupedOrders(data, { + filter: (order) => + Object.values(ORDER_STATUS).includes(order?.shopResponse?.status?.toLowerCase()), + groupBy: 'shop._id', + idAlias: '0.shop._id', + restaurantAlias: '0.shop.name', + priceAlias: 'totalPrice', + shouldDividePrice: true, + }); + case FOOD_SERVICE.WOLT: + return transformToGroupedOrders(data, { + filter: (order) => order?.status?.toLowerCase() === ORDER_STATUS.DELIVERED, + groupBy: 'venue_id', + idAlias: '0.venue_id', + restaurantAlias: '0.venue_name', + priceAlias: 'total_price', + shouldDividePrice: true, + }); + default: + throw new Error(resolve(LITERALS_MAPPER.MESSAGES_ERRORS_INVALID_SERVICE)); + } +}; + +/** + * Returns the total sum spent on a given service. + * @param {string} service * @param {Array<*>} data * @returns {string} */ -const getTotalExpenses = (data) => sumBy(data, 'price').toFixed(2); +const getTotalExpenses = (service, data) => { + switch (service) { + case FOOD_SERVICE.EFOOD: + return sumBy( + filter(data, (order) => order?.status === ORDER_STATUS.ACCEPTED), + 'price', + ).toFixed(2); + case FOOD_SERVICE.BOX: + return ( + sumBy( + filter(data, (order) => + Object.values(ORDER_STATUS).includes(order?.shopResponse?.status?.toLowerCase()), + ), + 'totalPrice', + ) / 100 + ).toFixed(2); + case FOOD_SERVICE.WOLT: + return ( + sumBy( + filter(data, (order) => order?.status?.toLowerCase() === ORDER_STATUS.DELIVERED), + 'total_price', + ) / 100 + ).toFixed(2); + default: + throw new Error(resolve(LITERALS_MAPPER.MESSAGES_ERRORS_INVALID_SERVICE)); + } +}; /** * Returns the order date based on the index given. + * @param {string} service * @param {Array<*>} data * @param {number} index * @returns {string} */ -const getOderDateByIndex = (data, index) => get(data, `${index}.submission_date`)?.split(' ')[0]; +const getOderDateByIndex = (service, data, index) => { + switch (service) { + case FOOD_SERVICE.EFOOD: + return get(data, `${index}.submission_date`)?.split(' ')[0]; + case FOOD_SERVICE.BOX: + return get(data, `${index}.createdAt`)?.split('T')[0]; + case FOOD_SERVICE.WOLT: + return format(new Date(get(data, `${index}.delivery_time.$date`)), 'yyyy-MM-dd'); + default: + throw new Error(resolve(LITERALS_MAPPER.MESSAGES_ERRORS_INVALID_SERVICE)); + } +}; module.exports = { orderByHigherCost, diff --git a/yarn.lock b/yarn.lock index af133cd..c8a335d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1958,6 +1958,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -3664,7 +3669,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.6: +minimist@1.2.7, minimist@^1.2.0, minimist@^1.2.6: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==