From 4297b87b626c6b0c510aacdcb4cb668809e46f51 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Thu, 25 Nov 2021 20:56:40 +0100 Subject: [PATCH] feat(admin-ui): Add json editor field input component --- packages/admin-ui/package.json | 1 + .../json-editor-form-input.component.html | 4 + .../json-editor-form-input.component.scss | 44 ++++++ .../json-editor-form-input.component.ts | 129 ++++++++++++++++++ .../register-dynamic-input-components.ts | 2 + .../src/lib/core/src/shared/shared.module.ts | 2 + .../src/lib/static/styles/theme/dark.scss | 9 ++ .../src/lib/static/styles/theme/default.scss | 8 ++ packages/common/src/shared-types.ts | 47 ++++--- yarn.lock | 19 ++- 10 files changed, 243 insertions(+), 22 deletions(-) create mode 100644 packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.html create mode 100644 packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.scss create mode 100644 packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.ts diff --git a/packages/admin-ui/package.json b/packages/admin-ui/package.json index 13d7ebd460..236c23956e 100644 --- a/packages/admin-ui/package.json +++ b/packages/admin-ui/package.json @@ -43,6 +43,7 @@ "@webcomponents/custom-elements": "^1.4.3", "apollo-angular": "^2.4.0", "apollo-upload-client": "^14.1.3", + "codejar": "^3.5.0", "core-js": "^3.9.1", "dayjs": "^1.10.4", "graphql": "15.5.1", diff --git a/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.html b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.html new file mode 100644 index 0000000000..d640596a6f --- /dev/null +++ b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.html @@ -0,0 +1,4 @@ +
+
+ {{ errorMessage }} +
diff --git a/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.scss b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.scss new file mode 100644 index 0000000000..cf00b68cb1 --- /dev/null +++ b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.scss @@ -0,0 +1,44 @@ +.json-editor { + min-height: 6rem; + background-color: var(--color-json-editor-background-color); + color: var(--color-json-editor-text); + border: 1px solid var(--color-component-border-200); + border-radius: 3px; + padding: 6px; + tab-size: 4; + font-family: 'Source Code Pro', 'Lucida Console', Monaco, monospace; + font-size: 14px; + font-weight: 400; + height: 340px; + letter-spacing: normal; + line-height: 20px; + + &:focus { + border-color: var(--color-primary-500); + } + + &.invalid { + border-color: var(--clr-forms-invalid-color); + } + + // prettier-ignore + ::ng-deep { + .je-string { color: var(--color-json-editor-string); } + .je-number { color: var(--color-json-editor-number); } + .je-boolean { color: var(--color-json-editor-boolean); } + .je-null { color: var(--color-json-editor-null); } + .je-key { color: var(--color-json-editor-key); } + .je-error { + text-decoration-line: underline; + text-decoration-style: wavy; + text-decoration-color: var(--color-json-editor-error); + } + } +} + +.error-message { + min-height: 1rem; + color: var(--color-json-editor-error); +} + + diff --git a/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.ts b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.ts new file mode 100644 index 0000000000..64478b9c8f --- /dev/null +++ b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.ts @@ -0,0 +1,129 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + OnInit, + ViewChild, +} from '@angular/core'; +import { AbstractControl, FormControl, ValidationErrors, ValidatorFn } from '@angular/forms'; +import { DefaultFormComponentConfig, DefaultFormComponentId } from '@vendure/common/lib/shared-types'; +import { CodeJar } from 'codejar'; + +import { FormInputComponent } from '../../../common/component-registry-types'; + +export function jsonValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const error: ValidationErrors = { jsonInvalid: true }; + + try { + JSON.parse(control.value); + } catch (e) { + control.setErrors(error); + return error; + } + + control.setErrors(null); + return null; + }; +} + +@Component({ + selector: 'vdr-json-editor-form-input', + templateUrl: './json-editor-form-input.component.html', + styleUrls: ['./json-editor-form-input.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class JsonEditorFormInputComponent implements FormInputComponent, AfterViewInit, OnInit { + static readonly id: DefaultFormComponentId = 'json-editor-form-input'; + readonly: boolean; + formControl: FormControl; + config: DefaultFormComponentConfig<'json-editor-form-input'>; + isValid = true; + height: DefaultFormComponentConfig<'json-editor-form-input'>['height']; + errorMessage: string | undefined; + @ViewChild('editor') private editorElementRef: ElementRef; + jar: CodeJar; + + constructor(private changeDetector: ChangeDetectorRef) {} + + ngOnInit() { + this.formControl.addValidators(jsonValidator()); + } + + ngAfterViewInit() { + let lastVal = ''; + const highlight = (editor: HTMLElement) => { + const code = editor.textContent ?? ''; + if (code === lastVal) { + return; + } + lastVal = code; + this.errorMessage = this.getJsonError(code); + this.changeDetector.markForCheck(); + editor.innerHTML = this.syntaxHighlight(code, this.getErrorPos(this.errorMessage)); + }; + this.jar = CodeJar(this.editorElementRef.nativeElement, highlight); + this.jar.onUpdate(value => { + this.formControl.setValue(value); + this.formControl.markAsDirty(); + this.isValid = this.formControl.valid; + }); + this.jar.updateCode(this.formControl.value); + + if (this.readonly) { + this.editorElementRef.nativeElement.contentEditable = 'false'; + } + } + + private getJsonError(json: string): string | undefined { + try { + JSON.parse(json); + } catch (e) { + return e.message; + } + return; + } + + private getErrorPos(errorMessage: string | undefined): number | undefined { + if (!errorMessage) { + return; + } + const matches = errorMessage.match(/at position (\d+)/); + const pos = matches?.[1]; + return pos != null ? +pos : undefined; + } + + private syntaxHighlight(json: string, errorPos: number | undefined) { + json = json.replace(/&/g, '&').replace(//g, '>'); + let hasMarkedError = false; + return json.replace( + /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, + (match, ...args) => { + let cls = 'number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'key'; + } else { + cls = 'string'; + } + } else if (/true|false/.test(match)) { + cls = 'boolean'; + } else if (/null/.test(match)) { + cls = 'null'; + } + let errorClass = ''; + if (errorPos && !hasMarkedError) { + const length = args[0].length; + const offset = args[4]; + if (errorPos <= length + offset) { + errorClass = 'je-error'; + hasMarkedError = true; + } + } + return '' + match + ''; + }, + ); + } +} diff --git a/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/register-dynamic-input-components.ts b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/register-dynamic-input-components.ts index f553ba6237..cec51b1d00 100644 --- a/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/register-dynamic-input-components.ts +++ b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/register-dynamic-input-components.ts @@ -9,6 +9,7 @@ import { } from '../../providers/custom-field-component/custom-field-component.service'; import { BooleanFormInputComponent } from './boolean-form-input/boolean-form-input.component'; +import { JsonEditorFormInputComponent } from './code-editor-form-input/json-editor-form-input.component'; import { CurrencyFormInputComponent } from './currency-form-input/currency-form-input.component'; import { CustomerGroupFormInputComponent } from './customer-group-form-input/customer-group-form-input.component'; import { DateFormInputComponent } from './date-form-input/date-form-input.component'; @@ -36,6 +37,7 @@ export const defaultFormInputs = [ RelationFormInputComponent, TextareaFormInputComponent, RichTextFormInputComponent, + JsonEditorFormInputComponent, ]; /** diff --git a/packages/admin-ui/src/lib/core/src/shared/shared.module.ts b/packages/admin-ui/src/lib/core/src/shared/shared.module.ts index 719ad59581..e340ffe4d9 100644 --- a/packages/admin-ui/src/lib/core/src/shared/shared.module.ts +++ b/packages/admin-ui/src/lib/core/src/shared/shared.module.ts @@ -83,6 +83,7 @@ import { IfDefaultChannelActiveDirective } from './directives/if-default-channel import { IfMultichannelDirective } from './directives/if-multichannel.directive'; import { IfPermissionsDirective } from './directives/if-permissions.directive'; import { BooleanFormInputComponent } from './dynamic-form-inputs/boolean-form-input/boolean-form-input.component'; +import { JsonEditorFormInputComponent } from './dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component'; import { CurrencyFormInputComponent } from './dynamic-form-inputs/currency-form-input/currency-form-input.component'; import { CustomerGroupFormInputComponent } from './dynamic-form-inputs/customer-group-form-input/customer-group-form-input.component'; import { DateFormInputComponent } from './dynamic-form-inputs/date-form-input/date-form-input.component'; @@ -244,6 +245,7 @@ const DYNAMIC_FORM_INPUTS = [ RelationSelectorDialogComponent, TextareaFormInputComponent, RichTextFormInputComponent, + JsonEditorFormInputComponent, ]; @NgModule({ diff --git a/packages/admin-ui/src/lib/static/styles/theme/dark.scss b/packages/admin-ui/src/lib/static/styles/theme/dark.scss index 2b15075b70..77ea202162 100644 --- a/packages/admin-ui/src/lib/static/styles/theme/dark.scss +++ b/packages/admin-ui/src/lib/static/styles/theme/dark.scss @@ -36,6 +36,15 @@ --color-form-input-bg: hsl(212, 35%, 95%); --color-timeline-thread: var(--color-primary-700); + --color-json-editor-background-color: var(--color-grey-600); + --color-json-editor-text: var(--color-grey-100); + --color-json-editor-string: var(--color-secondary-300); + --color-json-editor-number: var(--color-primary-300); + --color-json-editor-boolean: var(--color-primary-300); + --color-json-editor-null: var(--color-grey-300-grey-500); + --color-json-editor-key: var(--color-success-300); + --color-json-editor-error: var(--color-error-200); + // clarity styles --clr-global-app-background: hsl(201, 30%, 15%); --clr-global-selection-color: hsl(203, 32%, 29%); diff --git a/packages/admin-ui/src/lib/static/styles/theme/default.scss b/packages/admin-ui/src/lib/static/styles/theme/default.scss index e96ccf9a8d..e40df72eb7 100644 --- a/packages/admin-ui/src/lib/static/styles/theme/default.scss +++ b/packages/admin-ui/src/lib/static/styles/theme/default.scss @@ -83,6 +83,14 @@ --color-chip-error-border: var(--color-error-200); --color-chip-error-text: var(--color-error-600); --color-chip-error-bg: var(--color-error-100); + --color-json-editor-background-color: var(--color-grey-200); + --color-json-editor-text: var(--color-grey-600); + --color-json-editor-string: var(--color-secondary-600); + --color-json-editor-number: var(--color-primary-600); + --color-json-editor-boolean: var(--color-primary-600); + --color-json-editor-null: var(--color-grey-500); + --color-json-editor-key: var(--color-success-500); + --color-json-editor-error: var(--color-error-500); // Other variables --login-page-bg: url(data:image/gif;base64,R0lGODlhFAAhAKIAABwcHBsbGxcXFx0dHRUVFRkZGR4eHhQUFCH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4zLWMwMTEgNjYuMTQ2NzI5LCAyMDEyLzA1LzAzLTEzOjQwOjAzICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTIuMCBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjhBOTg1NDlCNjMwNDExRUFBNEUwQ0M2RDdENUQ3RTBFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjhBOTg1NDlDNjMwNDExRUFBNEUwQ0M2RDdENUQ3RTBFIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OEE5ODU0OTk2MzA0MTFFQUE0RTBDQzZEN0Q1RDdFMEUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OEE5ODU0OUE2MzA0MTFFQUE0RTBDQzZEN0Q1RDdFMEUiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQAAAAAACwAAAAAFAAhAAADfSi23M7igEcZOIfUSvApm1N42RAuQ0cqZ0Ri0xa8nlaltAdSY44RJsfAt3q4iLDGDFlj4Ji6RQ/6GwypqyOtwO12BQKv+EawnRq93dlwLa0NWtZpSYptniRzZfpSP9o0QBVaNHJKUHYoKkh6BnxIaoBMgnBYGAp0lgCLlgQJADs=); diff --git a/packages/common/src/shared-types.ts b/packages/common/src/shared-types.ts index f35f592d1b..88275af4fc 100644 --- a/packages/common/src/shared-types.ts +++ b/packages/common/src/shared-types.ts @@ -132,17 +132,18 @@ export type ConfigArgType = 'string' | 'int' | 'float' | 'boolean' | 'datetime' export type DefaultFormComponentId = | 'boolean-form-input' | 'currency-form-input' + | 'customer-group-form-input' | 'date-form-input' | 'facet-value-form-input' + | 'json-editor-form-input' | 'number-form-input' - | 'select-form-input' + | 'password-form-input' | 'product-selector-form-input' - | 'customer-group-form-input' - | 'text-form-input' - | 'textarea-form-input' + | 'relation-form-input' | 'rich-text-form-input' - | 'password-form-input' - | 'relation-form-input'; + | 'select-form-input' + | 'text-form-input' + | 'textarea-form-input'; /** * @description @@ -151,40 +152,46 @@ export type DefaultFormComponentId = * @docsCategory ConfigurableOperationDef */ type DefaultFormConfigHash = { + 'boolean-form-input': {}; + 'currency-form-input': {}; + 'customer-group-form-input': {}; 'date-form-input': { min?: string; max?: string; yearRange?: number }; + 'facet-value-form-input': {}; + 'json-editor-form-input': { height?: string }; 'number-form-input': { min?: number; max?: number; step?: number; prefix?: string; suffix?: string }; + 'password-form-input': {}; + 'product-selector-form-input': {}; + 'relation-form-input': {}; + 'rich-text-form-input': {}; 'select-form-input': { options?: Array<{ value: string; label?: Array> }>; }; - 'boolean-form-input': {}; - 'currency-form-input': {}; - 'facet-value-form-input': {}; - 'product-selector-form-input': {}; - 'customer-group-form-input': {}; 'text-form-input': {}; 'textarea-form-input': { spellcheck?: boolean; }; - 'rich-text-form-input': {}; - 'password-form-input': {}; - 'relation-form-input': {}; }; export type DefaultFormComponentConfig = DefaultFormConfigHash[T]; export type UiComponentConfig = - | ({ component: 'number-form-input' } & DefaultFormComponentConfig<'number-form-input'>) - | ({ component: 'date-form-input' } & DefaultFormComponentConfig<'date-form-input'>) - | ({ component: 'select-form-input' } & DefaultFormComponentConfig<'select-form-input'>) - | ({ component: 'text-form-input' } & DefaultFormComponentConfig<'text-form-input'>) | ({ component: 'boolean-form-input' } & DefaultFormComponentConfig<'boolean-form-input'>) | ({ component: 'currency-form-input' } & DefaultFormComponentConfig<'currency-form-input'>) + | ({ component: 'customer-group-form-input' } & DefaultFormComponentConfig<'customer-group-form-input'>) + | ({ component: 'date-form-input' } & DefaultFormComponentConfig<'date-form-input'>) | ({ component: 'facet-value-form-input' } & DefaultFormComponentConfig<'facet-value-form-input'>) + | ({ component: 'json-editor-form-input' } & DefaultFormComponentConfig<'json-editor-form-input'>) + | ({ component: 'number-form-input' } & DefaultFormComponentConfig<'number-form-input'>) + | ({ component: 'password-form-input' } & DefaultFormComponentConfig<'password-form-input'>) | ({ component: 'product-selector-form-input'; } & DefaultFormComponentConfig<'product-selector-form-input'>) - | ({ component: 'customer-group-form-input' } & DefaultFormComponentConfig<'customer-group-form-input'>) - | { component: string; [prop: string]: Json }; + | ({ component: 'relation-form-input' } & DefaultFormComponentConfig<'relation-form-input'>) + | ({ component: 'rich-text-form-input' } & DefaultFormComponentConfig<'rich-text-form-input'>) + | ({ component: 'select-form-input' } & DefaultFormComponentConfig<'select-form-input'>) + | ({ component: 'text-form-input' } & DefaultFormComponentConfig<'text-form-input'>) + | ({ component: 'textarea-form-input' } & DefaultFormComponentConfig<'textarea-form-input'>) + | { component: string; [prop: string]: any }; export type CustomFieldsObject = { [key: string]: any }; diff --git a/yarn.lock b/yarn.lock index c1cc34dfce..49285bd461 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6584,6 +6584,11 @@ code-point-at@^1.0.0: resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +codejar@^3.5.0: + version "3.5.0" + resolved "https://registry.npmjs.org/codejar/-/codejar-3.5.0.tgz#be3a6a77b4c422998e56710ca854d166f8507eb2" + integrity sha512-uXrFZZ+yb23YY7+WtTux2Yyokt+Lty/kBnW/OhhEGp8IW8/lrJw5Gs1wwCyt2vpMfsVdudLmV5xAgYqsZY/49A== + codelyzer@^6.0.0: version "6.0.2" resolved "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.2.tgz#25d72eae641e8ff13ffd7d99b27c9c7ad5d7e135" @@ -10120,7 +10125,7 @@ ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore-walk@^3.0.1: +ignore-walk@^3.0.1, ignore-walk@^3.0.3: version "3.0.4" resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== @@ -13844,7 +13849,7 @@ npm-package-arg@8.1.5, npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-packa semver "^7.3.4" validate-npm-package-name "^3.0.0" -npm-packlist@1.1.12, npm-packlist@^1.1.6, npm-packlist@^2.1.4: +npm-packlist@^1.1.6: version "1.1.12" resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g== @@ -13852,6 +13857,16 @@ npm-packlist@1.1.12, npm-packlist@^1.1.6, npm-packlist@^2.1.4: ignore-walk "^3.0.1" npm-bundled "^1.0.1" +npm-packlist@^2.1.4: + version "2.2.2" + resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" + integrity sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg== + dependencies: + glob "^7.1.6" + ignore-walk "^3.0.3" + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + npm-pick-manifest@6.1.1, npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.1: version "6.1.1" resolved "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148"