diff --git a/package-lock.json b/package-lock.json index df66d30..eaf801e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,11 +69,36 @@ "typescript": "^5.1.3" } }, + "integrations/sample-app/node_modules/@intentjs/cli": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@intentjs/cli/-/cli-0.0.7.tgz", + "integrity": "sha512-mWH2P/OObjvF9rPx9QSBngR6J5tMIDd2nZuN4Gapbrh+iF+Vd0Q+ehBBjRKpuXEx3+dNLdENGa4TSHGSlsV++g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/cli": "^0.5.2", + "@swc/core": "^1.10.0", + "chokidar": "^3.5.1", + "commander": "^12.1.0", + "enquirer": "^2.4.1", + "fs-extra": "^11.2.0", + "picocolors": "^1.1.0", + "radash": "^12.1.0", + "tree-kill": "^1.2.2", + "typescript": "^5.6.2" + }, + "bin": { + "intent": "bin/intent.js" + }, + "engines": { + "node": ">= 16.14" + } + }, "integrations/sample-app/node_modules/@intentjs/cli/node_modules/@swc/cli": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.5.2.tgz", "integrity": "sha512-ul2qIqjM5bfe9zWLqFDmHZCf9HXXSZZAlZLe4czn+lH4PewO+OWZnQcYCscnJKlbx6MuWjzXVR7gkspjNEJwJA==", - "extraneous": true, + "dev": true, "license": "MIT", "dependencies": { "@swc/counter": "^0.1.3", @@ -104,6 +129,20 @@ } } }, + "integrations/sample-app/node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "license": "Apache-2.0", @@ -19025,7 +19064,7 @@ }, "packages/cli": { "name": "@intentjs/cli", - "version": "0.0.7", + "version": "0.0.9", "license": "MIT", "dependencies": { "@clack/prompts": "^0.9.0", @@ -19209,7 +19248,7 @@ }, "packages/core": { "name": "@intentjs/core", - "version": "0.1.40", + "version": "0.1.41", "license": "MIT", "dependencies": { "@intentjs/hyper-express": "^0.0.5", diff --git a/packages/core/lib/console/consoleIO.ts b/packages/core/lib/console/consoleIO.ts index 42cbaf7..7249886 100644 --- a/packages/core/lib/console/consoleIO.ts +++ b/packages/core/lib/console/consoleIO.ts @@ -7,7 +7,7 @@ import { ConsoleLogger } from './logger'; export class ConsoleIO { schema: ArgumentParserOutput; rawValues: Record; - values: Record = { arguments: {}, options: {} }; + values = { arguments: {}, options: {} }; hasErrors: boolean; missingArguments: string[]; @@ -36,6 +36,14 @@ export class ConsoleIO { return this.values.options[key]; } + arguments(): Record { + return this.values.arguments; + } + + options(): Record { + return this.values.options; + } + handle(): ConsoleIO { this.schema = ArgumentParser.from(this.schemaString); this.values = { arguments: {}, options: {} }; @@ -68,16 +76,11 @@ export class ConsoleIO { this.validateArguments(); if (this.hasErrors) return this; - /** - * Parse options - */ for (const option of this.schema.options) { - const value = Obj.get(this.argv, option.name, ...option.alias); - if (value) { - this.values.options[option.name] = value; - } else { - this.values.options[option.name] = option.defaultValue; - } + const values = Object.values( + Obj.pick(this.argv, [option.name, ...option.alias]), + ); + this.values.options[option.name] = values[0] ? values[0] : undefined; } return this; diff --git a/packages/core/lib/utils/array.ts b/packages/core/lib/utils/array.ts index 77c4fac..068af92 100644 --- a/packages/core/lib/utils/array.ts +++ b/packages/core/lib/utils/array.ts @@ -145,4 +145,21 @@ export class Arr { return undefined; } + + static intersect( + arr1: T[], + arr2: M[], + ): Array { + const tempMap = new Map(); + const newArr = [] as Array; + for (const val of arr1) { + tempMap.set(val, 1); + } + + for (const val2 of arr2) { + if (tempMap.has(val2)) newArr.push(val2); + } + + return newArr; + } } diff --git a/packages/core/lib/utils/helpers.ts b/packages/core/lib/utils/helpers.ts index dc573aa..87a9b94 100644 --- a/packages/core/lib/utils/helpers.ts +++ b/packages/core/lib/utils/helpers.ts @@ -12,28 +12,14 @@ import { join } from 'path'; import { findProjectRoot } from './path'; export const isEmpty = (value: any) => { - if (Str.isString(value)) { - return value === ''; - } - - if (Arr.isArray(value)) { - return !value.length; - } - - if (Obj.isObj(value)) { - return Obj.isEmpty(value); - } - - if (Number.isNaN(value) || value === undefined) { - return true; - } - + if (Str.isString(value)) return value === ''; + if (Arr.isArray(value)) return !value.length; + if (Obj.isObj(value)) return Obj.isEmpty(value); + if (Number.isNaN(value) || value === undefined) return true; return false; }; -export const isBoolean = (value: any): boolean => { - return typeof value === 'boolean'; -}; +export const isBoolean = (value: any): boolean => typeof value === 'boolean'; export const toBoolean = (value: any) => { const val = String(value); diff --git a/packages/core/lib/utils/object.ts b/packages/core/lib/utils/object.ts index ace582d..2cbe3c9 100644 --- a/packages/core/lib/utils/object.ts +++ b/packages/core/lib/utils/object.ts @@ -102,26 +102,24 @@ export class Obj { return !Obj.isEmpty(obj); } - static get( + static get( obj: Record, key: string, - ...aliasKeys: string[] + defaultValue?: T, ): T { - const keys = [key, ...aliasKeys]; - for (const key of keys) { - if (key in obj) return obj[key]; - const splitKeys = key.split('.'); - if (!splitKeys.length) return; + if (key in obj) return obj[key]; + const splitKeys = key.split('.'); + if (!splitKeys.length) return; - if (Arr.isArray(obj[splitKeys[0]])) { - return Arr.get(obj[splitKeys[0]], splitKeys.slice(1).join('.')); - } + if (Arr.isArray(obj[splitKeys[0]])) { + return Arr.get(obj[splitKeys[0]], splitKeys.slice(1).join('.')); + } - if (Obj.isObj(obj[splitKeys[0]])) { - return Obj.get(obj[splitKeys[0]], splitKeys.slice(1).join('.')); - } + if (Obj.isObj(obj[splitKeys[0]])) { + return Obj.get(obj[splitKeys[0]], splitKeys.slice(1).join('.')); } - return undefined; + + return defaultValue; } static sort>(obj: T): T { diff --git a/packages/core/lib/utils/string.ts b/packages/core/lib/utils/string.ts index 4144b90..b3fe5ea 100644 --- a/packages/core/lib/utils/string.ts +++ b/packages/core/lib/utils/string.ts @@ -208,7 +208,7 @@ export class Str { return !Str.isString(value); }; - static len = (str: string): number => { + static len = (str?: string): number => { return str?.length ?? 0; }; diff --git a/packages/core/tests/helpers/arrayHelper.spec.ts b/packages/core/tests/helpers/arrayHelper.spec.ts new file mode 100644 index 0000000..51c223e --- /dev/null +++ b/packages/core/tests/helpers/arrayHelper.spec.ts @@ -0,0 +1,91 @@ +import { Arr } from '../../lib'; + +jest.mock('../../lib/config/service'); + +describe('Array Helper', () => { + beforeEach(async () => {}); + + + it('should return object', () => { + const arr = [ + 2, + ['bar', 'piyush', 'intent'], + { foo: 'bar' }, + [{ bar: 'foo' }], + ]; + + const keys = ['foo', 'chhabra', 'best framework', 'obj']; + expect(Arr.toObj(arr, keys)).toStrictEqual([ + { foo: 'bar', chhabra: 'piyush', 'best framework': 'intent' }, + { foo: { bar: 'foo' } }, + ]); + }); + + // it('should throw exception', () => { + // const arr = {}; + // expect(Arr.isArray(arr as [], true)).toThrow(InvalidValue); + // }); + + it('should return false', () => { + const arr = {}; + expect(Arr.isArray(arr as [])).toBeFalsy(); + }); + + it('should return false', () => { + const arr = []; + expect(Arr.isArray(arr)).toBeTruthy(); + }); + + it('should return array of nested members flattened', () => { + const arr = [1, [2, 3, 4], [[5, 6], [[7], 8], 9]]; + expect(Arr.collapse(arr)).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); + + it('should return array with random order of input', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + const mockMath = Object.create(global.Math); + mockMath.random = () => 0.5; + global.Math = mockMath; + expect(Arr.random(arr)).toStrictEqual([1, 6, 2, 8, 3, 7, 4, 9, 5]); + }); + + it('should return array with sorted order of input', () => { + const arr = [6, 1, 2, 8, 3, 7, 4, 9, 5]; + expect(Arr.sort(arr)).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); + + it('should return array with descending order of input', () => { + const arr = [6, 1, 2, 8, 3, 7, 4, 9, 5]; + expect(Arr.sortDesc(arr)).toStrictEqual([9, 8, 7, 6, 5, 4, 3, 2, 1]); + }); + + it('should return array with common elements from both input arrays', () => { + const arr1 = [1, 2, 3, 4, 5]; + const arr2 = [4, 5, 6, 7, 8]; + expect(Arr.intersect(arr1, arr2)).toStrictEqual([4, 5]); + }); + + it('should return array with elements keys mentioned to pick', () => { + const arr = [ + { developer: { id: 1, name: 'Taylor' } }, + { developer: { id: 2, name: 'Abigail' } }, + ]; + const pick = ['*.developer.name']; + expect(Arr.pick(arr, pick)).toStrictEqual([ + { developer: { name: 'Taylor' } }, + { developer: { name: 'Abigail' } }, + ]); + }); + + it('should return array with all elements except the keys mentioned', () => { + const arr = [ + { developer: { id: 1, name: 'Taylor' } }, + { developer: { id: 2, name: 'Abigail' } }, + ]; + const pick = ['*.developer.name']; + expect(Arr.except(arr, pick)).toStrictEqual([ + { developer: { id: 1 } }, + { developer: { id: 2 } }, + ]); + }); +}); diff --git a/packages/core/tests/helpers/numberHelper.spec.ts b/packages/core/tests/helpers/numberHelper.spec.ts new file mode 100644 index 0000000..6733fba --- /dev/null +++ b/packages/core/tests/helpers/numberHelper.spec.ts @@ -0,0 +1,118 @@ +import { Num } from '../../lib'; + +jest.mock('../../lib/config/service'); + +describe('Numbers Helper', () => { + beforeEach(async () => {}); + + it('should abbrevate with en locale to 1 decimal point precision', () => { + const number = 12345; + expect(Num.abbreviate(number)).toBe('12.3K'); + }); + + it('should abbrevate with en locale to 3 decimal precision', () => { + const number = 12345; + const options = { precision: 3, locale: 'en' }; + expect(Num.abbreviate(number, options)).toBe('12.345K'); + }); + + it('should abbrevate with en-IN locale to 3 decimal precision', () => { + const number = 12345; + const options = { precision: 3, locale: 'en-IN' }; + expect(Num.abbreviate(number, options)).toBe('12.345K'); + }); + + it('should return number itself', () => { + const number = 12345; + const min = 12300; + const max = 12400; + expect(Num.clamp(number, min, max)).toBe(number); + }); + + it('should return minimum number', () => { + const number = 12345; + const min = 12350; + const max = 12400; + expect(Num.clamp(number, min, max)).toBe(min); + }); + + it('should return maximum number', () => { + const number = 12345; + const min = 12300; + const max = 12340; + expect(Num.clamp(number, min, max)).toBe(max); + }); + + it('should return number in currency style in INR', () => { + const number = 12345; + const options = { currency: 'INR', locale: 'en' }; + expect(Num.currency(number, options)).toBe('₹12,345.00'); + }); + + it('should return number in currency style in USD', () => { + const number = 12345; + const options = { currency: 'USD', locale: 'en' }; + expect(Num.currency(number, options)).toBe('$12,345.00'); + }); + + it('should return number in file size format', () => { + const number = 12345; + expect(Num.fileSize(number)).toBe('12.3KB'); + }); + + it('should return number in file size format with precision 3', () => { + const number = 123456789; + const options = { precision: 3 }; + expect(Num.fileSize(number, options)).toBe('123.457MB'); + }); + + it('should return number in humanize form with precision 1', () => { + const number = 12345; + const options = { precision: 1, locale: 'en' }; + expect(Num.forHumans(number, options)).toBe('12.3 thousand'); + }); + + it('should return number in humanize form with precision 3', () => { + const number = 123456789; + const options = { precision: 3, locale: 'en' }; + expect(Num.forHumans(number, options)).toBe('123.457 million'); + }); + + it('should return number in number system format with precision 1(default)', () => { + const number = 12345.78; + const options = { locale: 'en' }; + expect(Num.format(number, options)).toBe('12,345.8'); + }); + + it('should return number in percents when passed as decimal portion with precision 1(default)', () => { + const number = 17.8; + const options = { locale: 'en' }; + expect(Num.percentage(number, options)).toBe('17.8%'); + }); + + it('should return number in ordinal format', () => { + const number = 231; + expect(Num.ordinal(number)).toBe('231st'); + }); + + it('should return number in ordinal format', () => { + const number = 12345; + expect(Num.ordinal(number)).toBe('12345th'); + }); + + it('should return number in english words', () => { + const number = 12345; + expect(Num.spell(number)).toBe( + 'twelve thousand three hundred and forty five only', + ); + }); + + it('should return false', () => { + const number = '12345'; + expect(Num.isInteger(number)).toBe(false); + }); + it('should return true', () => { + const number = 12345; + expect(Num.isInteger(number)).toBe(true); + }); +}); diff --git a/packages/core/tests/helpers/objectHelper.spec.ts b/packages/core/tests/helpers/objectHelper.spec.ts new file mode 100644 index 0000000..146dbf7 --- /dev/null +++ b/packages/core/tests/helpers/objectHelper.spec.ts @@ -0,0 +1,215 @@ +import { InvalidValue } from '../../lib'; +import { Obj } from '../../lib'; + +jest.mock('../../lib/config/service'); + +describe('Object Helper', () => { + beforeEach(async () => {}); + + it('should return flattened object with dot notation', () => { + const obj = { + product: { price: 20, tags: [{ name: 'good' }] }, + name: 'piyush', + }; + expect(Obj.dot(obj)).toStrictEqual({ + 'product.price': 20, + name: 'piyush', + 'product.tags': [{ name: 'good' }], + }); + }); + + it('should return flattened object in array format with dot notation', () => { + const obj = { + product: { price: 20, tags: [{ name: 'good' }] }, + name: 'piyush', + }; + expect(Obj.entries(obj)).toStrictEqual([ + ['product.price', 20], + ['product.tags', [{ name: 'good' }]], + ['name', 'piyush'], + ]); + }); + + it('should return object with all string values trimmed', () => { + const obj = { + product: { price: 20, name: ' Intent', tags: [{ name: ' good ' }] }, + name: ' piyush ', + }; + expect(Obj.trim(obj)).toStrictEqual({ + product: { price: 20, name: 'Intent', tags: [{ name: ' good ' }] }, + name: 'piyush', + }); + }); + + it('should return false for empty and true for notEmpty', () => { + const obj = { + product: { price: 20, name: 'Intent', tags: [{ name: ' good ' }] }, + name: 'piyush', + }; + expect(Obj.isEmpty(obj)).toBeFalsy(); + expect(Obj.isNotEmpty(obj)).toBeTruthy(); + }); + + it('should return true for empty and false for notEmpty', () => { + const obj = {}; + expect(Obj.isEmpty(obj)).toBeTruthy(); + expect(Obj.isNotEmpty(obj)).toBeFalsy(); + }); + + it('should return object as a map', () => { + const obj = { + product: { price: 20, name: 'Intent', tags: [{ name: ' good ' }] }, + name: 'piyush', + }; + const map = new Map(); + map.set('product', { + price: 20, + name: 'Intent', + tags: [{ name: ' good ' }], + }); + map.set('name', 'piyush'); + expect(Obj.asMap(obj)).toStrictEqual(map); + }); + + it('should return true', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ' }], + }, + name: 'piyush', + }; + expect(Obj.isObj(obj)).toBeTruthy(); + }); + + it('should return false', () => { + const obj = true; + expect(Obj.isObj(obj)).toBeFalsy(); + }); + + it('should throw exception', () => { + const run = () => { + const obj = true; + Obj.isObj(obj, true); + }; + expect(run).toThrow(InvalidValue); + }); + + it('should return value of key', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ' }], + }, + name: 'piyush', + }; + expect(Obj.get(obj, 'name')).toBe('piyush'); + }); + + it('should return undefined', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ' }], + }, + name: 'piyush', + }; + expect(Obj.get(obj, 'foo')).toBe(undefined); + }); + + it('should return default value', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ' }], + }, + name: 'piyush', + }; + expect(Obj.get(obj, 'foo', 'bar')).toBe('bar'); + }); + + it('should return object with sorted keys and sorted values', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ' }], + }, + name: 'piyush', + }; + expect(Obj.sort(obj)).toStrictEqual({ + name: 'piyush', + product: { name: 'Intent', price: 20, tags: [{ name: ' good ' }] }, + }); + }); + + it('should return objects with keys only mentioned to pick', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ', time: 3 }], + }, + name: 'piyush', + }; + const pick = ['product.tags.*.time', 'name']; + expect(Obj.pick(obj, pick)).toStrictEqual({ + name: 'piyush', + product: { tags: [{ time: 3 }] }, + }); + }); + + it('should return objects with all keys but the mentioned ones to leave', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ', time: 3 }], + }, + name: 'piyush', + }; + const leave = ['product.tags.*.time', 'name']; + expect(Obj.except(obj, leave)).toStrictEqual({ + product: { name: 'Intent', price: 20, tags: [{ name: ' good ' }] }, + }); + }); + + it('should return objects with keys only mentioned to pick', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ', time: 3 }], + }, + name: 'piyush', + }; + const pick = ['product.tags.*.times', 'name']; + expect(Obj.pick(obj, pick)).toStrictEqual({ + name: 'piyush', + product: { tags: [{}] }, + }); + }); + + it('should return objects with all keys but the mentioned ones to leave', () => { + const obj = { + product: { + price: 20, + name: 'Intent', + tags: [{ name: ' good ', time: 3 }], + }, + name: 'piyush', + }; + const leave = ['product.tags.*.times', 'name']; + expect(Obj.except(obj, leave)).toStrictEqual({ + product: { + name: 'Intent', + price: 20, + tags: [{ name: ' good ', time: 3 }], + }, + }); + }); +});