commit f258a8e6199e38f37714340dc68d2d64860f481d Author: Mason Payne Date: Sun Jul 18 13:57:08 2021 -0600 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/build/mockProcess.js b/build/mockProcess.js new file mode 100644 index 0000000..e91f165 --- /dev/null +++ b/build/mockProcess.js @@ -0,0 +1,237 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const mockdata_1 = require("./mockdata"); +const cryptoRandomString = require("crypto-random-string"); +const unique_names_generator_1 = require("unique-names-generator"); +function handleEvent(eventLog) { + if (eventLog.eventName === 'bought') { + let vendingMachine = findVendingMachine(eventLog.data.offer); + let selection = vendingMachine.selections.find((sel) => { + return sel.id === eventLog.data.selection; + }); + selection.current = selection.current - 1; + } +} +function findVendingMachine(id) { + for (let i in mockdata_1.default.players) { + let player = mockdata_1.default.players[i]; + for (let j in player.vendingMachines) { + if (player.vendingMachines[i].id === id) { + return player.vendingMachines[i]; + } + } + } +} +function personPassesByVendingMachine(person, machine) { + let isThursty = Math.random() < person.thirst; + let availableSelections = machine.selections.filter((selection) => { + return !!selection.current; + }); + if (isThursty) { + if (!availableSelections.length) { + return wontBuy('NoneAvailable'); + } + let acceptableOptions = availableSelections.filter((option) => { + return person.preferences.indexOf(option.drink) !== -1; + }); + if (!acceptableOptions.length) { + return wontBuy('NoPreferredDrinks'); + } + let options = acceptableOptions.filter((selection) => { + return selection.price <= person.highestPrice; + }); + if (!options.length) { + return wontBuy('HighPrice'); + } + let picked = pick(acceptableOptions); + let selectionTypeData = getSelectionType(picked.type); + return { + eventName: 'bought', + data: { + reason: 'Thirsty', + amount: picked.price, + drink: picked.drink, + selection: picked.id, + selectionType: picked.type, + oz: selectionTypeData ? selectionTypeData.oz : 12, + person: person.id, + offer: machine.id, + } + }; + } + else { + return wontBuy('NotThirsty'); + } + function wontBuy(reason) { + return { + eventName: 'wontBuy', + data: { + reason: reason, + amount: 0, + drink: 'none', + selection: 'none', + selectionType: 'none', + oz: 0, + person: person.id, + offer: machine.id, + } + }; + } +} +function generatePerson(location) { + let money = Math.random() * 100; + return { + id: genId('person'), + name: genPersonName(), + preferences: genPreferences(), + highestPrice: (money / 20), + money: money, + thirst: clamp(location.dailyTraffic.thirstBonus + (Math.random() / 10)) + }; +} +function genId(prefix) { + return `${prefix}-${cryptoRandomString({ length: 6, type: 'distinguishable' })}`; +} +function genPersonName() { + const config = { + dictionaries: [unique_names_generator_1.names, unique_names_generator_1.names], + length: 2, + separator: ' ' + }; + return unique_names_generator_1.uniqueNamesGenerator(config); +} +function genDrinkName() { + const config = { + dictionaries: [unique_names_generator_1.adjectives, unique_names_generator_1.colors], + length: 2, + separator: ' ', + style: 'capital' + }; + return unique_names_generator_1.uniqueNamesGenerator(config); +} +function genStoreName() { + const stores = [ + 'shop', + 'reseller', + 'department Store', + 'chain Store', + 'emporium', + 'supermarket', + 'hypermarket', + 'superstore', + 'mart', + 'shed', + 'big Box', + 'convenience Store', + 'corner Store', + 'food Store', + 'market', + 'food Mart', + 'bodega', + ]; + const config = { + dictionaries: [unique_names_generator_1.names, stores], + length: 2, + separator: '\'s ', + style: 'capital' + }; + return unique_names_generator_1.uniqueNamesGenerator(config); +} +function genCityName() { + const places = [ + 'town', + 'city', + 'township', + 'borough', + 'seat', + 'metropolis', + 'burg', + 'municipality', + 'hamlet', + ]; + const config = { + dictionaries: [Math.random() > 0.5 ? unique_names_generator_1.colors : unique_names_generator_1.names, places], + length: 2, + separator: ' ', + style: 'capital' + }; + return unique_names_generator_1.uniqueNamesGenerator(config); +} +function clamp(value, max = 1, min = 0) { + if (value > max) { + return max; + } + if (value < min) { + return min; + } + return value; +} +function genPreferences() { + const availableDrinks = mockdata_1.default.drinks; + let prefCount = Math.floor(Math.random() * availableDrinks.length); + prefCount = prefCount === 0 ? prefCount + 1 : prefCount; + let preferences = []; + while (preferences.length < prefCount) { + let newPref = pick(availableDrinks); + if (preferences.indexOf(newPref.id) === -1) { + preferences.push(newPref.id); + } + } + return preferences; +} +function getSelectionType(typeName) { + return mockdata_1.default.selectionTypes.find((type) => { + return type.type === typeName; + }); +} +function randomNumber(min, max) { + return Math.random() * (max - min) + min; +} +function pick(arr) { + return arr[Math.floor(Math.random() * arr.length)]; +} +function getStatsFromEvents(events) { + let purchaseCount = 0; + let offerEventCount = 0; + let amountEarned = 0; + let ozSold = 0; + let popularity = {}; + let eventReasons = {}; + for (let i in events) { + let eventLog = events[i]; + if (!eventReasons[eventLog.data.reason]) { + eventReasons[eventLog.data.reason] = 0; + } + eventReasons[eventLog.data.reason] = eventReasons[eventLog.data.reason] + 1; + if (eventLog.eventName === 'bought' || eventLog.eventName === 'wontBuy') { + offerEventCount = offerEventCount + 1; + if (eventLog.eventName === 'bought') { + purchaseCount = purchaseCount + 1; + if (!popularity[eventLog.data.drink]) { + popularity[eventLog.data.drink] = 0; + } + popularity[eventLog.data.drink] = popularity[eventLog.data.drink] + 1; + amountEarned = amountEarned + eventLog.data.amount; + ozSold = ozSold + eventLog.data.oz; + } + } + } + console.log({ + purchaseCount, + purchaseRate: purchaseCount / offerEventCount, + offerEventCount, + amountEarned, + ozSold, + popularity, + eventReasons + }); +} +let events = []; +let traffic = randomNumber(mockdata_1.default.locations[0].dailyTraffic.min, mockdata_1.default.locations[0].dailyTraffic.max); +for (let i = 0; i < traffic; i++) { + let curEvent = personPassesByVendingMachine(generatePerson(mockdata_1.default.locations[0]), mockdata_1.default.players[0].vendingMachines[0]); + console.log(genCityName()); + events.push(curEvent); + handleEvent(curEvent); +} +getStatsFromEvents(events); diff --git a/build/mockdata.js b/build/mockdata.js new file mode 100644 index 0000000..6a6cb03 --- /dev/null +++ b/build/mockdata.js @@ -0,0 +1,220 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +let dataDefinition = { + players: [ + { + id: 'player-01', + name: 'payne8', + balance: 4000.00, + vendingMachines: [ + { + id: 'vend-01', + name: "First One!", + location: 'loc-01', + placement: 'plac-01', + compatibleSelectionTypes: ['can', 'bottle'], + selections: [ + { + id: 'sel-01', + name: 'Diet Mtn. Dew', + drink: 'drink-01', + max: 56, + current: 24, + price: 3.00, + type: 'can' + }, + { + id: 'sel-02', + name: 'Mtn. Dew', + drink: 'drink-02', + max: 56, + current: 24, + price: 2.00, + type: 'can' + }, + { + id: 'sel-03', + name: 'Dr. Pepper', + drink: 'drink-03', + max: 56, + current: 24, + price: 1.50, + type: 'can' + }, + { + id: 'sel-04', + name: 'Sprite', + drink: 'drink-04', + max: 56, + current: 24, + price: 0.75, + type: 'can' + }, + { + id: 'sel-05', + name: 'Coke', + drink: 'drink-05', + max: 56, + current: 24, + price: 1.00, + type: 'can' + }, + { + id: 'sel-06', + name: 'Diet Coke', + drink: 'drink-06', + max: 56, + current: 24, + price: 1.50, + type: 'can' + }, + { + id: 'sel-07', + name: 'Orange Soda', + drink: 'drink-07', + max: 56, + current: 24, + price: 2.00, + type: 'can' + }, + { + id: 'sel-08', + name: 'Grape Soda', + drink: 'drink-08', + max: 56, + current: 24, + price: 0.50, + type: 'can' + }, + ], + agreement: 0.0, + } + ], + deals: [ + { + id: 'deal-01', + location: 'loc-01', + selections: [ + { + id: 'sel-09', + name: 'Diet Mtn. Dew - Dozen Cans', + drink: 'drink-01', + max: 56, + current: 24, + price: 7.00, + type: 'dozen-box' + }, + ], + agreement: 0.4, + }, + ] + } + ], + selectionTypes: [ + { + type: 'can', + oz: 12, + }, + { + type: 'dozen-box', + os: 144 + } + ], + drinks: [ + { + id: 'drink-01', + name: 'Diet Mtn. Dew', + costPerOz: 0.005, + }, + { + id: 'drink-02', + name: 'Mtn. Dew', + costPerOz: 0.005, + }, + { + id: 'drink-03', + name: 'Dr. Pepper', + costPerOz: 0.005, + }, + { + id: 'drink-04', + name: 'Sprite', + costPerOz: 0.005, + }, + { + id: 'drink-05', + name: 'Coke', + costPerOz: 0.005, + }, + { + id: 'drink-06', + name: 'Diet Coke', + costPerOz: 0.005, + }, + { + id: 'drink-07', + name: 'Orange Soda', + costPerOz: 0.005, + }, + { + id: 'drink-08', + name: 'Grape Soda', + costPerOz: 0.005, + }, + ], + locations: [ + { + id: 'loc-01', + name: 'General Store', + city: 'city-01', + dailyTraffic: { + max: 2000, + min: 400, + thirstBonus: 0.05, + }, + deals: { + count: 2, + traffic: 0.4, + list: [ + 'deal-01' + ] + }, + placements: [ + { + id: 'plac-01', + traffic: 0.5, + }, + { + id: 'plac-02', + traffic: 0.6, + }, + ] + } + ], + cities: [ + { + id: 'city-01', + name: 'Hometown', + population: 15000 + } + ], + people: [ + { + id: 'person-01', + name: 'Steve Klemm', + preferences: [ + 'drink-01', + 'drink-04', + 'drink-08', + ], + highestPrice: 2.00, + wallet: { + cc: 500.00, + cash: 10.00, + coins: 1.25, + }, + thirst: 0.8, + } + ] +}; +exports.default = dataDefinition; diff --git a/data-relationships.svg b/data-relationships.svg new file mode 100644 index 0000000..e801538 --- /dev/null +++ b/data-relationships.svg @@ -0,0 +1,236 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + Vending Machines + + + Locations + + + Players + + + Deals + + + Placements + Owned by + + Cities + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..4b79dd4 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "vending-tycoon-cli", + "version": "1.0.0", + "main": "index.js", + "author": "Mason Payne ", + "license": "UNLICENCED", + "private": true, + "dependencies": { + "crypto-random-string": "^3.2.0", + "unique-names-generator": "^4.2.0" + } +} diff --git a/src/mockProcess.ts b/src/mockProcess.ts new file mode 100644 index 0000000..7b1882a --- /dev/null +++ b/src/mockProcess.ts @@ -0,0 +1,317 @@ +import mockData from './mockdata'; +import * as cryptoRandomString from 'crypto-random-string'; +import { uniqueNamesGenerator, Config, adjectives, names, colors } from 'unique-names-generator'; + +// function calculateBusinessDay(player: any) { +// let lineItems = []; +// for (let i in player.vendingMachines) { +// let machine = player.vendingMachines[i]; +// +// } +// } +function handleEvent(eventLog: EventLog) { + if (eventLog.eventName === 'bought') { + let vendingMachine = findVendingMachine(eventLog.data.offer); + let selection = vendingMachine.selections.find((sel) => { + return sel.id === eventLog.data.selection; + }); + selection.current = selection.current - 1; + } +} + +function findVendingMachine(id: string) { + for (let i in mockData.players) { + let player = mockData.players[i]; + for (let j in player.vendingMachines) { + if (player.vendingMachines[i].id === id) { + return player.vendingMachines[i]; + } + } + } +} + +function personPassesByVendingMachine(person: Person, machine): EventLog { + let isThursty: boolean = Math.random() < person.thirst; + let availableSelections = machine.selections.filter((selection) => { + return !!selection.current; + }); + + if (isThursty) { + if (!availableSelections.length) { + return wontBuy('NoneAvailable'); + } + // pick a drink + let acceptableOptions = availableSelections.filter((option) => { + return person.preferences.indexOf(option.drink) !== -1; + }); + if (!acceptableOptions.length) { + return wontBuy('NoPreferredDrinks'); + } + let options = acceptableOptions.filter((selection) => { + return selection.price <= person.highestPrice; + }); + if (!options.length) { + return wontBuy('HighPrice'); + } + let picked = pick(acceptableOptions); + let selectionTypeData = getSelectionType(picked.type); + return { + eventName: 'bought', + data: { + reason: 'Thirsty', + amount: picked.price, + drink: picked.drink, + selection: picked.id, + selectionType: picked.type, + oz: selectionTypeData ? selectionTypeData.oz : 12, + person: person.id, + offer: machine.id, + } + }; + } else { + return wontBuy('NotThirsty'); + } + + function wontBuy(reason): EventLog { + return { + eventName: 'wontBuy', + data: { + reason: reason, + amount: 0, + drink: 'none', + selection: 'none', + selectionType: 'none', + oz: 0, + person: person.id, + offer: machine.id, + } + }; + } +} + +function generatePerson(location: Location): Person { + let money = Math.random() * 100; + return { + id: genId('person'), + name: genPersonName(), + preferences: genPreferences(), + highestPrice: (money / 20), + money: money, + thirst: clamp(location.dailyTraffic.thirstBonus + (Math.random() / 10)) + } +} + +function genId(prefix: string): string { + return `${prefix}-${cryptoRandomString({length: 6, type: 'distinguishable'})}`; +} + +function genPersonName(): string { + const config: Config = { + dictionaries: [names, names], + length: 2, + separator: ' ' + }; + return uniqueNamesGenerator(config); +} + +function genDrinkName(): string { + const config: Config = { + dictionaries: [adjectives, colors], + length: 2, + separator: ' ', + style: 'capital' + }; + return uniqueNamesGenerator(config); +} + +function genStoreName(): string { + const stores: string[] = [ + 'shop', + 'reseller', + 'department Store', + 'chain Store', + 'emporium', + 'supermarket', + 'hypermarket', + 'superstore', + 'mart', + 'shed', + 'big Box', + 'convenience Store', + 'corner Store', + 'food Store', + 'market', + 'food Mart', + 'bodega', + ]; + const config: Config = { + dictionaries: [names, stores], + length: 2, + separator: '\'s ', + style: 'capital' + }; + return uniqueNamesGenerator(config); +} + +function genCityName(): string { + const places: string[] = [ + 'town', + 'city', + 'township', + 'borough', + 'seat', + 'metropolis', + 'burg', + 'municipality', + 'hamlet', + ]; + const config: Config = { + dictionaries: [Math.random() > 0.5 ? colors : names, places], + length: 2, + separator: ' ', + style: 'capital' + }; + return uniqueNamesGenerator(config); +} + +function clamp(value: number, max: number = 1, min: number = 0) { + if (value > max) { + return max; + } + if (value < min) { + return min; + } + return value; +} + +function genPreferences(): string[] { + const availableDrinks: string[] = mockData.drinks; + let prefCount = Math.floor(Math.random() * availableDrinks.length); + prefCount = prefCount === 0 ? prefCount + 1 : prefCount; // have at least one preference + let preferences: string[] = []; + while (preferences.length < prefCount) { + let newPref: Drink = pick(availableDrinks); + if (preferences.indexOf(newPref.id) === -1) { + preferences.push(newPref.id); + } + } + return preferences; +} + +function getSelectionType(typeName: string) { + return mockData.selectionTypes.find((type) => { + return type.type === typeName; + }); +} + +function randomNumber(min: number, max: number): number { + return Math.random() * (max - min) + min; +} + +function pick(arr: any[]) { + return arr[Math.floor(Math.random() * arr.length)]; +} + +function getStatsFromEvents(events: EventLog[]) { + let purchaseCount: number = 0; + let offerEventCount: number = 0; + let amountEarned: number = 0; + let ozSold: number = 0; + let popularity: {[drink: string]: number} = {}; + let eventReasons: {[reason: string]: number} = {}; + + for (let i in events) { + let eventLog: EventLog = events[i]; + if (!eventReasons[eventLog.data.reason]) { + eventReasons[eventLog.data.reason] = 0; + } + eventReasons[eventLog.data.reason] = eventReasons[eventLog.data.reason] + 1; + if (eventLog.eventName === 'bought' || eventLog.eventName === 'wontBuy') { + offerEventCount = offerEventCount + 1; + if (eventLog.eventName === 'bought') { + purchaseCount = purchaseCount + 1; + if (!popularity[eventLog.data.drink]) { + popularity[eventLog.data.drink] = 0; + } + popularity[eventLog.data.drink] = popularity[eventLog.data.drink] + 1; + amountEarned = amountEarned + eventLog.data.amount; + ozSold = ozSold + eventLog.data.oz; + } + } + } + + console.log({ + purchaseCount, + purchaseRate: purchaseCount / offerEventCount, + offerEventCount, + amountEarned, + ozSold, + popularity, + eventReasons + }); +} + +interface Drink { + id: string; + name: string; + costPerOz: number; +} + +interface DailyTraffic { + max: number; + min: number; + thirstBonus: number; +} + +interface Placement { + id: string; + name: string; + traffic: number; +} + +interface Location { + id: string; + name: string; + city: string; + dailyTraffic: DailyTraffic; + placements: Placement[]; +} + +interface Person { + id: string; + name: string; + preferences: string[]; + highestPrice: number; + money: number; + thirst: number; +} + +interface EventLog { + eventName: string; + data: any; +} + +interface PersonEvent { + reason: string; + amount: number; + drink: string; + selection: string; + selectionType: string; + oz: number; + person: string; // person involved in the event + offer: string; // id of deal or vending machine +} + +let events: EventLog[] = []; +let traffic: number = randomNumber(mockData.locations[0].dailyTraffic.min, mockData.locations[0].dailyTraffic.max) +for (let i = 0; i < traffic; i++) { + // console.log(generatePerson(mockData.locations[0])); + let curEvent: EventLog = personPassesByVendingMachine(generatePerson(mockData.locations[0]), mockData.players[0].vendingMachines[0]) + // console.log(genDrinkName()); + // console.log(genStoreName()); + console.log(genCityName()); + events.push(curEvent); + handleEvent(curEvent); +} + +getStatsFromEvents(events); diff --git a/src/mockdata.ts b/src/mockdata.ts new file mode 100644 index 0000000..df4e720 --- /dev/null +++ b/src/mockdata.ts @@ -0,0 +1,219 @@ +let dataDefinition: any = { + players: [ + { + id: 'player-01', + name: 'payne8', + balance: 4000.00, + vendingMachines: [ + { + id: 'vend-01', + name: "First One!", + location: 'loc-01', + placement: 'plac-01', + compatibleSelectionTypes: ['can', 'bottle'], + selections: [ + { + id: 'sel-01', + name: 'Diet Mtn. Dew', + drink: 'drink-01', + max: 56, + current: 24, + price: 3.00, + type: 'can' + }, + { + id: 'sel-02', + name: 'Mtn. Dew', + drink: 'drink-02', + max: 56, + current: 24, + price: 2.00, + type: 'can' + }, + { + id: 'sel-03', + name: 'Dr. Pepper', + drink: 'drink-03', + max: 56, + current: 24, + price: 1.50, + type: 'can' + }, + { + id: 'sel-04', + name: 'Sprite', + drink: 'drink-04', + max: 56, + current: 24, + price: 0.75, + type: 'can' + }, + { + id: 'sel-05', + name: 'Coke', + drink: 'drink-05', + max: 56, + current: 24, + price: 1.00, + type: 'can' + }, + { + id: 'sel-06', + name: 'Diet Coke', + drink: 'drink-06', + max: 56, + current: 24, + price: 1.50, + type: 'can' + }, + { + id: 'sel-07', + name: 'Orange Soda', + drink: 'drink-07', + max: 56, + current: 24, + price: 2.00, + type: 'can' + }, + { + id: 'sel-08', + name: 'Grape Soda', + drink: 'drink-08', + max: 56, + current: 24, + price: 0.50, + type: 'can' + }, + ], + agreement: 0.0, // revenue share + } + ], + deals: [ + { + id: 'deal-01', + location: 'loc-01', + selections: [ + { + id: 'sel-09', + name: 'Diet Mtn. Dew - Dozen Cans', + drink: 'drink-01', + max: 56, + current: 24, + price: 7.00, + type: 'dozen-box' + }, + ], + agreement: 0.4, // revenue share + }, + ] + } + ], + selectionTypes: [ + { + type: 'can', + oz: 12, + }, + { + type: 'dozen-box', + os: 144 + } + ], + drinks: [ + { + id: 'drink-01', + name: 'Diet Mtn. Dew', + costPerOz: 0.005, + }, + { + id: 'drink-02', + name: 'Mtn. Dew', + costPerOz: 0.005, + }, + { + id: 'drink-03', + name: 'Dr. Pepper', + costPerOz: 0.005, + }, + { + id: 'drink-04', + name: 'Sprite', + costPerOz: 0.005, + }, + { + id: 'drink-05', + name: 'Coke', + costPerOz: 0.005, + }, + { + id: 'drink-06', + name: 'Diet Coke', + costPerOz: 0.005, + }, + { + id: 'drink-07', + name: 'Orange Soda', + costPerOz: 0.005, + }, + { + id: 'drink-08', + name: 'Grape Soda', + costPerOz: 0.005, + }, + ], + locations: [ + { + id: 'loc-01', + name: 'General Store', + city: 'city-01', + dailyTraffic: { + max: 2000, + min: 400, + thirstBonus: 0.05, + }, + deals: { + count: 2, + traffic: 0.4, + list: [ + 'deal-01' + ] + }, + placements: [ + { + id: 'plac-01', + traffic: 0.5, + }, + { + id: 'plac-02', + traffic: 0.6, + }, + ] + } + ], + cities: [ + { + id: 'city-01', + name: 'Hometown', + population: 15000 + } + ], + people: [ + { + id: 'person-01', + name: 'Steve Klemm', + preferences: [ + 'drink-01', + 'drink-04', + 'drink-08', + ], + highestPrice: 2.00, + wallet: { + cc: 500.00, + cash: 10.00, + coins: 1.25, + }, + thirst: 0.8, + } + ] +}; + +export default dataDefinition; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..17d1269 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "isolatedModules": false, + "jsx": "react", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "declaration": false, + "noImplicitAny": false, + "noImplicitUseStrict": false, + "removeComments": true, + "noLib": false, + "preserveConstEnums": true, + "suppressImplicitAnyIndexErrors": true, + "outDir": "./build", + "rootDir": "./src/", + "sourceMap": false + }, + "compileOnSave": true, + "buildOnSave": true, + "exclue": [ + "node_modules", + "typings/browser", + "typings/browser.d.ts" + ] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..6b36a03 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,20 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +crypto-random-string@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-3.2.0.tgz#d513ef0c2ac6ff7cad5769de585d9bf2ad5a2b4d" + integrity sha512-8vPu5bsKaq2uKRy3OL7h1Oo7RayAWB8sYexLKAqvCXVib8SxgbmoF1IN4QMKjBv8uI8mp5gPPMbiRah25GMrVQ== + dependencies: + type-fest "^0.8.1" + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +unique-names-generator@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unique-names-generator/-/unique-names-generator-4.2.0.tgz#e1845155becb4f1d133ec12fc7efc32d0cb733b1" + integrity sha512-MaNgUy/U7jeIMr5n8tdYXFi5IJ9DVo3nc90Z0QDnbYmO0F/H3Swjb8H/bkHRQYBCNveBNU6rq90metcmxVdO6Q==