diff --git a/package.json b/package.json index 625f28d..cb2bab2 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "devDependencies": { "@electron/docs-parser": "^2.0.0", "@types/debug": "^4.1.12", - "@types/lodash": "^4.17.7", "husky": "^9.1.6", "lint-staged": "^16.2.7", "prettier": "^3.3.3", @@ -40,7 +39,6 @@ "@types/node": "^20.11.25", "chalk": "^5.3.0", "debug": "^4.3.7", - "lodash": "^4.17.11", "ora": "^8.1.0", "pretty-ms": "^9.1.0" }, diff --git a/src/dynamic-param-interfaces.ts b/src/dynamic-param-interfaces.ts index 0eb620f..f665b62 100644 --- a/src/dynamic-param-interfaces.ts +++ b/src/dynamic-param-interfaces.ts @@ -1,3 +1,5 @@ +import { isDeepStrictEqual } from 'node:util'; + import { EventParameterDocumentation, DetailedObjectType, @@ -7,9 +9,9 @@ import { } from '@electron/docs-parser'; import chalk from 'chalk'; import d from 'debug'; -import _ from 'lodash'; import * as utils from './utils.js'; +import { upperFirst, lowerFirst, camelCase } from './utils.js'; const debug = d('dynamic-param'); @@ -39,11 +41,13 @@ const polite = (s: string): string => { const ignoreDescriptions = ( props: T[], ): Pick>[] => - _.map(props, (p) => { - const { description, ...toReturn } = p; + props + .map((p) => { + const { description, ...toReturn } = p; - return toReturn; - }).sort((a, b) => a.name.localeCompare(b.name)); + return toReturn; + }) + .sort((a, b) => a.name.localeCompare(b.name)); const noDescriptionCache = new WeakMap(); const unsetDescriptions = (o: any): any => { @@ -71,14 +75,14 @@ const createParamInterface = ( ): string => { const maybeArray = (type: string) => (param.collection ? `Array<${type}>` : type); const potentialExistingArgType = polite(IName); - const potentialExistingArgName = _.lowerFirst(polite(IName)); - let argType = polite(IName) + _.upperFirst(_.camelCase(param.name)); + const potentialExistingArgName = lowerFirst(polite(IName)); + let argType = polite(IName) + upperFirst(camelCase(param.name)); let argName = param.name; // TODO: Note. It is still possible for even backupIName to be already used let usingExistingParamInterface = false; - _.forIn(paramInterfacesToDeclare, (value, key) => { + for (const [key, value] of Object.entries(paramInterfacesToDeclare)) { const test = unsetDescriptions( - _.assign({}, param, { + Object.assign({}, param, { name: argName, tName: argType, required: value.required, @@ -86,7 +90,7 @@ const createParamInterface = ( }), ); const potentialTest = unsetDescriptions( - _.assign({}, param, { + Object.assign({}, param, { name: potentialExistingArgName, tName: potentialExistingArgType, required: value.required, @@ -94,25 +98,25 @@ const createParamInterface = ( }), ); const unsetValue = unsetDescriptions(value); - if (_.isEqual(test, unsetValue) || _.isEqual(potentialTest, unsetValue)) { + if (isDeepStrictEqual(test, unsetValue) || isDeepStrictEqual(potentialTest, unsetValue)) { usingExistingParamInterface = true; debug( chalk.cyan( - `Using existing type for param name ${argType} --> ${key} in Interface: ${_.upperFirst( + `Using existing type for param name ${argType} --> ${key} in Interface: ${upperFirst( param.tName, )} --- This is because their structure is identical`, ), ); argType = key; - return false; + break; } - }); + } if (usingExistingParamInterface) { return maybeArray(argType); } if ( paramInterfacesToDeclare[argType] && - !_.isEqual( + !isDeepStrictEqual( ignoreDescriptions(paramInterfacesToDeclare[argType].properties), ignoreDescriptions(param.properties), ) @@ -167,7 +171,7 @@ const flushParamInterfaces = ( delete toDeclareCheck[prop]; delete declaredCheck[prop]; } - if (!_.isEqual(toDeclareCheck, declaredCheck)) { + if (!isDeepStrictEqual(toDeclareCheck, declaredCheck)) { throw new Error('Ruh roh, "' + paramKey + '" is already declared'); } delete paramInterfacesToDeclare[paramKey]; @@ -177,7 +181,7 @@ const flushParamInterfaces = ( const param = paramInterfacesToDeclare[paramKey]; const paramAPI: string[] = []; paramAPI.push( - `interface ${_.upperFirst(param.tName)}${ + `interface ${upperFirst(param.tName)}${ param.extends ? ` extends ${param.extends}` : '' } {`, ); @@ -193,12 +197,12 @@ const flushParamInterfaces = ( if (!Array.isArray(paramProperty.type) && paramProperty.type.toLowerCase() === 'object') { let argType = - (paramProperty as any).__type || _.upperFirst(_.camelCase(paramProperty.name)); + (paramProperty as any).__type || upperFirst(camelCase(paramProperty.name)); if (API.some((a) => a.name === argType)) { paramProperty.type = argType; debug( chalk.red( - `Auto-correcting type from Object --> ${argType} in Interface: ${_.upperFirst( + `Auto-correcting type from Object --> ${argType} in Interface: ${upperFirst( param.tName, )} --- This should be fixed in the docs`, ), @@ -230,12 +234,12 @@ const flushParamInterfaces = ( paramPropertyType.type.toLowerCase() === 'object' ) { let argType = - (paramProperty as any).__type || _.upperFirst(_.camelCase(paramProperty.name)); + (paramProperty as any).__type || upperFirst(camelCase(paramProperty.name)); if (API.some((a) => a.name === argType)) { paramPropertyType.type = argType; debug( chalk.red( - `Auto-correcting type from Object --> ${argType} in Interface: ${_.upperFirst( + `Auto-correcting type from Object --> ${argType} in Interface: ${upperFirst( param.tName, )} --- This should be fixed in the docs`, ), diff --git a/src/index.ts b/src/index.ts index 23922db..bb47f5b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,6 @@ import fs from 'node:fs'; import path from 'node:path'; import { ParsedDocumentationResult } from '@electron/docs-parser'; -import _ from 'lodash'; import * as utils from './utils.js'; import { getModuleDeclarations, generateModuleDeclaration } from './module-declaration.js'; @@ -30,7 +29,7 @@ const wrapWithHeaderAndFooter = (outputLines: string[], electronVersion: string) .split(/\r?\n/), ); - outputLines.slice(0).forEach((l) => newOutputLines.push(`${_.trimEnd(` ${l}`)}`)); + outputLines.slice(0).forEach((l) => newOutputLines.push(` ${l}`.trimEnd())); utils.extendArray(newOutputLines, ['}', '']); utils.extendArray( diff --git a/src/module-declaration.ts b/src/module-declaration.ts index e988273..36ed0f7 100644 --- a/src/module-declaration.ts +++ b/src/module-declaration.ts @@ -14,10 +14,10 @@ import { DetailedEventType, DetailedEventReferenceType, } from '@electron/docs-parser'; -import _ from 'lodash'; import { DynamicParamInterfaces } from './dynamic-param-interfaces.js'; import * as utils from './utils.js'; +import { upperFirst, camelCase } from './utils.js'; const modules: Record = {}; @@ -30,8 +30,8 @@ export const generateModuleDeclaration = ( index: number, API: ParsedDocumentationResult, ) => { - const moduleAPI = modules[_.upperFirst(module.name)] || []; - const newModule = !modules[_.upperFirst(module.name)]; + const moduleAPI = modules[upperFirst(module.name)] || []; + const newModule = !modules[upperFirst(module.name)]; const instanceModuleForStaticVersion = API.find( (tModule, tIndex) => index !== tIndex && tModule.name.toLowerCase() === module.name.toLowerCase(), @@ -66,16 +66,16 @@ export const generateModuleDeclaration = ( } if (extendsInfo) { moduleAPI.push( - `${isClass ? 'class' : 'interface'} ${_.upperFirst(module.name)}${extendsInfo} {`, + `${isClass ? 'class' : 'interface'} ${upperFirst(module.name)}${extendsInfo} {`, ); moduleAPI.push('', `// Docs: ${module.websiteUrl}`, ''); } else { - moduleAPI.push(`${isClass ? 'class' : 'interface'} ${_.upperFirst(module.name)} {`); + moduleAPI.push(`${isClass ? 'class' : 'interface'} ${upperFirst(module.name)} {`); moduleAPI.push('', `// Docs: ${module.websiteUrl}`, ''); } } else { moduleAPI.push( - `interface ${_.upperFirst(module.name)}${ + `interface ${upperFirst(module.name)}${ module.extends ? ` extends ${module.extends}` : '' } {`, ); @@ -86,13 +86,12 @@ export const generateModuleDeclaration = ( // Event Declaration if (module.type !== 'Element') { // To assist with declaration merging we define all parent events in this class too - _.concat( - [], - module.instanceEvents || [], - module.events || [], - ...parentModules.map((m) => m.events || []), - ...parentModules.map((m) => m.instanceEvents || []), - ) + [ + ...(module.instanceEvents || []), + ...(module.events || []), + ...parentModules.flatMap((m) => m.events || []), + ...parentModules.flatMap((m) => m.instanceEvents || []), + ] .sort((a, b) => a.name.localeCompare(b.name)) .forEach((moduleEvent, i, events) => { utils.extendArray( @@ -103,7 +102,7 @@ export const generateModuleDeclaration = ( if (moduleEvent.parameters) { const args: string[] = []; - const indent = _.repeat(' ', moduleEvent.name.length + 29); + const indent = ' '.repeat(moduleEvent.name.length + 29); moduleEvent.parameters.forEach((eventListenerArg, index) => { let argString = ''; @@ -129,9 +128,9 @@ export const generateModuleDeclaration = ( argType = DynamicParamInterfaces.createParamInterface( objectListenerArg, eventListenerArg.name === 'params' - ? _.upperFirst(_.camelCase(moduleEvent.name)) + ? upperFirst(camelCase(moduleEvent.name)) : undefined, - _.upperFirst(_.camelCase(moduleEvent.name)), + upperFirst(camelCase(moduleEvent.name)), ); } @@ -156,9 +155,7 @@ export const generateModuleDeclaration = ( }; eventParamsType = DynamicParamInterfaces.createParamInterface( fakeObject, - `${_.upperFirst(_.camelCase(module.name))}${_.upperFirst( - _.camelCase(moduleEvent.name), - )}`, + `${upperFirst(camelCase(module.name))}${upperFirst(camelCase(moduleEvent.name))}`, ); } if (eventReferenceListenerArg.eventPropertiesReference) { @@ -229,16 +226,14 @@ export const generateModuleDeclaration = ( domEvent.parameters.forEach((eventListenerProp, index) => { if (eventListenerProp.name === 'result') { - (eventListenerProp as any).__type = `${_.upperFirst( - _.camelCase(domEvent.name), - )}Result`; + (eventListenerProp as any).__type = `${upperFirst(camelCase(domEvent.name))}Result`; } fakeObject.properties.push(eventListenerProp); }); eventType = DynamicParamInterfaces.createParamInterface( fakeObject, - _.upperFirst(_.camelCase(domEvent.name)), + upperFirst(camelCase(domEvent.name)), ); } @@ -300,7 +295,7 @@ export const generateModuleDeclaration = ( if (returnType === 'Object' || (returnType as TypeInformation).type === 'Object') { returnType = DynamicParamInterfaces.createParamInterface( moduleMethod.returns! as any, - _.upperFirst(moduleMethod.name), + upperFirst(moduleMethod.name), ); // The process module is not in the Electron namespace so we need to reference the Electron namespace to use these types @@ -323,7 +318,7 @@ export const generateModuleDeclaration = ( ? '' : `: ${utils.typify( returnType as TypeInformation, - `${_.upperFirst(moduleMethod.name)}ReturnValue`, + `${upperFirst(moduleMethod.name)}ReturnValue`, )}` };`, ); @@ -448,7 +443,7 @@ export const generateModuleDeclaration = ( } // Save moduleAPI for later reuse - modules[_.upperFirst(module.name)] = moduleAPI; + modules[upperFirst(module.name)] = moduleAPI; }; export const getModuleDeclarations = () => modules; diff --git a/src/primary-interfaces.ts b/src/primary-interfaces.ts index e4bda9d..2ba6a21 100644 --- a/src/primary-interfaces.ts +++ b/src/primary-interfaces.ts @@ -1,6 +1,7 @@ import { ParsedDocumentationResult } from '@electron/docs-parser'; import d from 'debug'; -import _ from 'lodash'; + +import { upperFirst } from './utils.js'; const debug = d('primary-interface'); @@ -46,8 +47,8 @@ export const generatePrimaryInterfaces = ( } const moduleString = isClass ? module.process.exported - ? ` class ${_.upperFirst(module.name)} extends Electron.${_.upperFirst(module.name)} {}` - : ` type ${_.upperFirst(module.name)} = Electron.${_.upperFirst(module.name)}` + ? ` class ${upperFirst(module.name)} extends Electron.${upperFirst(module.name)} {}` + : ` type ${upperFirst(module.name)} = Electron.${upperFirst(module.name)}` : ''; const newConstDeclarations: string[] = []; const newTypeAliases: string[] = []; @@ -61,28 +62,24 @@ export const generatePrimaryInterfaces = ( if ((!isClass || module.name !== classify(module.name)) && module.process.exported) { if (isClass) { newConstDeclarations.push( - `type ${classify(module.name)} = ${_.upperFirst(module.name)};`, - `const ${classify(module.name)}: typeof ${_.upperFirst(module.name)};`, + `type ${classify(module.name)} = ${upperFirst(module.name)};`, + `const ${classify(module.name)}: typeof ${upperFirst(module.name)};`, ); } else { if (isModuleButActuallyStaticClass && !isClass) { newConstDeclarations.push( - `const ${classify(module.name)}: typeof ${_.upperFirst(module.name)};`, + `const ${classify(module.name)}: typeof ${upperFirst(module.name)};`, ); } else { - newConstDeclarations.push( - `const ${classify(module.name)}: ${_.upperFirst(module.name)};`, - ); + newConstDeclarations.push(`const ${classify(module.name)}: ${upperFirst(module.name)};`); } newTypeAliases.push( - `type ${_.upperFirst(module.name)} = Electron.${_.upperFirst(module.name)};`, + `type ${upperFirst(module.name)} = Electron.${upperFirst(module.name)};`, ); } } if (module.type === 'Element') { - newTypeAliases.push( - `type ${_.upperFirst(module.name)} = Electron.${_.upperFirst(module.name)};`, - ); + newTypeAliases.push(`type ${upperFirst(module.name)} = Electron.${upperFirst(module.name)};`); } constDeclarations.push(...newConstDeclarations); if (module.process.main && module.process.renderer) { diff --git a/src/remap-optionals.ts b/src/remap-optionals.ts index f7c9360..21e41c0 100644 --- a/src/remap-optionals.ts +++ b/src/remap-optionals.ts @@ -1,7 +1,6 @@ import { ParsedDocumentationResult, MethodDocumentationBlock } from '@electron/docs-parser'; import chalk from 'chalk'; import d from 'debug'; -import _ from 'lodash'; import * as utils from './utils.js'; @@ -17,7 +16,7 @@ export const remapOptionals = (API: ParsedDocumentationResult) => { if (!method.parameters) return; if ((method as any).__handled) return; let optionalFound = false; - _.concat([], method.parameters).forEach((param, index) => { + [...method.parameters].forEach((param, index) => { if (optionalFound && !utils.isOptional(param)) { debug( chalk.cyan( @@ -25,8 +24,8 @@ export const remapOptionals = (API: ParsedDocumentationResult) => { ), ); moreMethods.push( - Object.assign({}, _.cloneDeep(method), { - parameters: [..._.cloneDeep(method.parameters)].filter((tParam, pIndex) => { + Object.assign({}, structuredClone(method), { + parameters: structuredClone(method.parameters).filter((tParam, pIndex) => { if (pIndex >= index) return true; return !utils.isOptional(tParam); }), diff --git a/src/utils.ts b/src/utils.ts index 1ad46b6..0408eae 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,7 +8,6 @@ import { DocumentationTag, ParsedDocumentationResult, } from '@electron/docs-parser'; -import _ from 'lodash'; import d from 'debug'; import { DynamicParamInterfaces } from './dynamic-param-interfaces.js'; @@ -21,6 +20,21 @@ export const setParamInterfaces = (provided: typeof DynamicParamInterfaces) => { paramInterfaces = provided; }; +export const upperFirst = (s?: string): string => (s ? s[0].toUpperCase() + s.slice(1) : ''); + +export const lowerFirst = (s?: string): string => (s ? s[0].toLowerCase() + s.slice(1) : ''); + +export const camelCase = (s?: string): string => { + if (!s) return ''; + return s + .replace(/([a-z])([A-Z])/g, '$1 $2') + .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2') + .split(/[^a-zA-Z0-9]+/) + .filter(Boolean) + .map((w, i) => (i === 0 ? w.toLowerCase() : upperFirst(w.toLowerCase()))) + .join(''); +}; + export const extendArray = (arr1: T[], arr2: T[]): T[] => { arr1.push(...arr2); return arr1; @@ -350,25 +364,25 @@ export const genMethodString = ( ) { return paramInterfaces.createParamInterface( objectParam, - _.upperFirst(module.name) + _.upperFirst(moduleMethod.name), + upperFirst(module.name) + upperFirst(moduleMethod.name), ); } - return paramInterfaces.createParamInterface(objectParam, _.upperFirst(moduleMethod.name)); + return paramInterfaces.createParamInterface(objectParam, upperFirst(moduleMethod.name)); } if (['set', 'get'].includes(moduleMethod.name.toLowerCase())) { return paramInterfaces.createParamInterface( objectParam, - _.upperFirst(module.name) + _.upperFirst(moduleMethod.name), + upperFirst(module.name) + upperFirst(moduleMethod.name), ); } return paramInterfaces.createParamInterface( objectParam, '', - _.upperFirst(moduleMethod.name), - topLevelModuleMethod ? _.upperFirst(topLevelModuleMethod.name) : '', + upperFirst(moduleMethod.name), + topLevelModuleMethod ? upperFirst(topLevelModuleMethod.name) : '', ); }; return `${includeType ? '(' : ''}${(moduleMethod.parameters || []) @@ -395,7 +409,7 @@ export const genMethodString = ( paramInterfaces, module, { - name: _.upperFirst(moduleMethod.name) + _.upperFirst(param.name), + name: upperFirst(moduleMethod.name) + upperFirst(param.name), ...functionParam, } as any /* FIXME: */, true, diff --git a/test/remap-optionals.spec.ts b/test/remap-optionals.spec.ts index 1bd476e..4087b75 100644 --- a/test/remap-optionals.spec.ts +++ b/test/remap-optionals.spec.ts @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { describe, expect, it } from 'vitest'; import { remapOptionals } from '../src/remap-optionals.js'; @@ -64,35 +63,35 @@ const middleAPI = [ describe('remap-optionals', () => { it('should duplicate a method with a preceeding optional parameter', () => { - const API = _.cloneDeep(fakeAPI); + const API = structuredClone(fakeAPI); expect(API[0].methods.length).toEqual(2); remapOptionals(API as any); expect(API[0].methods.length).toEqual(3); }); it('should duplicate methods and remove preceeding optional parameters', () => { - const API = _.cloneDeep(fakeAPI); + const API = structuredClone(fakeAPI); expect(API[0].methods[0].parameters.length).toEqual(2); remapOptionals(API as any); expect(API[0].methods[2].parameters.length).toEqual(1); }); it('should make the original method legal by making param non-optional', () => { - const API = _.cloneDeep(fakeAPI); + const API = structuredClone(fakeAPI); expect(API[0].methods[0].parameters[0].description).toContain('optional'); remapOptionals(API as any); expect(API[0].methods[0].parameters[0].description).not.toContain('optional'); }); it('should not affect legal parameter orders', () => { - const API = _.cloneDeep(fakeAPI); - const methodBefore = _.cloneDeep(API[0].methods[1]); + const API = structuredClone(fakeAPI); + const methodBefore = structuredClone(API[0].methods[1]); remapOptionals(API as any); expect(API[0].methods[1]).toEqual(methodBefore); }); it('should remap optional middle params', () => { - const API = _.cloneDeep(middleAPI); + const API = structuredClone(middleAPI); expect(API[0].methods.length).toEqual(1); expect(API[0].methods[0].parameters.length).toEqual(3); remapOptionals(API as any); diff --git a/test/utils.spec.ts b/test/utils.spec.ts index 1c7be48..9ab1a32 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -4,6 +4,69 @@ import { describe, expect, it } from 'vitest'; import * as utils from '../src/utils.js'; describe('utils', () => { + describe('upperFirst', () => { + it('should uppercase the first character', () => { + expect(utils.upperFirst('foo')).toEqual('Foo'); + }); + + it('should leave the rest of the string untouched', () => { + expect(utils.upperFirst('fooBAR')).toEqual('FooBAR'); + }); + + it('should leave already-uppercase strings unchanged', () => { + expect(utils.upperFirst('Foo')).toEqual('Foo'); + }); + + it('should return an empty string for empty or undefined input', () => { + expect(utils.upperFirst('')).toEqual(''); + expect(utils.upperFirst(undefined)).toEqual(''); + }); + }); + + describe('lowerFirst', () => { + it('should lowercase the first character', () => { + expect(utils.lowerFirst('Foo')).toEqual('foo'); + }); + + it('should leave the rest of the string untouched', () => { + expect(utils.lowerFirst('FooBAR')).toEqual('fooBAR'); + }); + + it('should return an empty string for empty or undefined input', () => { + expect(utils.lowerFirst('')).toEqual(''); + expect(utils.lowerFirst(undefined)).toEqual(''); + }); + }); + + describe('camelCase', () => { + it('should convert kebab-case to camelCase', () => { + expect(utils.camelCase('did-finish-load')).toEqual('didFinishLoad'); + expect(utils.camelCase('window-all-closed')).toEqual('windowAllClosed'); + }); + + it('should convert snake_case to camelCase', () => { + expect(utils.camelCase('foo_bar_baz')).toEqual('fooBarBaz'); + }); + + it('should lowercase the first character of an already camelCased string', () => { + expect(utils.camelCase('authInfo')).toEqual('authInfo'); + expect(utils.camelCase('WebContents')).toEqual('webContents'); + }); + + it('should split runs of uppercase letters', () => { + expect(utils.camelCase('IPCMain')).toEqual('ipcMain'); + }); + + it('should pass through single lowercase words', () => { + expect(utils.camelCase('params')).toEqual('params'); + }); + + it('should return an empty string for empty or undefined input', () => { + expect(utils.camelCase('')).toEqual(''); + expect(utils.camelCase(undefined)).toEqual(''); + }); + }); + describe('extendArray', () => { it('should return an array with all elements added correctly', () => { expect(utils.extendArray(['foo'], ['bar'])).toEqual(['foo', 'bar']); diff --git a/yarn.lock b/yarn.lock index db9fc8f..243b5dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,13 +29,11 @@ __metadata: dependencies: "@electron/docs-parser": "npm:^2.0.0" "@types/debug": "npm:^4.1.12" - "@types/lodash": "npm:^4.17.7" "@types/node": "npm:^20.11.25" chalk: "npm:^5.3.0" debug: "npm:^4.3.7" husky: "npm:^9.1.6" lint-staged: "npm:^16.2.7" - lodash: "npm:^4.17.11" ora: "npm:^8.1.0" prettier: "npm:^3.3.3" pretty-ms: "npm:^9.1.0" @@ -269,13 +267,6 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:^4.17.7": - version: 4.17.7 - resolution: "@types/lodash@npm:4.17.7" - checksum: 10c0/40c965b5ffdcf7ff5c9105307ee08b782da228c01b5c0529122c554c64f6b7168fc8f11dc79aa7bae4e67e17efafaba685dc3a47e294dbf52a65ed2b67100561 - languageName: node - linkType: hard - "@types/markdown-it@npm:^14.1.2": version: 14.1.2 resolution: "@types/markdown-it@npm:14.1.2" @@ -865,13 +856,6 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.11": - version: 4.17.23 - resolution: "lodash@npm:4.17.23" - checksum: 10c0/1264a90469f5bb95d4739c43eb6277d15b6d9e186df4ac68c3620443160fc669e2f14c11e7d8b2ccf078b81d06147c01a8ccced9aab9f9f63d50dcf8cace6bf6 - languageName: node - linkType: hard - "log-symbols@npm:^6.0.0": version: 6.0.0 resolution: "log-symbols@npm:6.0.0"