From a317582b0de15bb949e013af8820591ebf545976 Mon Sep 17 00:00:00 2001 From: Ivo Murrell Date: Fri, 22 Apr 2022 11:46:25 +0100 Subject: [PATCH 01/16] feat(box): initial experimentation with React and ink Created a prototype Tool Kit TUI for innovation day. --- core/box/package.json | 40 +++ core/box/src/index.tsx | 112 +++++++ core/box/tsconfig.json | 9 + package-lock.json | 659 +++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 3 + 5 files changed, 823 insertions(+) create mode 100644 core/box/package.json create mode 100644 core/box/src/index.tsx create mode 100644 core/box/tsconfig.json diff --git a/core/box/package.json b/core/box/package.json new file mode 100644 index 000000000..013a37dd3 --- /dev/null +++ b/core/box/package.json @@ -0,0 +1,40 @@ +{ + "name": "@dotcom-tool-kit/box", + "version": "0.0.0-development", + "description": "", + "main": "lib", + "bin": "lib/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "FT.com Platforms Team ", + "license": "ISC", + "dependencies": { + "@dotcom-tool-kit/logger": "file:../../lib/logger", + "dotcom-tool-kit": "file:../cli", + "ink": "^3.2.0", + "ink-big-text": "^1.2.0", + "react": "^17.0.2", + "winston": "^3.6.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/financial-times/dotcom-tool-kit.git", + "directory": "core/box" + }, + "bugs": "https://github.com/financial-times/dotcom-tool-kit/issues", + "homepage": "https://github.com/financial-times/dotcom-tool-kit/tree/main/core/box", + "files": [ + "/lib", + ".toolkitrc.yml" + ], + "volta": { + "extends": "../../package.json" + }, + "devDependencies": { + "@dotcom-tool-kit/types": "file:../../lib/types", + "@types/ink-big-text": "^1.2.1", + "@types/react": "^17.0.39" + } +} diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx new file mode 100644 index 000000000..ee3209bbb --- /dev/null +++ b/core/box/src/index.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react' +import { render, Box, Text, useInput, BoxProps } from 'ink' +import BigText from 'ink-big-text' +import winston from 'winston' +import { loadConfig } from 'dotcom-tool-kit/lib/config' +import { styles } from '@dotcom-tool-kit/logger' +import type { Plugin } from '@dotcom-tool-kit/types' + +const Title = () => + +interface SelectableBoxProps extends BoxProps { + selected: boolean +} +const SelectableBox = (props: React.PropsWithChildren) => { + const { selected, children, ...rest } = props + return ( + + {children} + + ) +} + +interface ListProps { + items: string[] + selected: boolean + cursor: number +} + +const List = (props: ListProps) => ( + + {props.items.map((item, index) => ( + + {item} + + ))} + +) + +interface PluginDetailsProps { + plugin: Plugin + selected: boolean +} +const PluginDetails = (props: PluginDetailsProps) => ( + + Included by {styles.plugin(props.plugin.parent!.id)} + {props.plugin.hooks && Object.keys(props.plugin.hooks).length > 0 && ( + <> + Defines the following hooks: + {Object.keys(props.plugin.hooks).map((hook) => ( + - {styles.hook(hook)} + ))} + + )} + {props.plugin.tasks && props.plugin.tasks.length > 0 && ( + <> + Defines the following tasks: + {props.plugin.tasks.map((task) => ( + - {styles.task(task.id!)} + ))} + + )} + +) + +interface PluginsViewProps { + plugins: [string, Plugin][] +} + +const PluginsView = (props: PluginsViewProps) => { + const [cursor, setCursor] = useState(0) + const [detailsSelected, setDetailsSelected] = useState(false) + useInput((input, key) => { + const maxCursor = props.plugins.length - 1 + if (!detailsSelected && (key.downArrow || input === 'j')) { + setCursor(cursor === maxCursor ? 0 : cursor + 1) + } + if (!detailsSelected && (key.upArrow || input === 'k')) { + setCursor(cursor === 0 ? maxCursor : cursor - 1) + } + if (!detailsSelected && (key.return || key.rightArrow || input === 'l')) { + setDetailsSelected(true) + } + if (detailsSelected && (key.escape || key.leftArrow || input === 'h')) { + setDetailsSelected(false) + } + }) + return ( + + styles.plugin(id))} + selected={!detailsSelected} + cursor={cursor} + /> + + + ) +} + +async function main() { + const logger = winston.createLogger({ silent: true }) + + const config = await loadConfig(logger) + + render( + <> + + <PluginsView plugins={Object.entries(config.plugins)} /> + </> + ) +} + +main() diff --git a/core/box/tsconfig.json b/core/box/tsconfig.json new file mode 100644 index 000000000..748508250 --- /dev/null +++ b/core/box/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.settings.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src", + "jsx": "react" + }, + "references": [{ "path": "../cli" }, { "path": "../../lib/logger" }, { "path": "../../lib/types" }] +} diff --git a/package-lock.json b/package-lock.json index da4aac3ac..e5ab3ac4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,26 @@ "npm": "7.x || 8.x" } }, + "core/box": { + "version": "0.0.0-development", + "license": "ISC", + "dependencies": { + "@dotcom-tool-kit/logger": "file:../../lib/logger", + "dotcom-tool-kit": "file:../cli", + "ink": "^3.2.0", + "ink-big-text": "^1.2.0", + "react": "^17.0.2", + "winston": "^3.6.0" + }, + "bin": { + "box": "lib/index.js" + }, + "devDependencies": { + "@dotcom-tool-kit/types": "file:../../lib/types", + "@types/ink-big-text": "^1.2.1", + "@types/react": "^17.0.39" + } + }, "core/cli": { "name": "dotcom-tool-kit", "version": "2.3.6", @@ -2228,6 +2248,10 @@ "resolved": "plugins/backend-app", "link": true }, + "node_modules/@dotcom-tool-kit/box": { + "resolved": "core/box", + "link": true + }, "node_modules/@dotcom-tool-kit/circleci": { "resolved": "plugins/circleci", "link": true @@ -3999,6 +4023,15 @@ "@types/node": "*" } }, + "node_modules/@types/ink-big-text": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/ink-big-text/-/ink-big-text-1.2.1.tgz", + "integrity": "sha512-2J+vxIcVzYkQrWfgqDorcui0ueF8g7j2BgQ+iYSsRkXi8/B3mSRM9fNL4jN3UOPWC2upfDssrumG2bOkRs6aXA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "license": "MIT" @@ -4194,6 +4227,12 @@ "@types/node": "*" } }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "devOptional": true + }, "node_modules/@types/puppeteer": { "version": "3.0.8", "dev": true, @@ -4202,6 +4241,17 @@ "@types/node": "*" } }, + "node_modules/@types/react": { + "version": "17.0.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.48.tgz", + "integrity": "sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A==", + "devOptional": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/request": { "version": "2.48.8", "license": "MIT", @@ -4228,6 +4278,12 @@ "version": "0.12.0", "license": "MIT" }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "devOptional": true + }, "node_modules/@types/semver": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", @@ -4297,6 +4353,11 @@ "@types/node": "*" } }, + "node_modules/@types/yoga-layout": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", + "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "4.33.0", "dev": true, @@ -5180,6 +5241,17 @@ "node": ">= 4.5.0" } }, + "node_modules/auto-bind": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", + "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/aws-sdk": { "version": "2.1128.0", "license": "Apache-2.0", @@ -5957,6 +6029,21 @@ "version": "0.12.0", "license": "Apache-2.0" }, + "node_modules/cfonts": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/cfonts/-/cfonts-2.10.1.tgz", + "integrity": "sha512-l5IcLv4SaOdL/EGR6BpOF5SEro88VcGJJ6+xbvJb+wXi19YC6UeHE/brv7a4vIcLZopnt3Ys3zWeNnyfB04UPg==", + "dependencies": { + "chalk": "^4", + "window-size": "^1.1.1" + }, + "bin": { + "cfonts": "bin/index.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/chai": { "version": "4.3.6", "dev": true, @@ -6367,6 +6454,17 @@ "node": ">= 0.12.0" } }, + "node_modules/code-excerpt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-3.0.0.tgz", + "integrity": "sha512-VHNTVhd7KsLGOqfX3SyeO8RyYPMp1GJOg194VITk04WMYCv4plV68YWe6TJZxd9MhobjtpMRnVky01gqZsalaw==", + "dependencies": { + "convert-to-spaces": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/code-point-at": { "version": "1.1.0", "dev": true, @@ -6747,6 +6845,14 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/convert-to-spaces": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz", + "integrity": "sha512-cj09EBuObp9gZNQCzc7hByQyrs6jVGE+o9kSJmeUoj+GiPiJvi5LYqEH/Hmme4+MTLHM+Ejtq+FChpjjEnsPdQ==", + "engines": { + "node": ">= 4" + } + }, "node_modules/cookie": { "version": "0.3.1", "license": "MIT", @@ -7098,6 +7204,12 @@ "version": "0.3.8", "license": "MIT" }, + "node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "devOptional": true + }, "node_modules/cwise-compiler": { "version": "1.1.3", "license": "MIT", @@ -10207,6 +10319,91 @@ "version": "1.3.8", "license": "ISC" }, + "node_modules/ink": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", + "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "auto-bind": "4.0.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "code-excerpt": "^3.0.0", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "lodash": "^4.17.20", + "patch-console": "^1.0.0", + "react-devtools-core": "^4.19.1", + "react-reconciler": "^0.26.2", + "scheduler": "^0.20.2", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "stack-utils": "^2.0.2", + "string-width": "^4.2.2", + "type-fest": "^0.12.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "ws": "^7.5.5", + "yoga-layout-prebuilt": "^1.9.6" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": ">=16.8.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/ink-big-text": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ink-big-text/-/ink-big-text-1.2.0.tgz", + "integrity": "sha512-xDfn8oOhiji9c4wojTKSaBnEfgpTTd3KL7jsMYVht4SbpfLdSKvVZiMi3U5v45eSjLm1ycMmeMWAP1G99lWL5Q==", + "dependencies": { + "cfonts": "^2.8.6", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "peerDependencies": { + "ink": ">=2.0.0", + "react": ">=16.8.0" + } + }, + "node_modules/ink/node_modules/type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/inquirer": { "version": "7.3.3", "license": "MIT", @@ -12353,6 +12550,17 @@ "version": "0.8.4", "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "2.3.4", "dev": true, @@ -15207,6 +15415,14 @@ "node": ">=0.10.0" } }, + "node_modules/patch-console": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-1.0.0.tgz", + "integrity": "sha512-nxl9nrnLQmh64iTzMfyylSlRozL7kAXIaxw1fVcLYdyhNkJCRUzirRZTikXGJsg+hc4fqpneTK6iU2H1Q8THSA==", + "engines": { + "node": ">=10" + } + }, "node_modules/path": { "version": "0.12.7", "license": "MIT", @@ -15489,6 +15705,21 @@ "node": ">= 6" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/protocolify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/protocolify/-/protocolify-3.0.0.tgz", @@ -15764,10 +15995,47 @@ "node": ">=0.10.0" } }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.25.0.tgz", + "integrity": "sha512-iewRrnu0ZnmfL+jJayKphXj04CFh6i3ezVnpCtcnZbTPSQgN09XqHAzXbKbqNDl7aTg9QLNkQRP6M3DvdrinWA==", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, "node_modules/react-is": { "version": "17.0.2", "license": "MIT" }, + "node_modules/react-reconciler": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", + "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^17.0.2" + } + }, "node_modules/read-package-json": { "version": "5.0.1", "license": "ISC", @@ -16566,6 +16834,15 @@ "node": ">= 0.10.0" } }, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "node_modules/schema-utils": { "version": "1.0.0", "license": "MIT", @@ -16740,6 +17017,11 @@ "node": ">=0.10.0" } }, + "node_modules/shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" + }, "node_modules/side-channel": { "version": "1.0.4", "license": "MIT", @@ -19588,6 +19870,89 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/window-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz", + "integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==", + "dependencies": { + "define-property": "^1.0.0", + "is-number": "^3.0.0" + }, + "bin": { + "window-size": "cli.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/window-size/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/window-size/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/window-size/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/window-size/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/window-size/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/window-size/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/winston": { "version": "3.7.2", "license": "MIT", @@ -19926,6 +20291,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoga-layout-prebuilt": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz", + "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==", + "dependencies": { + "@types/yoga-layout": "1.9.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/zip-stream": { "version": "2.1.3", "license": "MIT", @@ -22136,6 +22512,20 @@ "@dotcom-tool-kit/npm": "^2.0.10" } }, + "@dotcom-tool-kit/box": { + "version": "file:core/box", + "requires": { + "@dotcom-tool-kit/logger": "file:../../lib/logger", + "@dotcom-tool-kit/types": "file:../../lib/types", + "@types/ink-big-text": "^1.2.1", + "@types/react": "^17.0.39", + "dotcom-tool-kit": "file:../cli", + "ink": "^3.2.0", + "ink-big-text": "^1.2.0", + "react": "^17.0.2", + "winston": "^3.6.0" + } + }, "@dotcom-tool-kit/circleci": { "version": "file:plugins/circleci", "requires": { @@ -24258,6 +24648,15 @@ "@types/node": "*" } }, + "@types/ink-big-text": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/ink-big-text/-/ink-big-text-1.2.1.tgz", + "integrity": "sha512-2J+vxIcVzYkQrWfgqDorcui0ueF8g7j2BgQ+iYSsRkXi8/B3mSRM9fNL4jN3UOPWC2upfDssrumG2bOkRs6aXA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.4" }, @@ -24426,6 +24825,12 @@ "@types/node": "*" } }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "devOptional": true + }, "@types/puppeteer": { "version": "3.0.8", "dev": true, @@ -24433,6 +24838,17 @@ "@types/node": "*" } }, + "@types/react": { + "version": "17.0.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.48.tgz", + "integrity": "sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A==", + "devOptional": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, "@types/request": { "version": "2.48.8", "requires": { @@ -24455,6 +24871,12 @@ "@types/retry": { "version": "0.12.0" }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "devOptional": true + }, "@types/semver": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", @@ -24515,6 +24937,11 @@ "@types/node": "*" } }, + "@types/yoga-layout": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", + "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==" + }, "@typescript-eslint/eslint-plugin": { "version": "4.33.0", "dev": true, @@ -25089,6 +25516,11 @@ "atob": { "version": "2.1.2" }, + "auto-bind": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", + "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==" + }, "aws-sdk": { "version": "2.1128.0", "requires": { @@ -25605,6 +26037,15 @@ "caseless": { "version": "0.12.0" }, + "cfonts": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/cfonts/-/cfonts-2.10.1.tgz", + "integrity": "sha512-l5IcLv4SaOdL/EGR6BpOF5SEro88VcGJJ6+xbvJb+wXi19YC6UeHE/brv7a4vIcLZopnt3Ys3zWeNnyfB04UPg==", + "requires": { + "chalk": "^4", + "window-size": "^1.1.1" + } + }, "chai": { "version": "4.3.6", "dev": true, @@ -25867,6 +26308,14 @@ "co": { "version": "4.6.0" }, + "code-excerpt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-3.0.0.tgz", + "integrity": "sha512-VHNTVhd7KsLGOqfX3SyeO8RyYPMp1GJOg194VITk04WMYCv4plV68YWe6TJZxd9MhobjtpMRnVky01gqZsalaw==", + "requires": { + "convert-to-spaces": "^1.0.1" + } + }, "code-point-at": { "version": "1.1.0", "dev": true @@ -26154,6 +26603,11 @@ "safe-buffer": "~5.1.1" } }, + "convert-to-spaces": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz", + "integrity": "sha512-cj09EBuObp9gZNQCzc7hByQyrs6jVGE+o9kSJmeUoj+GiPiJvi5LYqEH/Hmme4+MTLHM+Ejtq+FChpjjEnsPdQ==" + }, "cookie": { "version": "0.3.1" }, @@ -26382,6 +26836,12 @@ } } }, + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "devOptional": true + }, "cwise-compiler": { "version": "1.1.3", "requires": { @@ -28458,6 +28918,62 @@ "ini": { "version": "1.3.8" }, + "ink": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", + "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", + "requires": { + "ansi-escapes": "^4.2.1", + "auto-bind": "4.0.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "code-excerpt": "^3.0.0", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "lodash": "^4.17.20", + "patch-console": "^1.0.0", + "react-devtools-core": "^4.19.1", + "react-reconciler": "^0.26.2", + "scheduler": "^0.20.2", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "stack-utils": "^2.0.2", + "string-width": "^4.2.2", + "type-fest": "^0.12.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "ws": "^7.5.5", + "yoga-layout-prebuilt": "^1.9.6" + }, + "dependencies": { + "type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "ink-big-text": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ink-big-text/-/ink-big-text-1.2.0.tgz", + "integrity": "sha512-xDfn8oOhiji9c4wojTKSaBnEfgpTTd3KL7jsMYVht4SbpfLdSKvVZiMi3U5v45eSjLm1ycMmeMWAP1G99lWL5Q==", + "requires": { + "cfonts": "^2.8.6", + "prop-types": "^15.7.2" + } + }, "inquirer": { "version": "7.3.3", "requires": { @@ -29839,6 +30355,14 @@ "loglevel-plugin-prefix": { "version": "0.8.4" }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loupe": { "version": "2.3.4", "dev": true, @@ -31790,6 +32314,11 @@ "pascalcase": { "version": "0.1.1" }, + "patch-console": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-1.0.0.tgz", + "integrity": "sha512-nxl9nrnLQmh64iTzMfyylSlRozL7kAXIaxw1fVcLYdyhNkJCRUzirRZTikXGJsg+hc4fqpneTK6iU2H1Q8THSA==" + }, "path": { "version": "0.12.7", "requires": { @@ -31950,6 +32479,23 @@ "sisteransi": "^1.0.5" } }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "protocolify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/protocolify/-/protocolify-3.0.0.tgz", @@ -32141,9 +32687,37 @@ } } }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-devtools-core": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.25.0.tgz", + "integrity": "sha512-iewRrnu0ZnmfL+jJayKphXj04CFh6i3ezVnpCtcnZbTPSQgN09XqHAzXbKbqNDl7aTg9QLNkQRP6M3DvdrinWA==", + "requires": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, "react-is": { "version": "17.0.2" }, + "react-reconciler": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", + "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + } + }, "read-package-json": { "version": "5.0.1", "requires": { @@ -32671,6 +33245,15 @@ "scarlet": { "version": "2.0.20" }, + "scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "schema-utils": { "version": "1.0.0", "requires": { @@ -32785,6 +33368,11 @@ "version": "1.0.0", "dev": true }, + "shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" + }, "side-channel": { "version": "1.0.4", "requires": { @@ -34700,6 +35288,69 @@ "wildcard": { "version": "2.0.0" }, + "window-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz", + "integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==", + "requires": { + "define-property": "^1.0.0", + "is-number": "^3.0.0" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + } + } + }, "winston": { "version": "3.7.2", "requires": { @@ -34908,6 +35559,14 @@ "yocto-queue": { "version": "0.1.0" }, + "yoga-layout-prebuilt": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz", + "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==", + "requires": { + "@types/yoga-layout": "1.9.2" + } + }, "zip-stream": { "version": "2.1.3", "requires": { diff --git a/tsconfig.json b/tsconfig.json index f75e39f9c..f8951d52a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -105,6 +105,9 @@ }, { "path": "plugins/cypress" + }, + { + "path": "core/box" } ] } From 10f15a022d65d951b769330915b4d5c77b88bdc4 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 22 Apr 2022 11:47:26 +0100 Subject: [PATCH 02/16] feat(box): add links to details view in TUI --- core/box/src/index.tsx | 116 +++++++++++++++++++++++++++++------------ package-lock.json | 1 + 2 files changed, 85 insertions(+), 32 deletions(-) diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index ee3209bbb..154672162 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { render, Box, Text, useInput, BoxProps } from 'ink' +import { render, Box, BoxProps, Text, useApp, useInput } from 'ink' import BigText from 'ink-big-text' import winston from 'winston' import { loadConfig } from 'dotcom-tool-kit/lib/config' @@ -36,62 +36,114 @@ const List = (props: ListProps) => ( </SelectableBox> ) +const getItemLengths = (plugin: Plugin): { hooksLength: number; tasksLength: number } => { + const hooksLength = (plugin.hooks && Object.keys(plugin.hooks).length) ?? 0 + const tasksLength = (plugin.tasks && Object.keys(plugin.tasks).length) ?? 0 + return { hooksLength, tasksLength } +} + interface PluginDetailsProps { plugin: Plugin selected: boolean + cursor: number +} +const PluginDetails = (props: PluginDetailsProps) => { + const { hooksLength, tasksLength } = getItemLengths(props.plugin) + return ( + <SelectableBox selected={props.selected} width={72} flexDirection="column"> + <Text> + Included by{' '} + <Text bold={props.selected && props.cursor === 0}>{styles.plugin(props.plugin.parent!.id)}</Text> + </Text> + {hooksLength > 0 && ( + <> + <Text>Defines the following hooks:</Text> + {Object.keys(props.plugin.hooks!).map((hook, index) => ( + <Text key={hook}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hook)}</Text> + </Text> + ))} + </> + )} + {tasksLength > 0 && ( + <> + <Text>Defines the following tasks:</Text> + {props.plugin.tasks!.map((task, index) => ( + <Text key={task.id!}> + -{' '} + <Text bold={props.selected && props.cursor === index + 1 + hooksLength}> + {styles.task(task.id!)} + </Text> + </Text> + ))} + </> + )} + </SelectableBox> + ) } -const PluginDetails = (props: PluginDetailsProps) => ( - <SelectableBox selected={props.selected} width={72} flexDirection="column"> - <Text>Included by {styles.plugin(props.plugin.parent!.id)}</Text> - {props.plugin.hooks && Object.keys(props.plugin.hooks).length > 0 && ( - <> - <Text>Defines the following hooks:</Text> - {Object.keys(props.plugin.hooks).map((hook) => ( - <Text key={hook}>- {styles.hook(hook)}</Text> - ))} - </> - )} - {props.plugin.tasks && props.plugin.tasks.length > 0 && ( - <> - <Text>Defines the following tasks:</Text> - {props.plugin.tasks.map((task) => ( - <Text key={task.id!}>- {styles.task(task.id!)}</Text> - ))} - </> - )} - </SelectableBox> -) interface PluginsViewProps { plugins: [string, Plugin][] } const PluginsView = (props: PluginsViewProps) => { - const [cursor, setCursor] = useState(0) + const { exit } = useApp() + const [listCursor, setListCursor] = useState(0) + const [detailsCursor, setDetailsCursor] = useState(0) const [detailsSelected, setDetailsSelected] = useState(false) + + const selectedPlugin = props.plugins[listCursor][1] + useInput((input, key) => { - const maxCursor = props.plugins.length - 1 - if (!detailsSelected && (key.downArrow || input === 'j')) { - setCursor(cursor === maxCursor ? 0 : cursor + 1) + const maxListCursor = props.plugins.length - 1 + const { hooksLength, tasksLength } = getItemLengths(selectedPlugin) + const maxDetailsCursor = hooksLength + tasksLength + if (key.downArrow || input === 'j') { + if (detailsSelected) { + setDetailsCursor(detailsCursor === maxDetailsCursor ? 0 : detailsCursor + 1) + } else { + setListCursor(listCursor === maxListCursor ? 0 : listCursor + 1) + setDetailsCursor(0) + } } - if (!detailsSelected && (key.upArrow || input === 'k')) { - setCursor(cursor === 0 ? maxCursor : cursor - 1) + if (key.upArrow || input === 'k') { + if (detailsSelected) { + setDetailsCursor(detailsCursor === 0 ? maxDetailsCursor : detailsCursor - 1) + } else { + setListCursor(listCursor === 0 ? maxListCursor : listCursor - 1) + setDetailsCursor(0) + } } - if (!detailsSelected && (key.return || key.rightArrow || input === 'l')) { - setDetailsSelected(true) + if (key.return || key.rightArrow || input === 'l') { + if (detailsSelected) { + if (detailsCursor === 0) { + const parentPluginIndex = props.plugins.findIndex( + ([pluginName]) => pluginName === selectedPlugin.parent!.id + ) + if (parentPluginIndex !== -1) { + setListCursor(parentPluginIndex) + setDetailsCursor(0) + } + } + } else { + setDetailsSelected(true) + } } if (detailsSelected && (key.escape || key.leftArrow || input === 'h')) { setDetailsSelected(false) } + if (input === 'q') { + exit() + } }) return ( <Box paddingLeft={detailsSelected ? 1 : 0}> <List items={props.plugins.map(([id]) => styles.plugin(id))} selected={!detailsSelected} - cursor={cursor} + cursor={listCursor} /> - <PluginDetails plugin={props.plugins[cursor][1]} selected={detailsSelected} /> + <PluginDetails plugin={selectedPlugin} selected={detailsSelected} cursor={detailsCursor} /> </Box> ) } diff --git a/package-lock.json b/package-lock.json index e5ab3ac4b..3b133fa23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ } }, "core/box": { + "name": "@dotcom-tool-kit/box", "version": "0.0.0-development", "license": "ISC", "dependencies": { From 4fd66b38b5d14ae245772b5af1282f7776331156 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Sat, 25 Jun 2022 14:29:28 +0100 Subject: [PATCH 03/16] feat(box): add extra tab pages for hooks and tasks --- core/box/package.json | 10 +- core/box/src/index.tsx | 308 +++++++++++++++---- package-lock.json | 661 +++++++++++++++-------------------------- 3 files changed, 484 insertions(+), 495 deletions(-) diff --git a/core/box/package.json b/core/box/package.json index 013a37dd3..5fccdc53e 100644 --- a/core/box/package.json +++ b/core/box/package.json @@ -3,7 +3,6 @@ "version": "0.0.0-development", "description": "", "main": "lib", - "bin": "lib/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -13,9 +12,9 @@ "dependencies": { "@dotcom-tool-kit/logger": "file:../../lib/logger", "dotcom-tool-kit": "file:../cli", - "ink": "^3.2.0", - "ink-big-text": "^1.2.0", - "react": "^17.0.2", + "ink": "~3.1.0", + "ink-tab": "^4.2.0", + "react": "^16.14.0", "winston": "^3.6.0" }, "repository": { @@ -34,7 +33,6 @@ }, "devDependencies": { "@dotcom-tool-kit/types": "file:../../lib/types", - "@types/ink-big-text": "^1.2.1", - "@types/react": "^17.0.39" + "@types/react": "^16.14.24" } } diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index 154672162..3fb1743a9 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -1,12 +1,12 @@ import React, { useState } from 'react' import { render, Box, BoxProps, Text, useApp, useInput } from 'ink' -import BigText from 'ink-big-text' +import { Tabs, Tab } from 'ink-tab' import winston from 'winston' -import { loadConfig } from 'dotcom-tool-kit/lib/config' +import { loadConfig, ValidConfig } from 'dotcom-tool-kit/lib/config' import { styles } from '@dotcom-tool-kit/logger' -import type { Plugin } from '@dotcom-tool-kit/types' +import type { Hook, HookClass, Plugin, TaskClass } from '@dotcom-tool-kit/types' -const Title = () => <BigText text="dotcom-tool-kit" colors={['#AB153D', '#181E2C']} /> +const logger = winston.createLogger({ silent: true }) interface SelectableBoxProps extends BoxProps { selected: boolean @@ -36,95 +36,172 @@ const List = (props: ListProps) => ( </SelectableBox> ) -const getItemLengths = (plugin: Plugin): { hooksLength: number; tasksLength: number } => { - const hooksLength = (plugin.hooks && Object.keys(plugin.hooks).length) ?? 0 - const tasksLength = (plugin.tasks && Object.keys(plugin.tasks).length) ?? 0 - return { hooksLength, tasksLength } -} - -interface PluginDetailsProps { - plugin: Plugin +interface ToolEssentials { selected: boolean cursor: number } -const PluginDetails = (props: PluginDetailsProps) => { - const { hooksLength, tasksLength } = getItemLengths(props.plugin) - return ( - <SelectableBox selected={props.selected} width={72} flexDirection="column"> - <Text> - Included by{' '} - <Text bold={props.selected && props.cursor === 0}>{styles.plugin(props.plugin.parent!.id)}</Text> + +const DetailsBox = (props: React.PropsWithChildren<SelectableBoxProps>) => ( + <SelectableBox width={72} flexDirection="column" {...props}> + {props.children} + </SelectableBox> +) + +interface PluginDetailsProps extends ToolEssentials { + parent?: Plugin + hookIds: string[] + taskIds: string[] +} + +const PluginDetails = (props: PluginDetailsProps) => ( + <DetailsBox selected={props.selected}> + <Text> + Included by{' '} + <Text bold={props.selected && props.cursor === 0}> + {styles.plugin(props.parent?.id ?? 'no parent')} </Text> - {hooksLength > 0 && ( + </Text> + {props.hookIds.length > 0 && ( + <> + <Text>Defines the following hooks:</Text> + {props.hookIds.map((hookId, index) => ( + <Text key={hookId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hookId)}</Text> + </Text> + ))} + </> + )} + {props.taskIds.length > 0 && ( + <> + <Text>Defines the following tasks:</Text> + {props.taskIds.map((taskId, index) => ( + <Text key={taskId}> + -{' '} + <Text bold={props.selected && props.cursor === index + 1 + props.hookIds.length}> + {styles.task(taskId)} + </Text> + </Text> + ))} + </> + )} + </DetailsBox> +) + +interface HookDetailsProps extends ToolEssentials { + hook: Hook + taskIds: string[] + pluginIds: string[] +} + +const HookDetails = (props: HookDetailsProps) => { + return ( + <DetailsBox selected={props.selected}> + <Text>{(props.hook.constructor as HookClass).description}</Text> + {props.hook.plugin && <Text>Defined in the {styles.plugin(props.hook.plugin.id)} plugin</Text>} + {props.taskIds.length > 0 && ( <> - <Text>Defines the following hooks:</Text> - {Object.keys(props.plugin.hooks!).map((hook, index) => ( - <Text key={hook}> - - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hook)}</Text> + <Text>Calls the following tasks:</Text> + {props.taskIds.map((taskId, index) => ( + <Text key={taskId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.plugin(taskId)}</Text> </Text> ))} </> )} - {tasksLength > 0 && ( + {props.pluginIds.length > 0 && ( <> - <Text>Defines the following tasks:</Text> - {props.plugin.tasks!.map((task, index) => ( - <Text key={task.id!}> - -{' '} - <Text bold={props.selected && props.cursor === index + 1 + hooksLength}> - {styles.task(task.id!)} - </Text> + <Text>Appears in the following plugins:</Text> + {props.pluginIds.map((pluginId, index) => ( + <Text key={pluginId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.plugin(pluginId)}</Text> </Text> ))} </> )} - </SelectableBox> + </DetailsBox> ) } -interface PluginsViewProps { - plugins: [string, Plugin][] +interface TaskDetailsProps extends ToolEssentials { + task: TaskClass + pluginIds: string[] + hookIds: string[] } -const PluginsView = (props: PluginsViewProps) => { +const TaskDetails = (props: TaskDetailsProps) => { + return ( + <DetailsBox selected={props.selected}> + <Text>{props.task.description}</Text> + {props.task.plugin && <Text>Defined in the {styles.plugin(props.task.plugin.id)} plugin</Text>} + {props.pluginIds.length > 0 && ( + <> + <Text>Appears in the following plugins:</Text> + {props.pluginIds.map((pluginId, index) => ( + <Text key={pluginId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.plugin(pluginId)}</Text> + </Text> + ))} + </> + )} + {props.hookIds.length > 0 && ( + <> + <Text>Calls the following hooks:</Text> + {props.hookIds.map((hookId, index) => ( + <Text key={hookId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hookId)}</Text> + </Text> + ))} + </> + )} + </DetailsBox> + ) +} + +interface NavigationState { + listCursor: number + detailsCursor: number + detailsSelected: boolean +} + +const useNavigation = ( + maxListCursor: number, + getMaxDetailsCursor: (listCursor: number) => number +): NavigationState => { const { exit } = useApp() const [listCursor, setListCursor] = useState(0) const [detailsCursor, setDetailsCursor] = useState(0) const [detailsSelected, setDetailsSelected] = useState(false) - const selectedPlugin = props.plugins[listCursor][1] + const maxDetailsCursor = getMaxDetailsCursor(listCursor) useInput((input, key) => { - const maxListCursor = props.plugins.length - 1 - const { hooksLength, tasksLength } = getItemLengths(selectedPlugin) - const maxDetailsCursor = hooksLength + tasksLength if (key.downArrow || input === 'j') { if (detailsSelected) { - setDetailsCursor(detailsCursor === maxDetailsCursor ? 0 : detailsCursor + 1) + setDetailsCursor(detailsCursor !== maxDetailsCursor ? detailsCursor + 1 : 0) } else { - setListCursor(listCursor === maxListCursor ? 0 : listCursor + 1) + setListCursor(listCursor !== maxListCursor ? listCursor + 1 : 0) setDetailsCursor(0) } } if (key.upArrow || input === 'k') { if (detailsSelected) { - setDetailsCursor(detailsCursor === 0 ? maxDetailsCursor : detailsCursor - 1) + setDetailsCursor(detailsCursor !== 0 ? detailsCursor - 1 : maxDetailsCursor) } else { - setListCursor(listCursor === 0 ? maxListCursor : listCursor - 1) + setListCursor(listCursor !== 0 ? listCursor - 1 : maxListCursor) setDetailsCursor(0) } } if (key.return || key.rightArrow || input === 'l') { if (detailsSelected) { - if (detailsCursor === 0) { - const parentPluginIndex = props.plugins.findIndex( - ([pluginName]) => pluginName === selectedPlugin.parent!.id - ) - if (parentPluginIndex !== -1) { - setListCursor(parentPluginIndex) - setDetailsCursor(0) - } - } + // if (detailsCursor === 0) { + // const parentPluginIndex = props.plugins.findIndex( + // ([pluginName]) => pluginName === selectedComponent.parent!.id + // ) + // if (parentPluginIndex !== -1) { + // setListCursor(parentPluginIndex) + // setDetailsCursor(0) + // } + // } } else { setDetailsSelected(true) } @@ -136,6 +213,20 @@ const PluginsView = (props: PluginsViewProps) => { exit() } }) + + return { listCursor, detailsCursor, detailsSelected } +} + +interface PluginsPageProps { + plugins: [string, Plugin][] +} + +const PluginsPage = (props: PluginsPageProps) => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.plugins.length - 1, (cursor) => { + const [, plugin] = props.plugins[cursor] + return Object.keys(plugin.module?.hooks ?? {}).length + (plugin.module?.tasks?.length ?? 0) + }) + const [, selectedPlugin] = props.plugins[listCursor] return ( <Box paddingLeft={detailsSelected ? 1 : 0}> <List @@ -143,22 +234,119 @@ const PluginsView = (props: PluginsViewProps) => { selected={!detailsSelected} cursor={listCursor} /> - <PluginDetails plugin={selectedPlugin} selected={detailsSelected} cursor={detailsCursor} /> + <PluginDetails + parent={selectedPlugin.parent} + hookIds={Object.keys(selectedPlugin.module?.hooks ?? {})} + taskIds={ + selectedPlugin.module?.tasks?.map((task) => task.id).filter((id): id is string => !!id) ?? [] + } + selected={detailsSelected} + cursor={detailsCursor} + /> </Box> ) } -async function main() { - const logger = winston.createLogger({ silent: true }) +interface HooksPageProps { + hooks: [string, Hook][] + taskMap: Record<string, string[]> + pluginMap: Record<string, string[]> +} - const config = await loadConfig(logger) +const HooksPage = (props: HooksPageProps) => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.hooks.length - 1, (cursor) => { + const [hookId, hook] = props.hooks[cursor] + return ( + (hook.plugin ? 1 : 0) + (props.taskMap[hookId]?.length ?? 0) + (props.pluginMap[hookId]?.length ?? 0) + ) + }) + const [hookId, selectedHook] = props.hooks[listCursor] + return ( + <Box paddingLeft={detailsSelected ? 1 : 0}> + <List + items={props.hooks.map(([id]) => styles.hook(id))} + selected={!detailsSelected} + cursor={listCursor} + /> + <HookDetails + hook={selectedHook} + taskIds={props.taskMap[hookId] ?? []} + pluginIds={props.pluginMap[hookId] ?? []} + selected={detailsSelected} + cursor={detailsCursor} + /> + </Box> + ) +} + +interface TasksPageProps { + tasks: [string, TaskClass][] + pluginMap: Record<string, string[]> + hookMap: Record<string, string[]> +} + +const TasksPage = (props: TasksPageProps) => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.tasks.length - 1, (cursor) => { + const [taskId, task] = props.tasks[cursor] + return ( + (task.plugin ? 1 : 0) + (props.pluginMap[taskId]?.length ?? 0) + (props.hookMap[taskId]?.length ?? 0) + ) + }) + const [taskId, selectedTask] = props.tasks[listCursor] + return ( + <Box paddingLeft={detailsSelected ? 1 : 0}> + <List + items={props.tasks.map(([id]) => styles.task(id))} + selected={!detailsSelected} + cursor={listCursor} + /> + <TaskDetails + task={selectedTask} + pluginIds={props.pluginMap[taskId] ?? []} + hookIds={props.hookMap[taskId] ?? []} + selected={detailsSelected} + cursor={detailsCursor} + /> + </Box> + ) +} + +type TabName = 'plugins' | 'hooks' | 'tasks' - render( +interface TabbedViewProps { + config: ValidConfig +} + +const TabbedView = (props: TabbedViewProps) => { + const [activeTab, setActiveTab] = useState<TabName>('plugins') + return ( <> - <Title /> - <PluginsView plugins={Object.entries(config.plugins)} /> + <Tabs + onChange={(newTab: TabName) => setActiveTab(newTab)} + showIndex={false} + keyMap={{ + previous: [], + next: [] + }}> + <Tab name="plugins">plugins</Tab> + <Tab name="hooks">hooks</Tab> + <Tab name="tasks">tasks</Tab> + </Tabs> + {activeTab === 'plugins' && <PluginsPage plugins={Object.entries(props.config.plugins)} />} + {activeTab === 'hooks' && ( + <HooksPage hooks={Object.entries(props.config.hooks)} taskMap={{}} pluginMap={{}} /> + )} + {activeTab === 'tasks' && ( + <TasksPage tasks={Object.entries(props.config.tasks)} pluginMap={{}} hookMap={{}} /> + )} </> ) } +async function main() { + const config = await loadConfig(logger) + + render(<TabbedView config={config} />) +} + main() diff --git a/package-lock.json b/package-lock.json index 3b133fa23..d5c0e467d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,18 +50,142 @@ "dependencies": { "@dotcom-tool-kit/logger": "file:../../lib/logger", "dotcom-tool-kit": "file:../cli", - "ink": "^3.2.0", - "ink-big-text": "^1.2.0", - "react": "^17.0.2", + "ink": "~3.1.0", + "ink-tab": "^4.2.0", + "react": "^16.14.0", "winston": "^3.6.0" }, - "bin": { - "box": "lib/index.js" - }, "devDependencies": { "@dotcom-tool-kit/types": "file:../../lib/types", - "@types/ink-big-text": "^1.2.1", - "@types/react": "^17.0.39" + "@types/react": "^16.14.24" + } + }, + "core/box/node_modules/@types/react": { + "version": "16.14.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.29.tgz", + "integrity": "sha512-I5IwEaefGZbpmmK1J7zHwZe3JkGxcRkc5WJUDWmNySVVovueViRTEUWV7spTvpe96l3nbKD/K6+GxoN69CYb/w==", + "devOptional": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "core/box/node_modules/ink": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-3.1.0.tgz", + "integrity": "sha512-vET9Yd1dFIckwUw9bDQHITMj4BdySCCjfviqQ65dNAyjzXoCr3LZI8h4pIeaRpivmXnK16trVexCgGwTRZJHYw==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "auto-bind": "4.0.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "code-excerpt": "^3.0.0", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "lodash": "^4.17.20", + "patch-console": "^1.0.0", + "react-devtools-core": "^4.6.0", + "react-reconciler": "^0.24.0", + "scheduler": "^0.18.0", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "stack-utils": "^2.0.2", + "string-width": "^4.2.2", + "type-fest": "^0.12.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "ws": "^7.2.5", + "yoga-layout-prebuilt": "^1.9.6" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": ">=16.8.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "core/box/node_modules/ink-tab": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ink-tab/-/ink-tab-4.2.0.tgz", + "integrity": "sha512-KrvNWrebGCXWbGf74QE0XYKYGUCa0dEgHGa3B4VJK3PQh8+wXycQeGnl1s/kq3OokWOmMyfkc/BTPgulxjXspQ==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "ink": "^3.0.0", + "react": "^16.8.0" + } + }, + "core/box/node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "core/box/node_modules/react-reconciler": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.24.0.tgz", + "integrity": "sha512-gAGnwWkf+NOTig9oOowqid9O0HjTDC+XVGBCAmJYYJ2A2cN/O4gDdIuuUQjv8A4v6GDwVfJkagpBBLW5OW9HSw==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.18.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^16.0.0" + } + }, + "core/box/node_modules/scheduler": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "core/box/node_modules/type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "core/box/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, "core/cli": { @@ -4024,15 +4148,6 @@ "@types/node": "*" } }, - "node_modules/@types/ink-big-text": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/ink-big-text/-/ink-big-text-1.2.1.tgz", - "integrity": "sha512-2J+vxIcVzYkQrWfgqDorcui0ueF8g7j2BgQ+iYSsRkXi8/B3mSRM9fNL4jN3UOPWC2upfDssrumG2bOkRs6aXA==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "license": "MIT" @@ -4242,17 +4357,6 @@ "@types/node": "*" } }, - "node_modules/@types/react": { - "version": "17.0.48", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.48.tgz", - "integrity": "sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A==", - "devOptional": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, "node_modules/@types/request": { "version": "2.48.8", "license": "MIT", @@ -6030,21 +6134,6 @@ "version": "0.12.0", "license": "Apache-2.0" }, - "node_modules/cfonts": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/cfonts/-/cfonts-2.10.1.tgz", - "integrity": "sha512-l5IcLv4SaOdL/EGR6BpOF5SEro88VcGJJ6+xbvJb+wXi19YC6UeHE/brv7a4vIcLZopnt3Ys3zWeNnyfB04UPg==", - "dependencies": { - "chalk": "^4", - "window-size": "^1.1.1" - }, - "bin": { - "cfonts": "bin/index.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/chai": { "version": "4.3.6", "dev": true, @@ -10320,91 +10409,6 @@ "version": "1.3.8", "license": "ISC" }, - "node_modules/ink": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", - "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "auto-bind": "4.0.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.0", - "cli-cursor": "^3.1.0", - "cli-truncate": "^2.1.0", - "code-excerpt": "^3.0.0", - "indent-string": "^4.0.0", - "is-ci": "^2.0.0", - "lodash": "^4.17.20", - "patch-console": "^1.0.0", - "react-devtools-core": "^4.19.1", - "react-reconciler": "^0.26.2", - "scheduler": "^0.20.2", - "signal-exit": "^3.0.2", - "slice-ansi": "^3.0.0", - "stack-utils": "^2.0.2", - "string-width": "^4.2.2", - "type-fest": "^0.12.0", - "widest-line": "^3.1.0", - "wrap-ansi": "^6.2.0", - "ws": "^7.5.5", - "yoga-layout-prebuilt": "^1.9.6" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": ">=16.8.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/ink-big-text": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ink-big-text/-/ink-big-text-1.2.0.tgz", - "integrity": "sha512-xDfn8oOhiji9c4wojTKSaBnEfgpTTd3KL7jsMYVht4SbpfLdSKvVZiMi3U5v45eSjLm1ycMmeMWAP1G99lWL5Q==", - "dependencies": { - "cfonts": "^2.8.6", - "prop-types": "^15.7.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependencies": { - "ink": ">=2.0.0", - "react": ">=16.8.0" - } - }, - "node_modules/ink/node_modules/type-fest": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", - "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ink/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/inquirer": { "version": "7.3.3", "license": "MIT", @@ -15996,18 +16000,6 @@ "node": ">=0.10.0" } }, - "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-devtools-core": { "version": "4.25.0", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.25.0.tgz", @@ -16021,22 +16013,6 @@ "version": "17.0.2", "license": "MIT" }, - "node_modules/react-reconciler": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", - "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^17.0.2" - } - }, "node_modules/read-package-json": { "version": "5.0.1", "license": "ISC", @@ -16835,15 +16811,6 @@ "node": ">= 0.10.0" } }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "node_modules/schema-utils": { "version": "1.0.0", "license": "MIT", @@ -19871,89 +19838,6 @@ "version": "2.0.0", "license": "MIT" }, - "node_modules/window-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz", - "integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==", - "dependencies": { - "define-property": "^1.0.0", - "is-number": "^3.0.0" - }, - "bin": { - "window-size": "cli.js" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/window-size/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/window-size/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/window-size/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/window-size/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/window-size/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/window-size/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/winston": { "version": "3.7.2", "license": "MIT", @@ -22518,13 +22402,108 @@ "requires": { "@dotcom-tool-kit/logger": "file:../../lib/logger", "@dotcom-tool-kit/types": "file:../../lib/types", - "@types/ink-big-text": "^1.2.1", - "@types/react": "^17.0.39", + "@types/react": "^16.14.24", "dotcom-tool-kit": "file:../cli", - "ink": "^3.2.0", - "ink-big-text": "^1.2.0", - "react": "^17.0.2", + "ink": "~3.1.0", + "ink-tab": "^4.2.0", + "react": "^16.14.0", "winston": "^3.6.0" + }, + "dependencies": { + "@types/react": { + "version": "16.14.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.29.tgz", + "integrity": "sha512-I5IwEaefGZbpmmK1J7zHwZe3JkGxcRkc5WJUDWmNySVVovueViRTEUWV7spTvpe96l3nbKD/K6+GxoN69CYb/w==", + "devOptional": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "ink": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-3.1.0.tgz", + "integrity": "sha512-vET9Yd1dFIckwUw9bDQHITMj4BdySCCjfviqQ65dNAyjzXoCr3LZI8h4pIeaRpivmXnK16trVexCgGwTRZJHYw==", + "requires": { + "ansi-escapes": "^4.2.1", + "auto-bind": "4.0.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "code-excerpt": "^3.0.0", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "lodash": "^4.17.20", + "patch-console": "^1.0.0", + "react-devtools-core": "^4.6.0", + "react-reconciler": "^0.24.0", + "scheduler": "^0.18.0", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "stack-utils": "^2.0.2", + "string-width": "^4.2.2", + "type-fest": "^0.12.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "ws": "^7.2.5", + "yoga-layout-prebuilt": "^1.9.6" + } + }, + "ink-tab": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ink-tab/-/ink-tab-4.2.0.tgz", + "integrity": "sha512-KrvNWrebGCXWbGf74QE0XYKYGUCa0dEgHGa3B4VJK3PQh8+wXycQeGnl1s/kq3OokWOmMyfkc/BTPgulxjXspQ==", + "requires": { + "prop-types": "^15.7.2" + } + }, + "react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-reconciler": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.24.0.tgz", + "integrity": "sha512-gAGnwWkf+NOTig9oOowqid9O0HjTDC+XVGBCAmJYYJ2A2cN/O4gDdIuuUQjv8A4v6GDwVfJkagpBBLW5OW9HSw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.18.0" + } + }, + "scheduler": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } } }, "@dotcom-tool-kit/circleci": { @@ -24649,15 +24628,6 @@ "@types/node": "*" } }, - "@types/ink-big-text": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/ink-big-text/-/ink-big-text-1.2.1.tgz", - "integrity": "sha512-2J+vxIcVzYkQrWfgqDorcui0ueF8g7j2BgQ+iYSsRkXi8/B3mSRM9fNL4jN3UOPWC2upfDssrumG2bOkRs6aXA==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, "@types/istanbul-lib-coverage": { "version": "2.0.4" }, @@ -24839,17 +24809,6 @@ "@types/node": "*" } }, - "@types/react": { - "version": "17.0.48", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.48.tgz", - "integrity": "sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A==", - "devOptional": true, - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, "@types/request": { "version": "2.48.8", "requires": { @@ -26038,15 +25997,6 @@ "caseless": { "version": "0.12.0" }, - "cfonts": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/cfonts/-/cfonts-2.10.1.tgz", - "integrity": "sha512-l5IcLv4SaOdL/EGR6BpOF5SEro88VcGJJ6+xbvJb+wXi19YC6UeHE/brv7a4vIcLZopnt3Ys3zWeNnyfB04UPg==", - "requires": { - "chalk": "^4", - "window-size": "^1.1.1" - } - }, "chai": { "version": "4.3.6", "dev": true, @@ -28919,62 +28869,6 @@ "ini": { "version": "1.3.8" }, - "ink": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", - "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", - "requires": { - "ansi-escapes": "^4.2.1", - "auto-bind": "4.0.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.0", - "cli-cursor": "^3.1.0", - "cli-truncate": "^2.1.0", - "code-excerpt": "^3.0.0", - "indent-string": "^4.0.0", - "is-ci": "^2.0.0", - "lodash": "^4.17.20", - "patch-console": "^1.0.0", - "react-devtools-core": "^4.19.1", - "react-reconciler": "^0.26.2", - "scheduler": "^0.20.2", - "signal-exit": "^3.0.2", - "slice-ansi": "^3.0.0", - "stack-utils": "^2.0.2", - "string-width": "^4.2.2", - "type-fest": "^0.12.0", - "widest-line": "^3.1.0", - "wrap-ansi": "^6.2.0", - "ws": "^7.5.5", - "yoga-layout-prebuilt": "^1.9.6" - }, - "dependencies": { - "type-fest": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", - "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "ink-big-text": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ink-big-text/-/ink-big-text-1.2.0.tgz", - "integrity": "sha512-xDfn8oOhiji9c4wojTKSaBnEfgpTTd3KL7jsMYVht4SbpfLdSKvVZiMi3U5v45eSjLm1ycMmeMWAP1G99lWL5Q==", - "requires": { - "cfonts": "^2.8.6", - "prop-types": "^15.7.2" - } - }, "inquirer": { "version": "7.3.3", "requires": { @@ -32688,15 +32582,6 @@ } } }, - "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "react-devtools-core": { "version": "4.25.0", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.25.0.tgz", @@ -32709,16 +32594,6 @@ "react-is": { "version": "17.0.2" }, - "react-reconciler": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", - "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - } - }, "read-package-json": { "version": "5.0.1", "requires": { @@ -33246,15 +33121,6 @@ "scarlet": { "version": "2.0.20" }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "schema-utils": { "version": "1.0.0", "requires": { @@ -35289,69 +35155,6 @@ "wildcard": { "version": "2.0.0" }, - "window-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz", - "integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==", - "requires": { - "define-property": "^1.0.0", - "is-number": "^3.0.0" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - } - } - }, "winston": { "version": "3.7.2", "requires": { From e92ad368bbdf5c99037b201fd42f9a7fa4a16239 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 22 Jul 2022 14:29:28 +0100 Subject: [PATCH 04/16] feat(box): calculate where hooks and tasks are defined and used --- core/box/src/index.tsx | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index 3fb1743a9..f83c7bbfe 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -145,7 +145,7 @@ const TaskDetails = (props: TaskDetailsProps) => { )} {props.hookIds.length > 0 && ( <> - <Text>Calls the following hooks:</Text> + <Text>Is called by the following hooks:</Text> {props.hookIds.map((hookId, index) => ( <Text key={hookId}> - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hookId)}</Text> @@ -319,6 +319,28 @@ interface TabbedViewProps { const TabbedView = (props: TabbedViewProps) => { const [activeTab, setActiveTab] = useState<TabName>('plugins') + const pluginsWithHook: Record<string, string[]> = {} + const pluginsWithTask: Record<string, string[]> = {} + for (const [pluginId, plugin] of Object.entries(props.config.plugins)) { + for (const hookId of Object.keys(plugin.module?.hooks ?? {})) { + pluginsWithHook[hookId] ??= [] + pluginsWithHook[hookId].push(pluginId) + } + for (const taskId of Object.keys(plugin.module?.tasks ?? {})) { + pluginsWithTask[taskId] ??= [] + pluginsWithTask[taskId].push(pluginId) + } + } + const tasksWithHook = Object.entries(props.config.hookTasks).map( + ([hookId, hookTask]) => [hookId, hookTask.tasks] as const + ) + const hooksWithTask: Record<string, string[]> = {} + for (const [hookId, tasks] of tasksWithHook) { + for (const task of tasks) { + hooksWithTask[task] ??= [] + hooksWithTask[task].push(hookId) + } + } return ( <> <Tabs @@ -334,10 +356,18 @@ const TabbedView = (props: TabbedViewProps) => { </Tabs> {activeTab === 'plugins' && <PluginsPage plugins={Object.entries(props.config.plugins)} />} {activeTab === 'hooks' && ( - <HooksPage hooks={Object.entries(props.config.hooks)} taskMap={{}} pluginMap={{}} /> + <HooksPage + hooks={Object.entries(props.config.hooks)} + taskMap={Object.fromEntries(tasksWithHook)} + pluginMap={pluginsWithHook} + /> )} {activeTab === 'tasks' && ( - <TasksPage tasks={Object.entries(props.config.tasks)} pluginMap={{}} hookMap={{}} /> + <TasksPage + tasks={Object.entries(props.config.tasks)} + pluginMap={pluginsWithTask} + hookMap={hooksWithTask} + /> )} </> ) From 7db05d524e54d84a54d0dacb94e2c18cbefb09f3 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 22 Jul 2022 14:54:28 +0100 Subject: [PATCH 05/16] fix(box): highlight and loop through list items correctly --- core/box/src/index.tsx | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index f83c7bbfe..cbff3d37f 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -97,7 +97,13 @@ const HookDetails = (props: HookDetailsProps) => { return ( <DetailsBox selected={props.selected}> <Text>{(props.hook.constructor as HookClass).description}</Text> - {props.hook.plugin && <Text>Defined in the {styles.plugin(props.hook.plugin.id)} plugin</Text>} + {props.hook.plugin && ( + <Text> + Defined in the{' '} + <Text bold={props.selected && props.cursor === 0}>{styles.plugin(props.hook.plugin.id)}</Text>{' '} + plugin + </Text> + )} {props.taskIds.length > 0 && ( <> <Text>Calls the following tasks:</Text> @@ -113,7 +119,10 @@ const HookDetails = (props: HookDetailsProps) => { <Text>Appears in the following plugins:</Text> {props.pluginIds.map((pluginId, index) => ( <Text key={pluginId}> - - <Text bold={props.selected && props.cursor === index + 1}>{styles.plugin(pluginId)}</Text> + -{' '} + <Text bold={props.selected && props.cursor === index + 1 + props.taskIds.length}> + {styles.plugin(pluginId)} + </Text> </Text> ))} </> @@ -132,7 +141,13 @@ const TaskDetails = (props: TaskDetailsProps) => { return ( <DetailsBox selected={props.selected}> <Text>{props.task.description}</Text> - {props.task.plugin && <Text>Defined in the {styles.plugin(props.task.plugin.id)} plugin</Text>} + {props.task.plugin && ( + <Text> + Defined in the{' '} + <Text bold={props.selected && props.cursor === 0}>{styles.plugin(props.task.plugin.id)}</Text>{' '} + plugin + </Text> + )} {props.pluginIds.length > 0 && ( <> <Text>Appears in the following plugins:</Text> @@ -164,15 +179,16 @@ interface NavigationState { } const useNavigation = ( - maxListCursor: number, - getMaxDetailsCursor: (listCursor: number) => number + listLength: number, + getDetailsLength: (listCursor: number) => number ): NavigationState => { const { exit } = useApp() const [listCursor, setListCursor] = useState(0) const [detailsCursor, setDetailsCursor] = useState(0) const [detailsSelected, setDetailsSelected] = useState(false) - const maxDetailsCursor = getMaxDetailsCursor(listCursor) + const maxListCursor = listLength - 1 + const maxDetailsCursor = getDetailsLength(listCursor) - 1 useInput((input, key) => { if (key.downArrow || input === 'j') { @@ -222,9 +238,9 @@ interface PluginsPageProps { } const PluginsPage = (props: PluginsPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.plugins.length - 1, (cursor) => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.plugins.length, (cursor) => { const [, plugin] = props.plugins[cursor] - return Object.keys(plugin.module?.hooks ?? {}).length + (plugin.module?.tasks?.length ?? 0) + return 1 + Object.keys(plugin.module?.hooks ?? {}).length + (plugin.module?.tasks?.length ?? 0) }) const [, selectedPlugin] = props.plugins[listCursor] return ( @@ -254,7 +270,7 @@ interface HooksPageProps { } const HooksPage = (props: HooksPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.hooks.length - 1, (cursor) => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.hooks.length, (cursor) => { const [hookId, hook] = props.hooks[cursor] return ( (hook.plugin ? 1 : 0) + (props.taskMap[hookId]?.length ?? 0) + (props.pluginMap[hookId]?.length ?? 0) @@ -286,7 +302,7 @@ interface TasksPageProps { } const TasksPage = (props: TasksPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.tasks.length - 1, (cursor) => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.tasks.length, (cursor) => { const [taskId, task] = props.tasks[cursor] return ( (task.plugin ? 1 : 0) + (props.pluginMap[taskId]?.length ?? 0) + (props.hookMap[taskId]?.length ?? 0) From f94402bc776c666c240c691c17e4f8afca82eaa3 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 19 Aug 2022 15:54:18 +0100 Subject: [PATCH 06/16] test(box): add ESLint plugins for linting React code --- .eslintrc.js | 11 +- package-lock.json | 348 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 + 3 files changed, 356 insertions(+), 5 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 09b84c5b2..24cb7406f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,7 +5,9 @@ module.exports = { 'prettier', 'plugin:import/recommended', 'plugin:import/typescript', - 'plugin:@typescript-eslint/recommended' + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended' ], rules: { // We use winston's logging instead @@ -19,5 +21,10 @@ module.exports = { '@typescript-eslint/no-var-requires': 'off' } } - ] + ], + settings: { + react: { + version: 'detect' + } + } } diff --git a/package-lock.json b/package-lock.json index d5c0e467d..cf8d8c3ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,8 @@ "eslint": "^7.20.0", "eslint-config-prettier": "^8.0.0", "eslint-plugin-import": "^2.22.1", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", "husky": "^4.3.8", "jest": "^27.4.7", "lint-staged": "^10.5.4", @@ -5206,6 +5208,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.map": { "version": "1.0.4", "license": "MIT", @@ -8340,6 +8360,93 @@ "node": ">=4.0.0" } }, + "node_modules/eslint-plugin-react": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "dev": true, @@ -9387,7 +9494,6 @@ "node_modules/functions-have-names": { "version": "1.2.3", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11934,6 +12040,19 @@ "node": ">=0.6.0" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "3.1.0", "license": "MIT", @@ -14346,6 +14465,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object.pick": { "version": "1.3.0", "license": "MIT", @@ -16335,6 +16498,23 @@ "node": ">=0.10.0" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpp": { "version": "3.2.0", "dev": true, @@ -17590,6 +17770,25 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/string.prototype.trimend": { "version": "1.0.5", "license": "MIT", @@ -25377,6 +25576,18 @@ "es-shim-unscopables": "^1.0.0" } }, + "array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, "array.prototype.map": { "version": "1.0.4", "peer": true, @@ -27609,6 +27820,69 @@ "version": "2.6.0", "dev": true }, + "eslint-plugin-react": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "requires": {} + }, "eslint-scope": { "version": "5.1.1", "dev": true, @@ -28247,8 +28521,7 @@ "dev": true }, "functions-have-names": { - "version": "1.2.3", - "peer": true + "version": "1.2.3" }, "gauge": { "version": "4.0.4", @@ -29829,6 +30102,16 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + } + }, "keyv": { "version": "3.1.0", "requires": { @@ -31476,6 +31759,38 @@ "object-keys": "^1.1.1" } }, + "object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, "object.pick": { "version": "1.3.0", "requires": { @@ -32809,6 +33124,17 @@ } } }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, "regexpp": { "version": "3.2.0", "dev": true @@ -33659,6 +33985,22 @@ "strip-ansi": "^6.0.1" } }, + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + } + }, "string.prototype.trimend": { "version": "1.0.5", "requires": { diff --git a/package.json b/package.json index 744f7a1af..770a22f49 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "eslint": "^7.20.0", "eslint-config-prettier": "^8.0.0", "eslint-plugin-import": "^2.22.1", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", "husky": "^4.3.8", "jest": "^27.4.7", "lint-staged": "^10.5.4", From 3518db9ae408d23ac33bf5c6d4e78516e1d9489f Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 19 Aug 2022 16:45:14 +0100 Subject: [PATCH 07/16] feat(box): navigate to item when selected --- core/box/src/index.tsx | 185 ++++++++++++++++++++++++++++++++++------- 1 file changed, 153 insertions(+), 32 deletions(-) diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index cbff3d37f..d50f266be 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { render, Box, BoxProps, Text, useApp, useInput } from 'ink' import { Tabs, Tab } from 'ink-tab' import winston from 'winston' @@ -172,16 +172,34 @@ const TaskDetails = (props: TaskDetailsProps) => { ) } +interface TabPageProps { + startingItem?: string + onTabChange: (newTab: TabName, itemId: string | undefined) => void +} + +interface NavigationArgs { + listLength: number + getDetailsLength: (listCursor: number) => number + getSelectedItem: (listCursor: number, detailsCursor: number) => [TabName, string | undefined] + findItem: (itemId: string) => number + startingItem?: string + changeTab: (newTab: TabName, itemId: string | undefined) => void +} + interface NavigationState { listCursor: number detailsCursor: number detailsSelected: boolean } -const useNavigation = ( - listLength: number, - getDetailsLength: (listCursor: number) => number -): NavigationState => { +const useNavigation = ({ + listLength, + getDetailsLength, + getSelectedItem, + findItem, + startingItem, + changeTab +}: NavigationArgs): NavigationState => { const { exit } = useApp() const [listCursor, setListCursor] = useState(0) const [detailsCursor, setDetailsCursor] = useState(0) @@ -190,6 +208,12 @@ const useNavigation = ( const maxListCursor = listLength - 1 const maxDetailsCursor = getDetailsLength(listCursor) - 1 + useEffect(() => { + if (startingItem) { + setListCursor(findItem(startingItem)) + } + }, [startingItem, findItem]) + useInput((input, key) => { if (key.downArrow || input === 'j') { if (detailsSelected) { @@ -209,15 +233,8 @@ const useNavigation = ( } if (key.return || key.rightArrow || input === 'l') { if (detailsSelected) { - // if (detailsCursor === 0) { - // const parentPluginIndex = props.plugins.findIndex( - // ([pluginName]) => pluginName === selectedComponent.parent!.id - // ) - // if (parentPluginIndex !== -1) { - // setListCursor(parentPluginIndex) - // setDetailsCursor(0) - // } - // } + const [newTab, itemId] = getSelectedItem(listCursor, detailsCursor) + changeTab(newTab, itemId) } else { setDetailsSelected(true) } @@ -233,14 +250,36 @@ const useNavigation = ( return { listCursor, detailsCursor, detailsSelected } } -interface PluginsPageProps { +interface PluginsPageProps extends TabPageProps { plugins: [string, Plugin][] } const PluginsPage = (props: PluginsPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.plugins.length, (cursor) => { - const [, plugin] = props.plugins[cursor] - return 1 + Object.keys(plugin.module?.hooks ?? {}).length + (plugin.module?.tasks?.length ?? 0) + const { listCursor, detailsCursor, detailsSelected } = useNavigation({ + listLength: props.plugins.length, + getDetailsLength(cursor) { + const [, plugin] = props.plugins[cursor] + return 1 + Object.keys(plugin.module?.hooks ?? {}).length + (plugin.module?.tasks?.length ?? 0) + }, + getSelectedItem(listCursor, detailsCursor) { + const [, plugin] = props.plugins[listCursor] + const hookLength = Object.keys(plugin.module?.hooks ?? {}).length + if (detailsCursor === 0) { + return ['plugins', plugin.parent?.id ?? plugin.id] + } else if (detailsCursor <= hookLength) { + return ['hooks', Object.keys(plugin.module?.hooks ?? {})[detailsCursor - 1]] + } else { + return ['tasks', (plugin.module?.tasks ?? [])[detailsCursor - 1 - hookLength].id] + } + }, + findItem: useCallback( + (itemId) => { + return props.plugins.findIndex(([pluginId]) => pluginId === itemId) + }, + [props.plugins] + ), + startingItem: props.startingItem, + changeTab: props.onTabChange }) const [, selectedPlugin] = props.plugins[listCursor] return ( @@ -263,18 +302,44 @@ const PluginsPage = (props: PluginsPageProps) => { ) } -interface HooksPageProps { +interface HooksPageProps extends TabPageProps { hooks: [string, Hook][] taskMap: Record<string, string[]> pluginMap: Record<string, string[]> } const HooksPage = (props: HooksPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.hooks.length, (cursor) => { - const [hookId, hook] = props.hooks[cursor] - return ( - (hook.plugin ? 1 : 0) + (props.taskMap[hookId]?.length ?? 0) + (props.pluginMap[hookId]?.length ?? 0) - ) + const { listCursor, detailsCursor, detailsSelected } = useNavigation({ + listLength: props.hooks.length, + getDetailsLength(cursor) { + const [hookId, hook] = props.hooks[cursor] + return ( + (hook.plugin ? 1 : 0) + (props.taskMap[hookId]?.length ?? 0) + (props.pluginMap[hookId]?.length ?? 0) + ) + }, + getSelectedItem(listCursor, detailsCursor) { + const [hookId, hook] = props.hooks[listCursor] + if (detailsCursor === 0 && hook.plugin) { + return ['plugins', hook.plugin.id] + } else if (detailsCursor <= props.taskMap[hookId]?.length ?? 0) { + return ['tasks', props.taskMap[hookId][detailsCursor - (hook.plugin ? 1 : 0)]] + } else { + return [ + 'plugins', + props.pluginMap[hookId][ + detailsCursor - (props.taskMap[hookId]?.length ?? 0) - (hook.plugin ? 1 : 0) + ] + ] + } + }, + findItem: useCallback( + (itemId) => { + return props.hooks.findIndex(([hookId]) => hookId === itemId) + }, + [props.hooks] + ), + startingItem: props.startingItem, + changeTab: props.onTabChange }) const [hookId, selectedHook] = props.hooks[listCursor] return ( @@ -295,18 +360,44 @@ const HooksPage = (props: HooksPageProps) => { ) } -interface TasksPageProps { +interface TasksPageProps extends TabPageProps { tasks: [string, TaskClass][] pluginMap: Record<string, string[]> hookMap: Record<string, string[]> } const TasksPage = (props: TasksPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation(props.tasks.length, (cursor) => { - const [taskId, task] = props.tasks[cursor] - return ( - (task.plugin ? 1 : 0) + (props.pluginMap[taskId]?.length ?? 0) + (props.hookMap[taskId]?.length ?? 0) - ) + const { listCursor, detailsCursor, detailsSelected } = useNavigation({ + listLength: props.tasks.length, + getDetailsLength(cursor) { + const [taskId, task] = props.tasks[cursor] + return ( + (task.plugin ? 1 : 0) + (props.pluginMap[taskId]?.length ?? 0) + (props.hookMap[taskId]?.length ?? 0) + ) + }, + getSelectedItem(listCursor, detailsCursor) { + const [taskId, task] = props.tasks[listCursor] + if (detailsCursor === 0 && task.plugin) { + return ['plugins', task.plugin.id] + } else if (detailsCursor <= props.hookMap[taskId]?.length ?? 0) { + return ['hooks', props.hookMap[taskId][detailsCursor - (task.plugin ? 1 : 0)]] + } else { + return [ + 'plugins', + props.pluginMap[taskId][ + detailsCursor - (props.hookMap[taskId]?.length ?? 0) - (task.plugin ? 1 : 0) + ] + ] + } + }, + findItem: useCallback( + (itemId) => { + return props.tasks.findIndex(([taskId]) => taskId === itemId) + }, + [props.tasks] + ), + startingItem: props.startingItem, + changeTab: props.onTabChange }) const [taskId, selectedTask] = props.tasks[listCursor] return ( @@ -335,6 +426,9 @@ interface TabbedViewProps { const TabbedView = (props: TabbedViewProps) => { const [activeTab, setActiveTab] = useState<TabName>('plugins') + const [pluginsStart, setPluginsStart] = useState<string | undefined>() + const [hooksStart, setHooksStart] = useState<string | undefined>() + const [tasksStart, setTasksStart] = useState<string | undefined>() const pluginsWithHook: Record<string, string[]> = {} const pluginsWithTask: Record<string, string[]> = {} for (const [pluginId, plugin] of Object.entries(props.config.plugins)) { @@ -357,10 +451,27 @@ const TabbedView = (props: TabbedViewProps) => { hooksWithTask[task].push(hookId) } } + + const handleTabChange = (newTab: TabName, itemId?: string) => { + setActiveTab(newTab) + if (itemId) { + switch (newTab) { + case 'plugins': + setPluginsStart(itemId) + break + case 'hooks': + setHooksStart(itemId) + break + case 'tasks': + setTasksStart(itemId) + break + } + } + } return ( <> <Tabs - onChange={(newTab: TabName) => setActiveTab(newTab)} + onChange={(newTab: TabName) => handleTabChange(newTab)} showIndex={false} keyMap={{ previous: [], @@ -370,19 +481,29 @@ const TabbedView = (props: TabbedViewProps) => { <Tab name="hooks">hooks</Tab> <Tab name="tasks">tasks</Tab> </Tabs> - {activeTab === 'plugins' && <PluginsPage plugins={Object.entries(props.config.plugins)} />} + {activeTab === 'plugins' && ( + <PluginsPage + plugins={Object.entries(props.config.plugins)} + startingItem={pluginsStart} + onTabChange={handleTabChange} + /> + )} {activeTab === 'hooks' && ( <HooksPage hooks={Object.entries(props.config.hooks)} taskMap={Object.fromEntries(tasksWithHook)} pluginMap={pluginsWithHook} + startingItem={hooksStart} + onTabChange={handleTabChange} /> )} {activeTab === 'tasks' && ( <TasksPage tasks={Object.entries(props.config.tasks)} pluginMap={pluginsWithTask} + startingItem={tasksStart} hookMap={hooksWithTask} + onTabChange={handleTabChange} /> )} </> From 4508692e279c313ac6b47e44484d944e19e46379 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 28 Oct 2022 17:15:58 +0100 Subject: [PATCH 08/16] fix(box): reimplement ink-tab to allow tabs to be updated programmatically ink-tab was used to display which section of the app you were currently looking at (plugins, hooks, or tasks). It also allowed you to switch between tabs with the TAB key. However, we can also switch between tabs by selecting an item in a details page. ink-tab has no provision to allow us to update the tab programmatically, so the tab view would lag behind the actual pages if you jumped from one tab to another. There's no way to get around this with the library's API, so let's just reimplement it ourselves seeing as its used functionality is relatively simple. --- core/box/package.json | 1 - core/box/src/index.tsx | 89 ++++++----- package-lock.json | 333 +++++++++++++++++++---------------------- 3 files changed, 205 insertions(+), 218 deletions(-) diff --git a/core/box/package.json b/core/box/package.json index 5fccdc53e..0216decab 100644 --- a/core/box/package.json +++ b/core/box/package.json @@ -13,7 +13,6 @@ "@dotcom-tool-kit/logger": "file:../../lib/logger", "dotcom-tool-kit": "file:../cli", "ink": "~3.1.0", - "ink-tab": "^4.2.0", "react": "^16.14.0", "winston": "^3.6.0" }, diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index d50f266be..e55fb59df 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react' import { render, Box, BoxProps, Text, useApp, useInput } from 'ink' -import { Tabs, Tab } from 'ink-tab' import winston from 'winston' import { loadConfig, ValidConfig } from 'dotcom-tool-kit/lib/config' import { styles } from '@dotcom-tool-kit/logger' @@ -418,14 +417,15 @@ const TasksPage = (props: TasksPageProps) => { ) } -type TabName = 'plugins' | 'hooks' | 'tasks' +const TabPages = ['plugins', 'hooks', 'tasks'] as const +type TabName = typeof TabPages[number] interface TabbedViewProps { config: ValidConfig } const TabbedView = (props: TabbedViewProps) => { - const [activeTab, setActiveTab] = useState<TabName>('plugins') + const [activeTab, setActiveTab] = useState(0) const [pluginsStart, setPluginsStart] = useState<string | undefined>() const [hooksStart, setHooksStart] = useState<string | undefined>() const [tasksStart, setTasksStart] = useState<string | undefined>() @@ -453,7 +453,7 @@ const TabbedView = (props: TabbedViewProps) => { } const handleTabChange = (newTab: TabName, itemId?: string) => { - setActiveTab(newTab) + setActiveTab(TabPages.indexOf(newTab)) if (itemId) { switch (newTab) { case 'plugins': @@ -468,44 +468,53 @@ const TabbedView = (props: TabbedViewProps) => { } } } + useInput((_, key) => { + if (key.tab) { + if (key.shift) { + const prevTab = activeTab - 1 + setActiveTab(prevTab < 0 ? TabPages.length - 1 : prevTab) + } else { + setActiveTab((activeTab + 1) % TabPages.length) + } + } + }) return ( <> - <Tabs - onChange={(newTab: TabName) => handleTabChange(newTab)} - showIndex={false} - keyMap={{ - previous: [], - next: [] - }}> - <Tab name="plugins">plugins</Tab> - <Tab name="hooks">hooks</Tab> - <Tab name="tasks">tasks</Tab> - </Tabs> - {activeTab === 'plugins' && ( - <PluginsPage - plugins={Object.entries(props.config.plugins)} - startingItem={pluginsStart} - onTabChange={handleTabChange} - /> - )} - {activeTab === 'hooks' && ( - <HooksPage - hooks={Object.entries(props.config.hooks)} - taskMap={Object.fromEntries(tasksWithHook)} - pluginMap={pluginsWithHook} - startingItem={hooksStart} - onTabChange={handleTabChange} - /> - )} - {activeTab === 'tasks' && ( - <TasksPage - tasks={Object.entries(props.config.tasks)} - pluginMap={pluginsWithTask} - startingItem={tasksStart} - hookMap={hooksWithTask} - onTabChange={handleTabChange} - /> - )} + <Box> + {TabPages.map((page, index) => ( + <React.Fragment key={page}> + {index !== 0 && <Text> | </Text>} + <Text bold={index === activeTab}>{page}</Text> + </React.Fragment> + ))} + </Box> + <Box> + {TabPages[activeTab] === 'plugins' && ( + <PluginsPage + plugins={Object.entries(props.config.plugins)} + startingItem={pluginsStart} + onTabChange={handleTabChange} + /> + )} + {TabPages[activeTab] === 'hooks' && ( + <HooksPage + hooks={Object.entries(props.config.hooks)} + taskMap={Object.fromEntries(tasksWithHook)} + pluginMap={pluginsWithHook} + startingItem={hooksStart} + onTabChange={handleTabChange} + /> + )} + {TabPages[activeTab] === 'tasks' && ( + <TasksPage + tasks={Object.entries(props.config.tasks)} + pluginMap={pluginsWithTask} + startingItem={tasksStart} + hookMap={hooksWithTask} + onTabChange={handleTabChange} + /> + )} + </Box> </> ) } diff --git a/package-lock.json b/package-lock.json index cf8d8c3ce..ad2767735 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,6 @@ "@dotcom-tool-kit/logger": "file:../../lib/logger", "dotcom-tool-kit": "file:../cli", "ink": "~3.1.0", - "ink-tab": "^4.2.0", "react": "^16.14.0", "winston": "^3.6.0" }, @@ -115,18 +114,6 @@ } } }, - "core/box/node_modules/ink-tab": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ink-tab/-/ink-tab-4.2.0.tgz", - "integrity": "sha512-KrvNWrebGCXWbGf74QE0XYKYGUCa0dEgHGa3B4VJK3PQh8+wXycQeGnl1s/kq3OokWOmMyfkc/BTPgulxjXspQ==", - "dependencies": { - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "ink": "^3.0.0", - "react": "^16.8.0" - } - }, "core/box/node_modules/react": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", @@ -192,13 +179,13 @@ }, "core/cli": { "name": "dotcom-tool-kit", - "version": "2.3.6", + "version": "2.4.0", "license": "MIT", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/options": "^2.0.9", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/options": "^2.0.10", + "@dotcom-tool-kit/types": "^2.7.0", "@dotcom-tool-kit/wait-for-ok": "^2.0.1", "cosmiconfig": "^7.0.0", "lodash.groupby": "^4.6.0", @@ -212,17 +199,17 @@ "dotcom-tool-kit": "bin/run" }, "devDependencies": { - "@dotcom-tool-kit/babel": "^2.0.9", - "@dotcom-tool-kit/backend-app": "^2.0.12", - "@dotcom-tool-kit/circleci": "^2.1.7", - "@dotcom-tool-kit/circleci-heroku": "^2.0.12", - "@dotcom-tool-kit/eslint": "^2.2.2", - "@dotcom-tool-kit/frontend-app": "^2.1.10", - "@dotcom-tool-kit/heroku": "^2.1.0", - "@dotcom-tool-kit/mocha": "^2.1.6", - "@dotcom-tool-kit/n-test": "^2.1.4", - "@dotcom-tool-kit/npm": "^2.0.10", - "@dotcom-tool-kit/webpack": "^2.1.8", + "@dotcom-tool-kit/babel": "^2.0.10", + "@dotcom-tool-kit/backend-app": "^2.0.13", + "@dotcom-tool-kit/circleci": "^3.0.0", + "@dotcom-tool-kit/circleci-heroku": "^2.1.0", + "@dotcom-tool-kit/eslint": "^2.2.3", + "@dotcom-tool-kit/frontend-app": "^2.1.11", + "@dotcom-tool-kit/heroku": "^2.1.1", + "@dotcom-tool-kit/mocha": "^2.1.7", + "@dotcom-tool-kit/n-test": "^2.1.5", + "@dotcom-tool-kit/npm": "^2.0.11", + "@dotcom-tool-kit/webpack": "^2.1.9", "@jest/globals": "^27.4.6", "@types/lodash.groupby": "^4.6.7", "@types/lodash.merge": "^4.6.6", @@ -261,12 +248,12 @@ }, "core/create": { "name": "@dotcom-tool-kit/create", - "version": "2.3.1", + "version": "2.3.2", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@financial-times/package-json": "^3.0.0", "@quarterto/parse-makefile-rules": "^1.1.0", "import-cwd": "^3.0.0", @@ -289,7 +276,7 @@ "@types/pacote": "^11.1.3", "@types/prompts": "^2.0.14", "cosmiconfig": "^7.0.1", - "dotcom-tool-kit": "^2.3.6" + "dotcom-tool-kit": "^2.4.0" } }, "core/create/node_modules/tslib": { @@ -302,11 +289,11 @@ "license": "ISC", "devDependencies": { "@dotcom-tool-kit/circleci-heroku": "^2.0.0", - "@dotcom-tool-kit/circleci-npm": "^2.0.0", + "@dotcom-tool-kit/circleci-npm": "^3.0.0", "@dotcom-tool-kit/eslint": "^2.0.0", "@dotcom-tool-kit/frontend-app": "^2.0.0", "@dotcom-tool-kit/jest": "^2.0.0", - "@dotcom-tool-kit/lint-staged": "^2.0.0", + "@dotcom-tool-kit/lint-staged": "^3.0.0", "@dotcom-tool-kit/lint-staged-npm": "^2.0.0", "@dotcom-tool-kit/mocha": "^2.0.0", "@dotcom-tool-kit/n-test": "^2.0.0", @@ -358,10 +345,10 @@ }, "lib/options": { "name": "@dotcom-tool-kit/options", - "version": "2.0.9", + "version": "2.0.10", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1" } }, @@ -372,7 +359,7 @@ }, "lib/package-json-hook": { "name": "@dotcom-tool-kit/package-json-hook", - "version": "2.1.1", + "version": "3.0.0", "license": "ISC", "dependencies": { "@financial-times/package-json": "^3.0.0", @@ -405,7 +392,7 @@ }, "lib/types": { "name": "@dotcom-tool-kit/types", - "version": "2.6.2", + "version": "2.7.0", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.0", @@ -427,12 +414,12 @@ }, "lib/vault": { "name": "@dotcom-tool-kit/vault", - "version": "2.0.9", + "version": "2.0.10", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", - "@dotcom-tool-kit/options": "^2.0.9", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/options": "^2.0.10", + "@dotcom-tool-kit/types": "^2.7.0", "@financial-times/n-fetch": "^1.0.0-beta.7", "fs": "0.0.1-security", "os": "^0.1.2", @@ -20400,12 +20387,12 @@ }, "plugins/babel": { "name": "@dotcom-tool-kit/babel", - "version": "2.0.9", + "version": "2.0.10", "license": "MIT", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "fast-glob": "^3.2.11", "tslib": "^2.3.1" }, @@ -20426,12 +20413,12 @@ }, "plugins/backend-app": { "name": "@dotcom-tool-kit/backend-app", - "version": "2.0.12", + "version": "2.0.13", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/circleci-heroku": "^2.0.12", - "@dotcom-tool-kit/node": "^2.2.2", - "@dotcom-tool-kit/npm": "^2.0.10" + "@dotcom-tool-kit/circleci-heroku": "^2.1.0", + "@dotcom-tool-kit/node": "^2.2.3", + "@dotcom-tool-kit/npm": "^2.0.11" }, "peerDependencies": { "dotcom-tool-kit": "2.x" @@ -20439,13 +20426,13 @@ }, "plugins/circleci": { "name": "@dotcom-tool-kit/circleci", - "version": "2.1.7", + "version": "3.0.0", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.0", "@dotcom-tool-kit/logger": "^2.1.1", "@dotcom-tool-kit/state": "^2.0.0", - "@dotcom-tool-kit/types": "^2.6.1", + "@dotcom-tool-kit/types": "^2.7.0", "lodash": "^4.17.21", "tslib": "^2.3.1", "yaml": "^2.1.1" @@ -20463,11 +20450,11 @@ }, "plugins/circleci-heroku": { "name": "@dotcom-tool-kit/circleci-heroku", - "version": "2.0.12", + "version": "2.1.0", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/circleci": "^2.1.7", - "@dotcom-tool-kit/heroku": "^2.1.0", + "@dotcom-tool-kit/circleci": "^3.0.0", + "@dotcom-tool-kit/heroku": "^2.1.1", "tslib": "^2.3.1" }, "peerDependencies": { @@ -20481,12 +20468,12 @@ }, "plugins/circleci-npm": { "name": "@dotcom-tool-kit/circleci-npm", - "version": "2.0.10", + "version": "3.0.0", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/circleci": "^2.1.7", - "@dotcom-tool-kit/npm": "^2.0.10", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/circleci": "^3.0.0", + "@dotcom-tool-kit/npm": "^2.0.11", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1" }, "peerDependencies": { @@ -20513,18 +20500,19 @@ }, "plugins/component": { "name": "@dotcom-tool-kit/component", - "version": "2.0.0", + "version": "2.0.1", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/circleci-npm": "^2.0.9", - "@dotcom-tool-kit/npm": "^2.0.9" + "@dotcom-tool-kit/circleci-npm": "^3.0.0", + "@dotcom-tool-kit/npm": "^2.0.11" }, "peerDependencies": { "dotcom-tool-kit": "2.x" } }, "plugins/cypress": { - "version": "0.1.0", + "name": "@dotcom-tool-kit/cypress", + "version": "2.0.0", "license": "ISC", "peerDependencies": { "dotcom-tool-kit": "2.x" @@ -20532,12 +20520,12 @@ }, "plugins/eslint": { "name": "@dotcom-tool-kit/eslint", - "version": "2.2.2", + "version": "2.2.3", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1" }, "devDependencies": { @@ -20750,11 +20738,11 @@ }, "plugins/frontend-app": { "name": "@dotcom-tool-kit/frontend-app", - "version": "2.1.10", + "version": "2.1.11", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/backend-app": "^2.0.12", - "@dotcom-tool-kit/webpack": "^2.1.8" + "@dotcom-tool-kit/backend-app": "^2.0.13", + "@dotcom-tool-kit/webpack": "^2.1.9" }, "peerDependencies": { "dotcom-tool-kit": "2.x" @@ -20762,16 +20750,16 @@ }, "plugins/heroku": { "name": "@dotcom-tool-kit/heroku", - "version": "2.1.0", + "version": "2.1.1", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/npm": "^2.0.10", - "@dotcom-tool-kit/package-json-hook": "^2.1.1", + "@dotcom-tool-kit/npm": "^2.0.11", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", - "@dotcom-tool-kit/vault": "^2.0.9", + "@dotcom-tool-kit/types": "^2.7.0", + "@dotcom-tool-kit/vault": "^2.0.10", "@dotcom-tool-kit/wait-for-ok": "^2.0.1", "@octokit/request": "^5.6.0", "@octokit/request-error": "^2.1.0", @@ -20797,10 +20785,10 @@ }, "plugins/husky-npm": { "name": "@dotcom-tool-kit/husky-npm", - "version": "2.2.1", + "version": "3.0.0", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/package-json-hook": "^2.1.1", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", "tslib": "^2.3.1" }, "peerDependencies": { @@ -20815,11 +20803,11 @@ }, "plugins/jest": { "name": "@dotcom-tool-kit/jest", - "version": "2.0.9", + "version": "2.0.10", "license": "ISC", "dependencies": { "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1" }, "devDependencies": { @@ -20838,12 +20826,12 @@ }, "plugins/lint-staged": { "name": "@dotcom-tool-kit/lint-staged", - "version": "2.1.9", + "version": "3.0.0", "license": "ISC", "dependencies": { "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/package-json-hook": "^2.1.1", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", + "@dotcom-tool-kit/types": "^2.7.0", "lint-staged": "^11.2.3", "tslib": "^2.3.1" }, @@ -20853,12 +20841,12 @@ }, "plugins/lint-staged-npm": { "name": "@dotcom-tool-kit/lint-staged-npm", - "version": "2.0.10", + "version": "2.0.11", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/husky-npm": "^2.2.1", - "@dotcom-tool-kit/lint-staged": "^2.1.9", - "@dotcom-tool-kit/options": "^2.0.9", + "@dotcom-tool-kit/husky-npm": "^3.0.0", + "@dotcom-tool-kit/lint-staged": "^3.0.0", + "@dotcom-tool-kit/options": "^2.0.10", "tslib": "^2.3.1" }, "peerDependencies": { @@ -20927,12 +20915,12 @@ }, "plugins/mocha": { "name": "@dotcom-tool-kit/mocha", - "version": "2.1.6", + "version": "2.1.7", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "fs": "0.0.1-security", "glob": "^7.1.7", "mocha": "^8.3.2", @@ -20955,12 +20943,12 @@ }, "plugins/n-test": { "name": "@dotcom-tool-kit/n-test", - "version": "2.1.4", + "version": "2.1.5", "license": "ISC", "dependencies": { "@dotcom-tool-kit/logger": "^2.1.2", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@financial-times/n-test": "^4.0.1", "tslib": "^2.3.1" }, @@ -20980,14 +20968,14 @@ }, "plugins/next-router": { "name": "@dotcom-tool-kit/next-router", - "version": "2.0.9", + "version": "2.0.10", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", - "@dotcom-tool-kit/vault": "^2.0.9", + "@dotcom-tool-kit/types": "^2.7.0", + "@dotcom-tool-kit/vault": "^2.0.10", "ft-next-router": "^1.0.0", "tslib": "^2.3.1" }, @@ -21002,13 +20990,13 @@ }, "plugins/node": { "name": "@dotcom-tool-kit/node", - "version": "2.2.2", + "version": "2.2.3", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", - "@dotcom-tool-kit/vault": "^2.0.9", + "@dotcom-tool-kit/types": "^2.7.0", + "@dotcom-tool-kit/vault": "^2.0.10", "get-port": "^5.1.1", "tslib": "^2.3.1", "wait-port": "^0.2.9" @@ -21024,13 +21012,13 @@ }, "plugins/nodemon": { "name": "@dotcom-tool-kit/nodemon", - "version": "2.1.2", + "version": "2.1.3", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", - "@dotcom-tool-kit/vault": "^2.0.9", + "@dotcom-tool-kit/types": "^2.7.0", + "@dotcom-tool-kit/vault": "^2.0.10", "get-port": "^5.1.1", "tslib": "^2.3.1" }, @@ -21049,14 +21037,14 @@ }, "plugins/npm": { "name": "@dotcom-tool-kit/npm", - "version": "2.0.10", + "version": "2.0.11", "license": "ISC", "dependencies": { "@actions/exec": "^1.1.0", "@dotcom-tool-kit/error": "^2.0.1", - "@dotcom-tool-kit/package-json-hook": "^2.1.1", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "libnpmpack": "^3.1.0", "libnpmpublish": "^5.0.1", "pacote": "^12.0.3", @@ -21213,10 +21201,10 @@ }, "plugins/pa11y": { "name": "@dotcom-tool-kit/pa11y", - "version": "0.3.6", + "version": "0.3.7", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "pa11y-ci": "^3.0.1", "tslib": "^2.3.1" }, @@ -21234,13 +21222,13 @@ }, "plugins/prettier": { "name": "@dotcom-tool-kit/prettier", - "version": "2.1.0", + "version": "2.1.1", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/package-json-hook": "^2.1.1", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", + "@dotcom-tool-kit/types": "^2.7.0", "fast-glob": "^3.2.7", "hook-std": "^2.0.0", "prettier": "^2.2.1", @@ -21263,11 +21251,11 @@ }, "plugins/secret-squirrel": { "name": "@dotcom-tool-kit/secret-squirrel", - "version": "1.0.8", + "version": "1.0.9", "license": "ISC", "dependencies": { "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1" }, "peerDependencies": { @@ -21282,12 +21270,12 @@ }, "plugins/upload-assets-to-s3": { "name": "@dotcom-tool-kit/upload-assets-to-s3", - "version": "2.0.10", + "version": "2.0.11", "license": "ISC", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "aws-sdk": "^2.901.0", "glob": "^7.1.6", "mime": "^2.5.2", @@ -21312,12 +21300,12 @@ }, "plugins/webpack": { "name": "@dotcom-tool-kit/webpack", - "version": "2.1.8", + "version": "2.1.9", "license": "MIT", "dependencies": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1", "webpack-cli": "^4.6.0" }, @@ -22574,7 +22562,7 @@ "@babel/preset-env": "^7.16.11", "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@jest/globals": "^27.4.6", "fast-glob": "^3.2.11", "tslib": "^2.3.1", @@ -22591,9 +22579,9 @@ "@dotcom-tool-kit/backend-app": { "version": "file:plugins/backend-app", "requires": { - "@dotcom-tool-kit/circleci-heroku": "^2.0.12", - "@dotcom-tool-kit/node": "^2.2.2", - "@dotcom-tool-kit/npm": "^2.0.10" + "@dotcom-tool-kit/circleci-heroku": "^2.1.0", + "@dotcom-tool-kit/node": "^2.2.3", + "@dotcom-tool-kit/npm": "^2.0.11" } }, "@dotcom-tool-kit/box": { @@ -22604,7 +22592,6 @@ "@types/react": "^16.14.24", "dotcom-tool-kit": "file:../cli", "ink": "~3.1.0", - "ink-tab": "^4.2.0", "react": "^16.14.0", "winston": "^3.6.0" }, @@ -22650,14 +22637,6 @@ "yoga-layout-prebuilt": "^1.9.6" } }, - "ink-tab": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ink-tab/-/ink-tab-4.2.0.tgz", - "integrity": "sha512-KrvNWrebGCXWbGf74QE0XYKYGUCa0dEgHGa3B4VJK3PQh8+wXycQeGnl1s/kq3OokWOmMyfkc/BTPgulxjXspQ==", - "requires": { - "prop-types": "^15.7.2" - } - }, "react": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", @@ -22711,7 +22690,7 @@ "@dotcom-tool-kit/error": "^2.0.0", "@dotcom-tool-kit/logger": "^2.1.1", "@dotcom-tool-kit/state": "^2.0.0", - "@dotcom-tool-kit/types": "^2.6.1", + "@dotcom-tool-kit/types": "^2.7.0", "@jest/globals": "^27.4.6", "@types/jest": "^27.4.0", "@types/js-yaml": "^4.0.3", @@ -22737,8 +22716,8 @@ "@dotcom-tool-kit/circleci-heroku": { "version": "file:plugins/circleci-heroku", "requires": { - "@dotcom-tool-kit/circleci": "^2.1.7", - "@dotcom-tool-kit/heroku": "^2.1.0", + "@dotcom-tool-kit/circleci": "^3.0.0", + "@dotcom-tool-kit/heroku": "^2.1.1", "tslib": "^2.3.1" }, "dependencies": { @@ -22752,9 +22731,9 @@ "@dotcom-tool-kit/circleci-npm": { "version": "file:plugins/circleci-npm", "requires": { - "@dotcom-tool-kit/circleci": "^2.1.7", - "@dotcom-tool-kit/npm": "^2.0.10", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/circleci": "^3.0.0", + "@dotcom-tool-kit/npm": "^2.0.11", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1" }, "dependencies": { @@ -22768,8 +22747,8 @@ "@dotcom-tool-kit/component": { "version": "file:plugins/component", "requires": { - "@dotcom-tool-kit/circleci-npm": "^2.0.9", - "@dotcom-tool-kit/npm": "^2.0.9" + "@dotcom-tool-kit/circleci-npm": "^3.0.0", + "@dotcom-tool-kit/npm": "^2.0.11" } }, "@dotcom-tool-kit/create": { @@ -22777,7 +22756,7 @@ "requires": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@financial-times/package-json": "^3.0.0", "@quarterto/parse-makefile-rules": "^1.1.0", "@types/financial-times__package-json": "^1.9.0", @@ -22787,7 +22766,7 @@ "@types/pacote": "^11.1.3", "@types/prompts": "^2.0.14", "cosmiconfig": "^7.0.1", - "dotcom-tool-kit": "^2.3.6", + "dotcom-tool-kit": "^2.4.0", "import-cwd": "^3.0.0", "js-yaml": "^4.1.0", "komatsu": "^1.3.0", @@ -22825,7 +22804,7 @@ "requires": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@jest/globals": "^27.4.6", "@types/eslint": "^7.2.13", "eslint": "^8.15.0", @@ -22971,8 +22950,8 @@ "@dotcom-tool-kit/frontend-app": { "version": "file:plugins/frontend-app", "requires": { - "@dotcom-tool-kit/backend-app": "^2.0.12", - "@dotcom-tool-kit/webpack": "^2.1.8" + "@dotcom-tool-kit/backend-app": "^2.0.13", + "@dotcom-tool-kit/webpack": "^2.1.9" } }, "@dotcom-tool-kit/heroku": { @@ -22980,11 +22959,11 @@ "requires": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/npm": "^2.0.10", - "@dotcom-tool-kit/package-json-hook": "^2.1.1", + "@dotcom-tool-kit/npm": "^2.0.11", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", - "@dotcom-tool-kit/vault": "^2.0.9", + "@dotcom-tool-kit/types": "^2.7.0", + "@dotcom-tool-kit/vault": "^2.0.10", "@dotcom-tool-kit/wait-for-ok": "^2.0.1", "@octokit/request": "^5.6.0", "@octokit/request-error": "^2.1.0", @@ -23008,7 +22987,7 @@ "@dotcom-tool-kit/husky-npm": { "version": "file:plugins/husky-npm", "requires": { - "@dotcom-tool-kit/package-json-hook": "^2.1.1", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", "tslib": "^2.3.1" }, "dependencies": { @@ -23023,7 +23002,7 @@ "version": "file:plugins/jest", "requires": { "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@jest/globals": "^27.4.6", "tslib": "^2.3.1", "winston": "^3.5.1" @@ -23040,8 +23019,8 @@ "version": "file:plugins/lint-staged", "requires": { "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/package-json-hook": "^2.1.1", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", + "@dotcom-tool-kit/types": "^2.7.0", "lint-staged": "^11.2.3", "tslib": "^2.3.1" }, @@ -23087,9 +23066,9 @@ "@dotcom-tool-kit/lint-staged-npm": { "version": "file:plugins/lint-staged-npm", "requires": { - "@dotcom-tool-kit/husky-npm": "^2.2.1", - "@dotcom-tool-kit/lint-staged": "^2.1.9", - "@dotcom-tool-kit/options": "^2.0.9", + "@dotcom-tool-kit/husky-npm": "^3.0.0", + "@dotcom-tool-kit/lint-staged": "^3.0.0", + "@dotcom-tool-kit/options": "^2.0.10", "tslib": "^2.3.1" }, "dependencies": { @@ -23125,7 +23104,7 @@ "requires": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@jest/globals": "^27.4.6", "@types/glob": "^7.1.3", "@types/mocha": "^8.2.2", @@ -23148,7 +23127,7 @@ "requires": { "@dotcom-tool-kit/logger": "^2.1.2", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@financial-times/n-test": "^4.0.1", "@jest/globals": "^27.4.6", "@types/jest": "^27.4.0", @@ -23169,8 +23148,8 @@ "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", - "@dotcom-tool-kit/vault": "^2.0.9", + "@dotcom-tool-kit/types": "^2.7.0", + "@dotcom-tool-kit/vault": "^2.0.10", "ft-next-router": "^1.0.0", "tslib": "^2.3.1" }, @@ -23187,8 +23166,8 @@ "requires": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", - "@dotcom-tool-kit/vault": "^2.0.9", + "@dotcom-tool-kit/types": "^2.7.0", + "@dotcom-tool-kit/vault": "^2.0.10", "get-port": "^5.1.1", "tslib": "^2.3.1", "wait-port": "^0.2.9" @@ -23206,8 +23185,8 @@ "requires": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", - "@dotcom-tool-kit/vault": "^2.0.9", + "@dotcom-tool-kit/types": "^2.7.0", + "@dotcom-tool-kit/vault": "^2.0.10", "@types/nodemon": "^1.19.1", "get-port": "^5.1.1", "tslib": "^2.3.1" @@ -23225,9 +23204,9 @@ "requires": { "@actions/exec": "^1.1.0", "@dotcom-tool-kit/error": "^2.0.1", - "@dotcom-tool-kit/package-json-hook": "^2.1.1", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", "@dotcom-tool-kit/state": "^2.0.1", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@types/libnpmpublish": "^4.0.1", "@types/pacote": "^11.1.3", "@types/tar": "^6.1.1", @@ -23344,7 +23323,7 @@ "@dotcom-tool-kit/options": { "version": "file:lib/options", "requires": { - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1" }, "dependencies": { @@ -23358,7 +23337,7 @@ "@dotcom-tool-kit/pa11y": { "version": "file:plugins/pa11y", "requires": { - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@types/pa11y": "^5.3.4", "pa11y-ci": "^3.0.1", "tslib": "^2.3.1" @@ -23394,8 +23373,8 @@ "requires": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/package-json-hook": "^2.1.1", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/package-json-hook": "^3.0.0", + "@dotcom-tool-kit/types": "^2.7.0", "@jest/globals": "^27.4.6", "fast-glob": "^3.2.7", "hook-std": "^2.0.0", @@ -23417,11 +23396,11 @@ "version": "file:core/sandbox", "requires": { "@dotcom-tool-kit/circleci-heroku": "^2.0.0", - "@dotcom-tool-kit/circleci-npm": "^2.0.0", + "@dotcom-tool-kit/circleci-npm": "^3.0.0", "@dotcom-tool-kit/eslint": "^2.0.0", "@dotcom-tool-kit/frontend-app": "^2.0.0", "@dotcom-tool-kit/jest": "^2.0.0", - "@dotcom-tool-kit/lint-staged": "^2.0.0", + "@dotcom-tool-kit/lint-staged": "^3.0.0", "@dotcom-tool-kit/lint-staged-npm": "^2.0.0", "@dotcom-tool-kit/mocha": "^2.0.0", "@dotcom-tool-kit/n-test": "^2.0.0", @@ -23440,7 +23419,7 @@ "version": "file:plugins/secret-squirrel", "requires": { "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "tslib": "^2.3.1" }, "dependencies": { @@ -23490,7 +23469,7 @@ "@aws-sdk/types": "^3.13.1", "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@jest/globals": "^27.4.6", "@types/glob": "^7.1.3", "@types/jest": "^27.4.0", @@ -23513,8 +23492,8 @@ "version": "file:lib/vault", "requires": { "@dotcom-tool-kit/error": "^2.0.1", - "@dotcom-tool-kit/options": "^2.0.9", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/options": "^2.0.10", + "@dotcom-tool-kit/types": "^2.7.0", "@financial-times/n-fetch": "^1.0.0-beta.7", "@types/jest": "^27.0.2", "fs": "0.0.1-security", @@ -23567,7 +23546,7 @@ "requires": { "@dotcom-tool-kit/error": "^2.0.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/types": "^2.7.0", "@jest/globals": "^27.4.6", "ts-node": "^10.0.0", "tslib": "^2.3.1", @@ -27236,22 +27215,22 @@ "dotcom-tool-kit": { "version": "file:core/cli", "requires": { - "@dotcom-tool-kit/babel": "^2.0.9", - "@dotcom-tool-kit/backend-app": "^2.0.12", - "@dotcom-tool-kit/circleci": "^2.1.7", - "@dotcom-tool-kit/circleci-heroku": "^2.0.12", + "@dotcom-tool-kit/babel": "^2.0.10", + "@dotcom-tool-kit/backend-app": "^2.0.13", + "@dotcom-tool-kit/circleci": "^3.0.0", + "@dotcom-tool-kit/circleci-heroku": "^2.1.0", "@dotcom-tool-kit/error": "^2.0.1", - "@dotcom-tool-kit/eslint": "^2.2.2", - "@dotcom-tool-kit/frontend-app": "^2.1.10", - "@dotcom-tool-kit/heroku": "^2.1.0", + "@dotcom-tool-kit/eslint": "^2.2.3", + "@dotcom-tool-kit/frontend-app": "^2.1.11", + "@dotcom-tool-kit/heroku": "^2.1.1", "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/mocha": "^2.1.6", - "@dotcom-tool-kit/n-test": "^2.1.4", - "@dotcom-tool-kit/npm": "^2.0.10", - "@dotcom-tool-kit/options": "^2.0.9", - "@dotcom-tool-kit/types": "^2.6.2", + "@dotcom-tool-kit/mocha": "^2.1.7", + "@dotcom-tool-kit/n-test": "^2.1.5", + "@dotcom-tool-kit/npm": "^2.0.11", + "@dotcom-tool-kit/options": "^2.0.10", + "@dotcom-tool-kit/types": "^2.7.0", "@dotcom-tool-kit/wait-for-ok": "^2.0.1", - "@dotcom-tool-kit/webpack": "^2.1.8", + "@dotcom-tool-kit/webpack": "^2.1.9", "@jest/globals": "^27.4.6", "@types/lodash.groupby": "^4.6.7", "@types/lodash.merge": "^4.6.6", From eed09051f7bac628c4010e76eb03d89b4d3d348d Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Sun, 30 Oct 2022 20:21:34 +0000 Subject: [PATCH 09/16] chore(box): update to latest version of ink and supported react We're no longer being held back in order to support ink-tab now that we've implemented its functionality ourselves. --- core/box/package.json | 6 +- package-lock.json | 424 +++++++++++++++++++++--------------------- 2 files changed, 215 insertions(+), 215 deletions(-) diff --git a/core/box/package.json b/core/box/package.json index 0216decab..b291cce13 100644 --- a/core/box/package.json +++ b/core/box/package.json @@ -12,8 +12,8 @@ "dependencies": { "@dotcom-tool-kit/logger": "file:../../lib/logger", "dotcom-tool-kit": "file:../cli", - "ink": "~3.1.0", - "react": "^16.14.0", + "ink": "^3.2.0", + "react": "^17.0.2", "winston": "^3.6.0" }, "repository": { @@ -32,6 +32,6 @@ }, "devDependencies": { "@dotcom-tool-kit/types": "file:../../lib/types", - "@types/react": "^16.14.24" + "@types/react": "^17.0.52" } } diff --git a/package-lock.json b/package-lock.json index ad2767735..b4c513123 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,129 +52,13 @@ "dependencies": { "@dotcom-tool-kit/logger": "file:../../lib/logger", "dotcom-tool-kit": "file:../cli", - "ink": "~3.1.0", - "react": "^16.14.0", + "ink": "^3.2.0", + "react": "^17.0.2", "winston": "^3.6.0" }, "devDependencies": { "@dotcom-tool-kit/types": "file:../../lib/types", - "@types/react": "^16.14.24" - } - }, - "core/box/node_modules/@types/react": { - "version": "16.14.29", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.29.tgz", - "integrity": "sha512-I5IwEaefGZbpmmK1J7zHwZe3JkGxcRkc5WJUDWmNySVVovueViRTEUWV7spTvpe96l3nbKD/K6+GxoN69CYb/w==", - "devOptional": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "core/box/node_modules/ink": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-3.1.0.tgz", - "integrity": "sha512-vET9Yd1dFIckwUw9bDQHITMj4BdySCCjfviqQ65dNAyjzXoCr3LZI8h4pIeaRpivmXnK16trVexCgGwTRZJHYw==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "auto-bind": "4.0.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.0", - "cli-cursor": "^3.1.0", - "cli-truncate": "^2.1.0", - "code-excerpt": "^3.0.0", - "indent-string": "^4.0.0", - "is-ci": "^2.0.0", - "lodash": "^4.17.20", - "patch-console": "^1.0.0", - "react-devtools-core": "^4.6.0", - "react-reconciler": "^0.24.0", - "scheduler": "^0.18.0", - "signal-exit": "^3.0.2", - "slice-ansi": "^3.0.0", - "stack-utils": "^2.0.2", - "string-width": "^4.2.2", - "type-fest": "^0.12.0", - "widest-line": "^3.1.0", - "wrap-ansi": "^6.2.0", - "ws": "^7.2.5", - "yoga-layout-prebuilt": "^1.9.6" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": ">=16.8.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "core/box/node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "core/box/node_modules/react-reconciler": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.24.0.tgz", - "integrity": "sha512-gAGnwWkf+NOTig9oOowqid9O0HjTDC+XVGBCAmJYYJ2A2cN/O4gDdIuuUQjv8A4v6GDwVfJkagpBBLW5OW9HSw==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.18.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^16.0.0" - } - }, - "core/box/node_modules/scheduler": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", - "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "core/box/node_modules/type-fest": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", - "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "core/box/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" + "@types/react": "^17.0.52" } }, "core/cli": { @@ -4346,6 +4230,17 @@ "@types/node": "*" } }, + "node_modules/@types/react": { + "version": "17.0.52", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.52.tgz", + "integrity": "sha512-vwk8QqVODi0VaZZpDXQCmEmiOuyjEFPY7Ttaw5vjM112LOq37yz1CDJGrRJwA1fYEq4Iitd5rnjd1yWAc/bT+A==", + "devOptional": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/request": { "version": "2.48.8", "license": "MIT", @@ -10502,6 +10397,72 @@ "version": "1.3.8", "license": "ISC" }, + "node_modules/ink": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", + "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "auto-bind": "4.0.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "code-excerpt": "^3.0.0", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "lodash": "^4.17.20", + "patch-console": "^1.0.0", + "react-devtools-core": "^4.19.1", + "react-reconciler": "^0.26.2", + "scheduler": "^0.20.2", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "stack-utils": "^2.0.2", + "string-width": "^4.2.2", + "type-fest": "^0.12.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "ws": "^7.5.5", + "yoga-layout-prebuilt": "^1.9.6" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": ">=16.8.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/ink/node_modules/type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/inquirer": { "version": "7.3.3", "license": "MIT", @@ -15864,6 +15825,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -15873,7 +15835,8 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true }, "node_modules/protocolify": { "version": "3.0.0", @@ -16150,6 +16113,18 @@ "node": ">=0.10.0" } }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-devtools-core": { "version": "4.25.0", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.25.0.tgz", @@ -16163,6 +16138,22 @@ "version": "17.0.2", "license": "MIT" }, + "node_modules/react-reconciler": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", + "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^17.0.2" + } + }, "node_modules/read-package-json": { "version": "5.0.1", "license": "ISC", @@ -16978,6 +16969,15 @@ "node": ">= 0.10.0" } }, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "node_modules/schema-utils": { "version": "1.0.0", "license": "MIT", @@ -22589,99 +22589,11 @@ "requires": { "@dotcom-tool-kit/logger": "file:../../lib/logger", "@dotcom-tool-kit/types": "file:../../lib/types", - "@types/react": "^16.14.24", + "@types/react": "^17.0.52", "dotcom-tool-kit": "file:../cli", - "ink": "~3.1.0", - "react": "^16.14.0", + "ink": "^3.2.0", + "react": "^17.0.2", "winston": "^3.6.0" - }, - "dependencies": { - "@types/react": { - "version": "16.14.29", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.29.tgz", - "integrity": "sha512-I5IwEaefGZbpmmK1J7zHwZe3JkGxcRkc5WJUDWmNySVVovueViRTEUWV7spTvpe96l3nbKD/K6+GxoN69CYb/w==", - "devOptional": true, - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "ink": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-3.1.0.tgz", - "integrity": "sha512-vET9Yd1dFIckwUw9bDQHITMj4BdySCCjfviqQ65dNAyjzXoCr3LZI8h4pIeaRpivmXnK16trVexCgGwTRZJHYw==", - "requires": { - "ansi-escapes": "^4.2.1", - "auto-bind": "4.0.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.0", - "cli-cursor": "^3.1.0", - "cli-truncate": "^2.1.0", - "code-excerpt": "^3.0.0", - "indent-string": "^4.0.0", - "is-ci": "^2.0.0", - "lodash": "^4.17.20", - "patch-console": "^1.0.0", - "react-devtools-core": "^4.6.0", - "react-reconciler": "^0.24.0", - "scheduler": "^0.18.0", - "signal-exit": "^3.0.2", - "slice-ansi": "^3.0.0", - "stack-utils": "^2.0.2", - "string-width": "^4.2.2", - "type-fest": "^0.12.0", - "widest-line": "^3.1.0", - "wrap-ansi": "^6.2.0", - "ws": "^7.2.5", - "yoga-layout-prebuilt": "^1.9.6" - } - }, - "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-reconciler": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.24.0.tgz", - "integrity": "sha512-gAGnwWkf+NOTig9oOowqid9O0HjTDC+XVGBCAmJYYJ2A2cN/O4gDdIuuUQjv8A4v6GDwVfJkagpBBLW5OW9HSw==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.18.0" - } - }, - "scheduler": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", - "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "type-fest": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", - "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } } }, "@dotcom-tool-kit/circleci": { @@ -24987,6 +24899,17 @@ "@types/node": "*" } }, + "@types/react": { + "version": "17.0.52", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.52.tgz", + "integrity": "sha512-vwk8QqVODi0VaZZpDXQCmEmiOuyjEFPY7Ttaw5vjM112LOq37yz1CDJGrRJwA1fYEq4Iitd5rnjd1yWAc/bT+A==", + "devOptional": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, "@types/request": { "version": "2.48.8", "requires": { @@ -29121,6 +29044,53 @@ "ini": { "version": "1.3.8" }, + "ink": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", + "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", + "requires": { + "ansi-escapes": "^4.2.1", + "auto-bind": "4.0.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "code-excerpt": "^3.0.0", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "lodash": "^4.17.20", + "patch-console": "^1.0.0", + "react-devtools-core": "^4.19.1", + "react-reconciler": "^0.26.2", + "scheduler": "^0.20.2", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "stack-utils": "^2.0.2", + "string-width": "^4.2.2", + "type-fest": "^0.12.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "ws": "^7.5.5", + "yoga-layout-prebuilt": "^1.9.6" + }, + "dependencies": { + "type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "inquirer": { "version": "7.3.3", "requires": { @@ -32672,6 +32642,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -32681,7 +32652,8 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true } } }, @@ -32876,6 +32848,15 @@ } } }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "react-devtools-core": { "version": "4.25.0", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.25.0.tgz", @@ -32888,6 +32869,16 @@ "react-is": { "version": "17.0.2" }, + "react-reconciler": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", + "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + } + }, "read-package-json": { "version": "5.0.1", "requires": { @@ -33426,6 +33417,15 @@ "scarlet": { "version": "2.0.20" }, + "scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "schema-utils": { "version": "1.0.0", "requires": { From 8b0e1f5cc906c57fd90faec9170c6253ee20ff56 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Sun, 30 Oct 2022 20:22:55 +0000 Subject: [PATCH 10/16] fix(box): enable strict mode to check against some unsafe patterns --- core/box/src/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index e55fb59df..7339b2afc 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -522,7 +522,11 @@ const TabbedView = (props: TabbedViewProps) => { async function main() { const config = await loadConfig(logger) - render(<TabbedView config={config} />) + render( + <React.StrictMode> + <TabbedView config={config} /> + </React.StrictMode> + ) } main() From ebdbf4fa0b326a958f80ebf69041315652a68486 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Sun, 30 Oct 2022 20:51:17 +0000 Subject: [PATCH 11/16] feat(box): make current tab more visible --- core/box/src/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index 7339b2afc..cc4eaef1d 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -419,6 +419,10 @@ const TasksPage = (props: TasksPageProps) => { const TabPages = ['plugins', 'hooks', 'tasks'] as const type TabName = typeof TabPages[number] +// Our styling uses ansi-colors, whereas ink uses chalk, and there's no nice +// way to convert between the two, so lets just copy the appropriate colours +// as strings. +const tabColours = { plugins: 'cyan', hooks: 'magenta', tasks: 'blueBright' } interface TabbedViewProps { config: ValidConfig @@ -484,7 +488,7 @@ const TabbedView = (props: TabbedViewProps) => { {TabPages.map((page, index) => ( <React.Fragment key={page}> {index !== 0 && <Text> | </Text>} - <Text bold={index === activeTab}>{page}</Text> + <Text backgroundColor={index === activeTab ? tabColours[page] : undefined}>{page}</Text> </React.Fragment> ))} </Box> From c14c45d4b7fc4fa4a51b6c3f648091166f566ff7 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 9 Dec 2022 17:54:25 +0000 Subject: [PATCH 12/16] refactor(box): split components into separate files --- core/box/src/components/DetailsBox.tsx | 8 + core/box/src/components/List.tsx | 19 + core/box/src/components/SelectableBox.tsx | 15 + core/box/src/components/TabbedView.tsx | 111 +++++ core/box/src/components/pages/Hooks.tsx | 109 +++++ core/box/src/components/pages/Plugins.tsx | 99 ++++ core/box/src/components/pages/Tasks.tsx | 106 +++++ core/box/src/components/pages/shared.tsx | 88 ++++ core/box/src/index.tsx | 528 +--------------------- 9 files changed, 560 insertions(+), 523 deletions(-) create mode 100644 core/box/src/components/DetailsBox.tsx create mode 100644 core/box/src/components/List.tsx create mode 100644 core/box/src/components/SelectableBox.tsx create mode 100644 core/box/src/components/TabbedView.tsx create mode 100644 core/box/src/components/pages/Hooks.tsx create mode 100644 core/box/src/components/pages/Plugins.tsx create mode 100644 core/box/src/components/pages/Tasks.tsx create mode 100644 core/box/src/components/pages/shared.tsx diff --git a/core/box/src/components/DetailsBox.tsx b/core/box/src/components/DetailsBox.tsx new file mode 100644 index 000000000..6e679bd5d --- /dev/null +++ b/core/box/src/components/DetailsBox.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import { SelectableBox, SelectableBoxProps } from './SelectableBox' + +export const DetailsBox = (props: React.PropsWithChildren<SelectableBoxProps>): JSX.Element => ( + <SelectableBox width={72} flexDirection="column" {...props}> + {props.children} + </SelectableBox> +) diff --git a/core/box/src/components/List.tsx b/core/box/src/components/List.tsx new file mode 100644 index 000000000..ae35aa95f --- /dev/null +++ b/core/box/src/components/List.tsx @@ -0,0 +1,19 @@ +import { Box, Text } from 'ink' +import React from 'react' +import { SelectableBox } from './SelectableBox' + +export interface ListProps { + items: string[] + selected: boolean + cursor: number +} + +export const List = (props: ListProps): JSX.Element => ( + <SelectableBox selected={props.selected} flexDirection="column"> + {props.items.map((item, index) => ( + <Box key={item}> + <Text bold={props.cursor === index}>{item}</Text> + </Box> + ))} + </SelectableBox> +) diff --git a/core/box/src/components/SelectableBox.tsx b/core/box/src/components/SelectableBox.tsx new file mode 100644 index 000000000..d76146cb0 --- /dev/null +++ b/core/box/src/components/SelectableBox.tsx @@ -0,0 +1,15 @@ +import { Box, BoxProps } from 'ink' +import React from 'react' + +export interface SelectableBoxProps extends BoxProps { + selected: boolean +} + +export const SelectableBox = (props: React.PropsWithChildren<SelectableBoxProps>): JSX.Element => { + const { selected, children, ...rest } = props + return ( + <Box borderStyle={selected ? 'single' : undefined} paddingTop={selected ? 0 : 1} paddingX={1} {...rest}> + {children} + </Box> + ) +} diff --git a/core/box/src/components/TabbedView.tsx b/core/box/src/components/TabbedView.tsx new file mode 100644 index 000000000..a79ba22ac --- /dev/null +++ b/core/box/src/components/TabbedView.tsx @@ -0,0 +1,111 @@ +import type { ValidConfig } from 'dotcom-tool-kit/lib/config' +import { Box, Text, useInput } from 'ink' +import React, { useState } from 'react' +import { HooksPage } from './pages/Hooks' +import { PluginsPage } from './pages/Plugins' +import { TabName, TabPages } from './pages/shared' +import { TasksPage } from './pages/Tasks' + +// Our styling uses ansi-colors, whereas ink uses chalk, and there's no nice +// way to convert between the two, so lets just copy the appropriate colours +// as strings. +const tabColours = { plugins: 'cyan', hooks: 'magenta', tasks: 'blueBright' } + +interface TabbedViewProps { + config: ValidConfig +} + +export const TabbedView = (props: TabbedViewProps): JSX.Element => { + const [activeTab, setActiveTab] = useState(0) + const [pluginsStart, setPluginsStart] = useState<string | undefined>() + const [hooksStart, setHooksStart] = useState<string | undefined>() + const [tasksStart, setTasksStart] = useState<string | undefined>() + const pluginsWithHook: Record<string, string[]> = {} + const pluginsWithTask: Record<string, string[]> = {} + for (const [pluginId, plugin] of Object.entries(props.config.plugins)) { + for (const hookId of Object.keys(plugin.module?.hooks ?? {})) { + pluginsWithHook[hookId] ??= [] + pluginsWithHook[hookId].push(pluginId) + } + for (const taskId of Object.keys(plugin.module?.tasks ?? {})) { + pluginsWithTask[taskId] ??= [] + pluginsWithTask[taskId].push(pluginId) + } + } + const tasksWithHook = Object.entries(props.config.hookTasks).map( + ([hookId, hookTask]) => [hookId, hookTask.tasks] as const + ) + const hooksWithTask: Record<string, string[]> = {} + for (const [hookId, tasks] of tasksWithHook) { + for (const task of tasks) { + hooksWithTask[task] ??= [] + hooksWithTask[task].push(hookId) + } + } + + const handleTabChange = (newTab: TabName, itemId?: string) => { + setActiveTab(TabPages.indexOf(newTab)) + if (itemId) { + switch (newTab) { + case 'plugins': + setPluginsStart(itemId) + break + case 'hooks': + setHooksStart(itemId) + break + case 'tasks': + setTasksStart(itemId) + break + } + } + } + useInput((_, key) => { + if (key.tab) { + if (key.shift) { + const prevTab = activeTab - 1 + setActiveTab(prevTab < 0 ? TabPages.length - 1 : prevTab) + } else { + setActiveTab((activeTab + 1) % TabPages.length) + } + } + }) + return ( + <> + <Box> + {TabPages.map((page, index) => ( + <React.Fragment key={page}> + {index !== 0 && <Text> | </Text>} + <Text backgroundColor={index === activeTab ? tabColours[page] : undefined}>{page}</Text> + </React.Fragment> + ))} + </Box> + <Box> + {TabPages[activeTab] === 'plugins' && ( + <PluginsPage + plugins={Object.entries(props.config.plugins)} + startingItem={pluginsStart} + onTabChange={handleTabChange} + /> + )} + {TabPages[activeTab] === 'hooks' && ( + <HooksPage + hooks={Object.entries(props.config.hooks)} + taskMap={Object.fromEntries(tasksWithHook)} + pluginMap={pluginsWithHook} + startingItem={hooksStart} + onTabChange={handleTabChange} + /> + )} + {TabPages[activeTab] === 'tasks' && ( + <TasksPage + tasks={Object.entries(props.config.tasks)} + pluginMap={pluginsWithTask} + startingItem={tasksStart} + hookMap={hooksWithTask} + onTabChange={handleTabChange} + /> + )} + </Box> + </> + ) +} diff --git a/core/box/src/components/pages/Hooks.tsx b/core/box/src/components/pages/Hooks.tsx new file mode 100644 index 000000000..ba5c2d9ab --- /dev/null +++ b/core/box/src/components/pages/Hooks.tsx @@ -0,0 +1,109 @@ +import { styles } from '@dotcom-tool-kit/logger' +import type { Hook, HookClass } from '@dotcom-tool-kit/types' +import { Box, Text } from 'ink' +import React, { useCallback } from 'react' +import { DetailsBox } from '../DetailsBox' +import { List } from '../List' +import { TabPageProps, ToolEssentials, useNavigation } from './shared' + +interface HookDetailsProps extends ToolEssentials { + hook: Hook<unknown> + taskIds: string[] + pluginIds: string[] +} + +const HookDetails = (props: HookDetailsProps) => { + return ( + <DetailsBox selected={props.selected}> + <Text>{(props.hook.constructor as HookClass).description}</Text> + {props.hook.plugin && ( + <Text> + Defined in the{' '} + <Text bold={props.selected && props.cursor === 0}>{styles.plugin(props.hook.plugin.id)}</Text>{' '} + plugin + </Text> + )} + {props.taskIds.length > 0 && ( + <> + <Text>Calls the following tasks:</Text> + {props.taskIds.map((taskId, index) => ( + <Text key={taskId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.plugin(taskId)}</Text> + </Text> + ))} + </> + )} + {props.pluginIds.length > 0 && ( + <> + <Text>Appears in the following plugins:</Text> + {props.pluginIds.map((pluginId, index) => ( + <Text key={pluginId}> + -{' '} + <Text bold={props.selected && props.cursor === index + 1 + props.taskIds.length}> + {styles.plugin(pluginId)} + </Text> + </Text> + ))} + </> + )} + </DetailsBox> + ) +} + +export interface HooksPageProps extends TabPageProps { + hooks: [string, Hook<unknown>][] + taskMap: Record<string, string[]> + pluginMap: Record<string, string[]> +} + +export const HooksPage = (props: HooksPageProps): JSX.Element => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation({ + listLength: props.hooks.length, + getDetailsLength(cursor) { + const [hookId, hook] = props.hooks[cursor] + return ( + (hook.plugin ? 1 : 0) + (props.taskMap[hookId]?.length ?? 0) + (props.pluginMap[hookId]?.length ?? 0) + ) + }, + getSelectedItem(listCursor, detailsCursor) { + const [hookId, hook] = props.hooks[listCursor] + if (detailsCursor === 0 && hook.plugin) { + return ['plugins', hook.plugin.id] + } else if (detailsCursor <= props.taskMap[hookId]?.length ?? 0) { + return ['tasks', props.taskMap[hookId][detailsCursor - (hook.plugin ? 1 : 0)]] + } else { + return [ + 'plugins', + props.pluginMap[hookId][ + detailsCursor - (props.taskMap[hookId]?.length ?? 0) - (hook.plugin ? 1 : 0) + ] + ] + } + }, + findItem: useCallback( + (itemId) => { + return props.hooks.findIndex(([hookId]) => hookId === itemId) + }, + [props.hooks] + ), + startingItem: props.startingItem, + changeTab: props.onTabChange + }) + const [hookId, selectedHook] = props.hooks[listCursor] + return ( + <Box paddingLeft={detailsSelected ? 1 : 0}> + <List + items={props.hooks.map(([id]) => styles.hook(id))} + selected={!detailsSelected} + cursor={listCursor} + /> + <HookDetails + hook={selectedHook} + taskIds={props.taskMap[hookId] ?? []} + pluginIds={props.pluginMap[hookId] ?? []} + selected={detailsSelected} + cursor={detailsCursor} + /> + </Box> + ) +} diff --git a/core/box/src/components/pages/Plugins.tsx b/core/box/src/components/pages/Plugins.tsx new file mode 100644 index 000000000..8afc78310 --- /dev/null +++ b/core/box/src/components/pages/Plugins.tsx @@ -0,0 +1,99 @@ +import { styles } from '@dotcom-tool-kit/logger' +import type { Plugin } from '@dotcom-tool-kit/types' +import { Box, Text } from 'ink' +import React, { useCallback } from 'react' +import { DetailsBox } from '../DetailsBox' +import { List } from '../List' +import { TabPageProps, ToolEssentials, useNavigation } from './shared' + +interface PluginDetailsProps extends ToolEssentials { + parent?: Plugin + hookIds: string[] + taskIds: string[] +} + +const PluginDetails = (props: PluginDetailsProps) => ( + <DetailsBox selected={props.selected}> + <Text> + Included by{' '} + <Text bold={props.selected && props.cursor === 0}> + {styles.plugin(props.parent?.id ?? 'no parent')} + </Text> + </Text> + {props.hookIds.length > 0 && ( + <> + <Text>Defines the following hooks:</Text> + {props.hookIds.map((hookId, index) => ( + <Text key={hookId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hookId)}</Text> + </Text> + ))} + </> + )} + {props.taskIds.length > 0 && ( + <> + <Text>Defines the following tasks:</Text> + {props.taskIds.map((taskId, index) => ( + <Text key={taskId}> + -{' '} + <Text bold={props.selected && props.cursor === index + 1 + props.hookIds.length}> + {styles.task(taskId)} + </Text> + </Text> + ))} + </> + )} + </DetailsBox> +) + +interface PluginsPageProps extends TabPageProps { + plugins: [string, Plugin][] +} + +export const PluginsPage = (props: PluginsPageProps): JSX.Element => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation({ + listLength: props.plugins.length, + getDetailsLength(cursor) { + const [, plugin] = props.plugins[cursor] + return 1 + Object.keys(plugin.module?.hooks ?? {}).length + (plugin.module?.tasks?.length ?? 0) + }, + getSelectedItem(listCursor, detailsCursor) { + const [, plugin] = props.plugins[listCursor] + const hookLength = Object.keys(plugin.module?.hooks ?? {}).length + if (detailsCursor === 0) { + return ['plugins', plugin.parent?.id ?? plugin.id] + } else if (detailsCursor <= hookLength) { + return ['hooks', Object.keys(plugin.module?.hooks ?? {})[detailsCursor - 1]] + } else { + return ['tasks', (plugin.module?.tasks ?? [])[detailsCursor - 1 - hookLength].id] + } + }, + findItem: useCallback( + (itemId) => { + return props.plugins.findIndex(([pluginId]) => pluginId === itemId) + }, + [props.plugins] + ), + startingItem: props.startingItem, + changeTab: props.onTabChange + }) + const [, selectedPlugin] = props.plugins[listCursor] + return ( + <Box paddingLeft={detailsSelected ? 1 : 0}> + <List + items={props.plugins.map(([id]) => styles.plugin(id))} + selected={!detailsSelected} + cursor={listCursor} + /> + <PluginDetails + parent={selectedPlugin.parent} + hookIds={Object.keys(selectedPlugin.module?.hooks ?? {})} + taskIds={ + selectedPlugin.module?.tasks?.map((task) => task.id).filter((id): id is string => !!id) ?? [] + } + selected={detailsSelected} + cursor={detailsCursor} + /> + </Box> + ) +} diff --git a/core/box/src/components/pages/Tasks.tsx b/core/box/src/components/pages/Tasks.tsx new file mode 100644 index 000000000..616e1782e --- /dev/null +++ b/core/box/src/components/pages/Tasks.tsx @@ -0,0 +1,106 @@ +import { styles } from '@dotcom-tool-kit/logger' +import type { TaskClass } from '@dotcom-tool-kit/types' +import { Box, Text } from 'ink' +import React, { useCallback } from 'react' +import { DetailsBox } from '../DetailsBox' +import { List } from '../List' +import { TabPageProps, ToolEssentials, useNavigation } from './shared' + +interface TaskDetailsProps extends ToolEssentials { + task: TaskClass + pluginIds: string[] + hookIds: string[] +} + +const TaskDetails = (props: TaskDetailsProps) => { + return ( + <DetailsBox selected={props.selected}> + <Text>{props.task.description}</Text> + {props.task.plugin && ( + <Text> + Defined in the{' '} + <Text bold={props.selected && props.cursor === 0}>{styles.plugin(props.task.plugin.id)}</Text>{' '} + plugin + </Text> + )} + {props.pluginIds.length > 0 && ( + <> + <Text>Appears in the following plugins:</Text> + {props.pluginIds.map((pluginId, index) => ( + <Text key={pluginId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.plugin(pluginId)}</Text> + </Text> + ))} + </> + )} + {props.hookIds.length > 0 && ( + <> + <Text>Is called by the following hooks:</Text> + {props.hookIds.map((hookId, index) => ( + <Text key={hookId}> + - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hookId)}</Text> + </Text> + ))} + </> + )} + </DetailsBox> + ) +} + +export interface TasksPageProps extends TabPageProps { + tasks: [string, TaskClass][] + pluginMap: Record<string, string[]> + hookMap: Record<string, string[]> +} + +export const TasksPage = (props: TasksPageProps): JSX.Element => { + const { listCursor, detailsCursor, detailsSelected } = useNavigation({ + listLength: props.tasks.length, + getDetailsLength(cursor) { + const [taskId, task] = props.tasks[cursor] + return ( + (task.plugin ? 1 : 0) + (props.pluginMap[taskId]?.length ?? 0) + (props.hookMap[taskId]?.length ?? 0) + ) + }, + getSelectedItem(listCursor, detailsCursor) { + const [taskId, task] = props.tasks[listCursor] + if (detailsCursor === 0 && task.plugin) { + return ['plugins', task.plugin.id] + } else if (detailsCursor <= props.hookMap[taskId]?.length ?? 0) { + return ['hooks', props.hookMap[taskId][detailsCursor - (task.plugin ? 1 : 0)]] + } else { + return [ + 'plugins', + props.pluginMap[taskId][ + detailsCursor - (props.hookMap[taskId]?.length ?? 0) - (task.plugin ? 1 : 0) + ] + ] + } + }, + findItem: useCallback( + (itemId) => { + return props.tasks.findIndex(([taskId]) => taskId === itemId) + }, + [props.tasks] + ), + startingItem: props.startingItem, + changeTab: props.onTabChange + }) + const [taskId, selectedTask] = props.tasks[listCursor] + return ( + <Box paddingLeft={detailsSelected ? 1 : 0}> + <List + items={props.tasks.map(([id]) => styles.task(id))} + selected={!detailsSelected} + cursor={listCursor} + /> + <TaskDetails + task={selectedTask} + pluginIds={props.pluginMap[taskId] ?? []} + hookIds={props.hookMap[taskId] ?? []} + selected={detailsSelected} + cursor={detailsCursor} + /> + </Box> + ) +} diff --git a/core/box/src/components/pages/shared.tsx b/core/box/src/components/pages/shared.tsx new file mode 100644 index 000000000..17ed42485 --- /dev/null +++ b/core/box/src/components/pages/shared.tsx @@ -0,0 +1,88 @@ +import { useApp, useInput } from 'ink' +import { useEffect, useState } from 'react' + +export interface ToolEssentials { + selected: boolean + cursor: number +} + +export const TabPages = ['plugins', 'hooks', 'tasks'] as const +export type TabName = typeof TabPages[number] + +export interface TabPageProps { + startingItem?: string + onTabChange: (newTab: TabName, itemId: string | undefined) => void +} + +interface NavigationArgs { + listLength: number + getDetailsLength: (listCursor: number) => number + getSelectedItem: (listCursor: number, detailsCursor: number) => [TabName, string | undefined] + findItem: (itemId: string) => number + startingItem?: string + changeTab: (newTab: TabName, itemId: string | undefined) => void +} + +interface NavigationState { + listCursor: number + detailsCursor: number + detailsSelected: boolean +} + +export const useNavigation = ({ + listLength, + getDetailsLength, + getSelectedItem, + findItem, + startingItem, + changeTab +}: NavigationArgs): NavigationState => { + const { exit } = useApp() + const [listCursor, setListCursor] = useState(0) + const [detailsCursor, setDetailsCursor] = useState(0) + const [detailsSelected, setDetailsSelected] = useState(false) + + const maxListCursor = listLength - 1 + const maxDetailsCursor = getDetailsLength(listCursor) - 1 + + useEffect(() => { + if (startingItem) { + setListCursor(findItem(startingItem)) + } + }, [startingItem, findItem]) + + useInput((input, key) => { + if (key.downArrow || input === 'j') { + if (detailsSelected) { + setDetailsCursor(detailsCursor !== maxDetailsCursor ? detailsCursor + 1 : 0) + } else { + setListCursor(listCursor !== maxListCursor ? listCursor + 1 : 0) + setDetailsCursor(0) + } + } + if (key.upArrow || input === 'k') { + if (detailsSelected) { + setDetailsCursor(detailsCursor !== 0 ? detailsCursor - 1 : maxDetailsCursor) + } else { + setListCursor(listCursor !== 0 ? listCursor - 1 : maxListCursor) + setDetailsCursor(0) + } + } + if (key.return || key.rightArrow || input === 'l') { + if (detailsSelected) { + const [newTab, itemId] = getSelectedItem(listCursor, detailsCursor) + changeTab(newTab, itemId) + } else { + setDetailsSelected(true) + } + } + if (detailsSelected && (key.escape || key.leftArrow || input === 'h')) { + setDetailsSelected(false) + } + if (input === 'q') { + exit() + } + }) + + return { listCursor, detailsCursor, detailsSelected } +} diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index cc4eaef1d..f8c6b8754 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -1,529 +1,11 @@ -import React, { useCallback, useEffect, useState } from 'react' -import { render, Box, BoxProps, Text, useApp, useInput } from 'ink' +import { loadConfig } from 'dotcom-tool-kit/lib/config' +import { render } from 'ink' +import React from 'react' import winston from 'winston' -import { loadConfig, ValidConfig } from 'dotcom-tool-kit/lib/config' -import { styles } from '@dotcom-tool-kit/logger' -import type { Hook, HookClass, Plugin, TaskClass } from '@dotcom-tool-kit/types' - -const logger = winston.createLogger({ silent: true }) - -interface SelectableBoxProps extends BoxProps { - selected: boolean -} -const SelectableBox = (props: React.PropsWithChildren<SelectableBoxProps>) => { - const { selected, children, ...rest } = props - return ( - <Box borderStyle={selected ? 'single' : undefined} paddingTop={selected ? 0 : 1} paddingX={1} {...rest}> - {children} - </Box> - ) -} - -interface ListProps { - items: string[] - selected: boolean - cursor: number -} - -const List = (props: ListProps) => ( - <SelectableBox selected={props.selected} flexDirection="column"> - {props.items.map((item, index) => ( - <Box key={item}> - <Text bold={props.cursor === index}>{item}</Text> - </Box> - ))} - </SelectableBox> -) - -interface ToolEssentials { - selected: boolean - cursor: number -} - -const DetailsBox = (props: React.PropsWithChildren<SelectableBoxProps>) => ( - <SelectableBox width={72} flexDirection="column" {...props}> - {props.children} - </SelectableBox> -) - -interface PluginDetailsProps extends ToolEssentials { - parent?: Plugin - hookIds: string[] - taskIds: string[] -} - -const PluginDetails = (props: PluginDetailsProps) => ( - <DetailsBox selected={props.selected}> - <Text> - Included by{' '} - <Text bold={props.selected && props.cursor === 0}> - {styles.plugin(props.parent?.id ?? 'no parent')} - </Text> - </Text> - {props.hookIds.length > 0 && ( - <> - <Text>Defines the following hooks:</Text> - {props.hookIds.map((hookId, index) => ( - <Text key={hookId}> - - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hookId)}</Text> - </Text> - ))} - </> - )} - {props.taskIds.length > 0 && ( - <> - <Text>Defines the following tasks:</Text> - {props.taskIds.map((taskId, index) => ( - <Text key={taskId}> - -{' '} - <Text bold={props.selected && props.cursor === index + 1 + props.hookIds.length}> - {styles.task(taskId)} - </Text> - </Text> - ))} - </> - )} - </DetailsBox> -) - -interface HookDetailsProps extends ToolEssentials { - hook: Hook - taskIds: string[] - pluginIds: string[] -} - -const HookDetails = (props: HookDetailsProps) => { - return ( - <DetailsBox selected={props.selected}> - <Text>{(props.hook.constructor as HookClass).description}</Text> - {props.hook.plugin && ( - <Text> - Defined in the{' '} - <Text bold={props.selected && props.cursor === 0}>{styles.plugin(props.hook.plugin.id)}</Text>{' '} - plugin - </Text> - )} - {props.taskIds.length > 0 && ( - <> - <Text>Calls the following tasks:</Text> - {props.taskIds.map((taskId, index) => ( - <Text key={taskId}> - - <Text bold={props.selected && props.cursor === index + 1}>{styles.plugin(taskId)}</Text> - </Text> - ))} - </> - )} - {props.pluginIds.length > 0 && ( - <> - <Text>Appears in the following plugins:</Text> - {props.pluginIds.map((pluginId, index) => ( - <Text key={pluginId}> - -{' '} - <Text bold={props.selected && props.cursor === index + 1 + props.taskIds.length}> - {styles.plugin(pluginId)} - </Text> - </Text> - ))} - </> - )} - </DetailsBox> - ) -} - -interface TaskDetailsProps extends ToolEssentials { - task: TaskClass - pluginIds: string[] - hookIds: string[] -} - -const TaskDetails = (props: TaskDetailsProps) => { - return ( - <DetailsBox selected={props.selected}> - <Text>{props.task.description}</Text> - {props.task.plugin && ( - <Text> - Defined in the{' '} - <Text bold={props.selected && props.cursor === 0}>{styles.plugin(props.task.plugin.id)}</Text>{' '} - plugin - </Text> - )} - {props.pluginIds.length > 0 && ( - <> - <Text>Appears in the following plugins:</Text> - {props.pluginIds.map((pluginId, index) => ( - <Text key={pluginId}> - - <Text bold={props.selected && props.cursor === index + 1}>{styles.plugin(pluginId)}</Text> - </Text> - ))} - </> - )} - {props.hookIds.length > 0 && ( - <> - <Text>Is called by the following hooks:</Text> - {props.hookIds.map((hookId, index) => ( - <Text key={hookId}> - - <Text bold={props.selected && props.cursor === index + 1}>{styles.hook(hookId)}</Text> - </Text> - ))} - </> - )} - </DetailsBox> - ) -} - -interface TabPageProps { - startingItem?: string - onTabChange: (newTab: TabName, itemId: string | undefined) => void -} - -interface NavigationArgs { - listLength: number - getDetailsLength: (listCursor: number) => number - getSelectedItem: (listCursor: number, detailsCursor: number) => [TabName, string | undefined] - findItem: (itemId: string) => number - startingItem?: string - changeTab: (newTab: TabName, itemId: string | undefined) => void -} - -interface NavigationState { - listCursor: number - detailsCursor: number - detailsSelected: boolean -} - -const useNavigation = ({ - listLength, - getDetailsLength, - getSelectedItem, - findItem, - startingItem, - changeTab -}: NavigationArgs): NavigationState => { - const { exit } = useApp() - const [listCursor, setListCursor] = useState(0) - const [detailsCursor, setDetailsCursor] = useState(0) - const [detailsSelected, setDetailsSelected] = useState(false) - - const maxListCursor = listLength - 1 - const maxDetailsCursor = getDetailsLength(listCursor) - 1 - - useEffect(() => { - if (startingItem) { - setListCursor(findItem(startingItem)) - } - }, [startingItem, findItem]) - - useInput((input, key) => { - if (key.downArrow || input === 'j') { - if (detailsSelected) { - setDetailsCursor(detailsCursor !== maxDetailsCursor ? detailsCursor + 1 : 0) - } else { - setListCursor(listCursor !== maxListCursor ? listCursor + 1 : 0) - setDetailsCursor(0) - } - } - if (key.upArrow || input === 'k') { - if (detailsSelected) { - setDetailsCursor(detailsCursor !== 0 ? detailsCursor - 1 : maxDetailsCursor) - } else { - setListCursor(listCursor !== 0 ? listCursor - 1 : maxListCursor) - setDetailsCursor(0) - } - } - if (key.return || key.rightArrow || input === 'l') { - if (detailsSelected) { - const [newTab, itemId] = getSelectedItem(listCursor, detailsCursor) - changeTab(newTab, itemId) - } else { - setDetailsSelected(true) - } - } - if (detailsSelected && (key.escape || key.leftArrow || input === 'h')) { - setDetailsSelected(false) - } - if (input === 'q') { - exit() - } - }) - - return { listCursor, detailsCursor, detailsSelected } -} - -interface PluginsPageProps extends TabPageProps { - plugins: [string, Plugin][] -} - -const PluginsPage = (props: PluginsPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation({ - listLength: props.plugins.length, - getDetailsLength(cursor) { - const [, plugin] = props.plugins[cursor] - return 1 + Object.keys(plugin.module?.hooks ?? {}).length + (plugin.module?.tasks?.length ?? 0) - }, - getSelectedItem(listCursor, detailsCursor) { - const [, plugin] = props.plugins[listCursor] - const hookLength = Object.keys(plugin.module?.hooks ?? {}).length - if (detailsCursor === 0) { - return ['plugins', plugin.parent?.id ?? plugin.id] - } else if (detailsCursor <= hookLength) { - return ['hooks', Object.keys(plugin.module?.hooks ?? {})[detailsCursor - 1]] - } else { - return ['tasks', (plugin.module?.tasks ?? [])[detailsCursor - 1 - hookLength].id] - } - }, - findItem: useCallback( - (itemId) => { - return props.plugins.findIndex(([pluginId]) => pluginId === itemId) - }, - [props.plugins] - ), - startingItem: props.startingItem, - changeTab: props.onTabChange - }) - const [, selectedPlugin] = props.plugins[listCursor] - return ( - <Box paddingLeft={detailsSelected ? 1 : 0}> - <List - items={props.plugins.map(([id]) => styles.plugin(id))} - selected={!detailsSelected} - cursor={listCursor} - /> - <PluginDetails - parent={selectedPlugin.parent} - hookIds={Object.keys(selectedPlugin.module?.hooks ?? {})} - taskIds={ - selectedPlugin.module?.tasks?.map((task) => task.id).filter((id): id is string => !!id) ?? [] - } - selected={detailsSelected} - cursor={detailsCursor} - /> - </Box> - ) -} - -interface HooksPageProps extends TabPageProps { - hooks: [string, Hook][] - taskMap: Record<string, string[]> - pluginMap: Record<string, string[]> -} - -const HooksPage = (props: HooksPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation({ - listLength: props.hooks.length, - getDetailsLength(cursor) { - const [hookId, hook] = props.hooks[cursor] - return ( - (hook.plugin ? 1 : 0) + (props.taskMap[hookId]?.length ?? 0) + (props.pluginMap[hookId]?.length ?? 0) - ) - }, - getSelectedItem(listCursor, detailsCursor) { - const [hookId, hook] = props.hooks[listCursor] - if (detailsCursor === 0 && hook.plugin) { - return ['plugins', hook.plugin.id] - } else if (detailsCursor <= props.taskMap[hookId]?.length ?? 0) { - return ['tasks', props.taskMap[hookId][detailsCursor - (hook.plugin ? 1 : 0)]] - } else { - return [ - 'plugins', - props.pluginMap[hookId][ - detailsCursor - (props.taskMap[hookId]?.length ?? 0) - (hook.plugin ? 1 : 0) - ] - ] - } - }, - findItem: useCallback( - (itemId) => { - return props.hooks.findIndex(([hookId]) => hookId === itemId) - }, - [props.hooks] - ), - startingItem: props.startingItem, - changeTab: props.onTabChange - }) - const [hookId, selectedHook] = props.hooks[listCursor] - return ( - <Box paddingLeft={detailsSelected ? 1 : 0}> - <List - items={props.hooks.map(([id]) => styles.hook(id))} - selected={!detailsSelected} - cursor={listCursor} - /> - <HookDetails - hook={selectedHook} - taskIds={props.taskMap[hookId] ?? []} - pluginIds={props.pluginMap[hookId] ?? []} - selected={detailsSelected} - cursor={detailsCursor} - /> - </Box> - ) -} - -interface TasksPageProps extends TabPageProps { - tasks: [string, TaskClass][] - pluginMap: Record<string, string[]> - hookMap: Record<string, string[]> -} - -const TasksPage = (props: TasksPageProps) => { - const { listCursor, detailsCursor, detailsSelected } = useNavigation({ - listLength: props.tasks.length, - getDetailsLength(cursor) { - const [taskId, task] = props.tasks[cursor] - return ( - (task.plugin ? 1 : 0) + (props.pluginMap[taskId]?.length ?? 0) + (props.hookMap[taskId]?.length ?? 0) - ) - }, - getSelectedItem(listCursor, detailsCursor) { - const [taskId, task] = props.tasks[listCursor] - if (detailsCursor === 0 && task.plugin) { - return ['plugins', task.plugin.id] - } else if (detailsCursor <= props.hookMap[taskId]?.length ?? 0) { - return ['hooks', props.hookMap[taskId][detailsCursor - (task.plugin ? 1 : 0)]] - } else { - return [ - 'plugins', - props.pluginMap[taskId][ - detailsCursor - (props.hookMap[taskId]?.length ?? 0) - (task.plugin ? 1 : 0) - ] - ] - } - }, - findItem: useCallback( - (itemId) => { - return props.tasks.findIndex(([taskId]) => taskId === itemId) - }, - [props.tasks] - ), - startingItem: props.startingItem, - changeTab: props.onTabChange - }) - const [taskId, selectedTask] = props.tasks[listCursor] - return ( - <Box paddingLeft={detailsSelected ? 1 : 0}> - <List - items={props.tasks.map(([id]) => styles.task(id))} - selected={!detailsSelected} - cursor={listCursor} - /> - <TaskDetails - task={selectedTask} - pluginIds={props.pluginMap[taskId] ?? []} - hookIds={props.hookMap[taskId] ?? []} - selected={detailsSelected} - cursor={detailsCursor} - /> - </Box> - ) -} - -const TabPages = ['plugins', 'hooks', 'tasks'] as const -type TabName = typeof TabPages[number] -// Our styling uses ansi-colors, whereas ink uses chalk, and there's no nice -// way to convert between the two, so lets just copy the appropriate colours -// as strings. -const tabColours = { plugins: 'cyan', hooks: 'magenta', tasks: 'blueBright' } - -interface TabbedViewProps { - config: ValidConfig -} - -const TabbedView = (props: TabbedViewProps) => { - const [activeTab, setActiveTab] = useState(0) - const [pluginsStart, setPluginsStart] = useState<string | undefined>() - const [hooksStart, setHooksStart] = useState<string | undefined>() - const [tasksStart, setTasksStart] = useState<string | undefined>() - const pluginsWithHook: Record<string, string[]> = {} - const pluginsWithTask: Record<string, string[]> = {} - for (const [pluginId, plugin] of Object.entries(props.config.plugins)) { - for (const hookId of Object.keys(plugin.module?.hooks ?? {})) { - pluginsWithHook[hookId] ??= [] - pluginsWithHook[hookId].push(pluginId) - } - for (const taskId of Object.keys(plugin.module?.tasks ?? {})) { - pluginsWithTask[taskId] ??= [] - pluginsWithTask[taskId].push(pluginId) - } - } - const tasksWithHook = Object.entries(props.config.hookTasks).map( - ([hookId, hookTask]) => [hookId, hookTask.tasks] as const - ) - const hooksWithTask: Record<string, string[]> = {} - for (const [hookId, tasks] of tasksWithHook) { - for (const task of tasks) { - hooksWithTask[task] ??= [] - hooksWithTask[task].push(hookId) - } - } - - const handleTabChange = (newTab: TabName, itemId?: string) => { - setActiveTab(TabPages.indexOf(newTab)) - if (itemId) { - switch (newTab) { - case 'plugins': - setPluginsStart(itemId) - break - case 'hooks': - setHooksStart(itemId) - break - case 'tasks': - setTasksStart(itemId) - break - } - } - } - useInput((_, key) => { - if (key.tab) { - if (key.shift) { - const prevTab = activeTab - 1 - setActiveTab(prevTab < 0 ? TabPages.length - 1 : prevTab) - } else { - setActiveTab((activeTab + 1) % TabPages.length) - } - } - }) - return ( - <> - <Box> - {TabPages.map((page, index) => ( - <React.Fragment key={page}> - {index !== 0 && <Text> | </Text>} - <Text backgroundColor={index === activeTab ? tabColours[page] : undefined}>{page}</Text> - </React.Fragment> - ))} - </Box> - <Box> - {TabPages[activeTab] === 'plugins' && ( - <PluginsPage - plugins={Object.entries(props.config.plugins)} - startingItem={pluginsStart} - onTabChange={handleTabChange} - /> - )} - {TabPages[activeTab] === 'hooks' && ( - <HooksPage - hooks={Object.entries(props.config.hooks)} - taskMap={Object.fromEntries(tasksWithHook)} - pluginMap={pluginsWithHook} - startingItem={hooksStart} - onTabChange={handleTabChange} - /> - )} - {TabPages[activeTab] === 'tasks' && ( - <TasksPage - tasks={Object.entries(props.config.tasks)} - pluginMap={pluginsWithTask} - startingItem={tasksStart} - hookMap={hooksWithTask} - onTabChange={handleTabChange} - /> - )} - </Box> - </> - ) -} +import { TabbedView } from './components/TabbedView' async function main() { + const logger = winston.createLogger({ silent: true }) const config = await loadConfig(logger) render( From 7ed5709a74f304d06f9d9282100e37fb9d5872e6 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 9 Dec 2022 17:56:56 +0000 Subject: [PATCH 13/16] fix(cli): return validated config if we're validating The validated config object has a slightly different structure than the config object but we don't seem to have been touching any of the fields that are changed in our codebase until the TUI was added so the bug slipped under the radar. --- core/cli/src/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/cli/src/config.ts b/core/cli/src/config.ts index 327d414ef..55ebdf529 100644 --- a/core/cli/src/config.ts +++ b/core/cli/src/config.ts @@ -200,6 +200,7 @@ export async function loadConfig(logger: Logger, { validate = true } = {}): Prom if (validate) { validateConfig(validPluginConfig) + return validPluginConfig } return config From 81c804706849dfec0cdb97e64f435cc2390ef837 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 9 Dec 2022 19:16:54 +0000 Subject: [PATCH 14/16] feat(box): define binary to run the TUI --- core/box/bin/run | 5 +++++ core/box/package.json | 3 +++ core/box/src/index.tsx | 4 +--- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100755 core/box/bin/run diff --git a/core/box/bin/run b/core/box/bin/run new file mode 100755 index 000000000..671b7e56b --- /dev/null +++ b/core/box/bin/run @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +const { main } = require('../lib/index.js') + +main() diff --git a/core/box/package.json b/core/box/package.json index b291cce13..1bd2c5e6b 100644 --- a/core/box/package.json +++ b/core/box/package.json @@ -3,6 +3,9 @@ "version": "0.0.0-development", "description": "", "main": "lib", + "bin": { + "dotcom-tool-kit": "./bin/run" + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/core/box/src/index.tsx b/core/box/src/index.tsx index f8c6b8754..4e4aa760b 100644 --- a/core/box/src/index.tsx +++ b/core/box/src/index.tsx @@ -4,7 +4,7 @@ import React from 'react' import winston from 'winston' import { TabbedView } from './components/TabbedView' -async function main() { +export async function main(): Promise<void> { const logger = winston.createLogger({ silent: true }) const config = await loadConfig(logger) @@ -14,5 +14,3 @@ async function main() { </React.StrictMode> ) } - -main() From 9c22f334854119b684ff182aa2f479a7f4053ba8 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 9 Dec 2022 19:17:56 +0000 Subject: [PATCH 15/16] fix(box): use npm workspace rather than local paths --- core/box/package.json | 6 +++--- package-lock.json | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/core/box/package.json b/core/box/package.json index 1bd2c5e6b..c72cbdc92 100644 --- a/core/box/package.json +++ b/core/box/package.json @@ -13,8 +13,8 @@ "author": "FT.com Platforms Team <platforms-team.customer-products@ft.com>", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/logger": "file:../../lib/logger", - "dotcom-tool-kit": "file:../cli", + "@dotcom-tool-kit/logger": "^2.1.2", + "dotcom-tool-kit": "^2.4.0", "ink": "^3.2.0", "react": "^17.0.2", "winston": "^3.6.0" @@ -34,7 +34,7 @@ "extends": "../../package.json" }, "devDependencies": { - "@dotcom-tool-kit/types": "file:../../lib/types", + "@dotcom-tool-kit/types": "^2.7.0", "@types/react": "^17.0.52" } } diff --git a/package-lock.json b/package-lock.json index b4c513123..9ec951d40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,14 +50,17 @@ "version": "0.0.0-development", "license": "ISC", "dependencies": { - "@dotcom-tool-kit/logger": "file:../../lib/logger", - "dotcom-tool-kit": "file:../cli", + "@dotcom-tool-kit/logger": "^2.1.2", + "dotcom-tool-kit": "^2.4.0", "ink": "^3.2.0", "react": "^17.0.2", "winston": "^3.6.0" }, + "bin": { + "dotcom-tool-kit": "bin/run" + }, "devDependencies": { - "@dotcom-tool-kit/types": "file:../../lib/types", + "@dotcom-tool-kit/types": "^2.7.0", "@types/react": "^17.0.52" } }, @@ -22587,10 +22590,10 @@ "@dotcom-tool-kit/box": { "version": "file:core/box", "requires": { - "@dotcom-tool-kit/logger": "file:../../lib/logger", - "@dotcom-tool-kit/types": "file:../../lib/types", + "@dotcom-tool-kit/logger": "^2.1.2", + "@dotcom-tool-kit/types": "*", "@types/react": "^17.0.52", - "dotcom-tool-kit": "file:../cli", + "dotcom-tool-kit": "^2.4.0", "ink": "^3.2.0", "react": "^17.0.2", "winston": "^3.6.0" From 670a013aa3d1d1289b8e85a9796a8c8dfdd38602 Mon Sep 17 00:00:00 2001 From: Ivo Murrell <ivo.murrell@ft.com> Date: Fri, 9 Dec 2022 19:32:30 +0000 Subject: [PATCH 16/16] refactor(tool-box): rename box to tool-box Credit to Serena, I think this analogy works slightly better when spelled out. --- core/{box => tool-box}/bin/run | 0 core/{box => tool-box}/package.json | 2 +- .../src/components/DetailsBox.tsx | 0 .../{box => tool-box}/src/components/List.tsx | 0 .../src/components/SelectableBox.tsx | 0 .../src/components/TabbedView.tsx | 0 .../src/components/pages/Hooks.tsx | 0 .../src/components/pages/Plugins.tsx | 0 .../src/components/pages/Tasks.tsx | 0 .../src/components/pages/shared.tsx | 0 core/{box => tool-box}/src/index.tsx | 0 core/{box => tool-box}/tsconfig.json | 0 package-lock.json | 51 +++++++++++++------ release-please-config.json | 1 + tsconfig.json | 2 +- 15 files changed, 38 insertions(+), 18 deletions(-) rename core/{box => tool-box}/bin/run (100%) rename core/{box => tool-box}/package.json (96%) rename core/{box => tool-box}/src/components/DetailsBox.tsx (100%) rename core/{box => tool-box}/src/components/List.tsx (100%) rename core/{box => tool-box}/src/components/SelectableBox.tsx (100%) rename core/{box => tool-box}/src/components/TabbedView.tsx (100%) rename core/{box => tool-box}/src/components/pages/Hooks.tsx (100%) rename core/{box => tool-box}/src/components/pages/Plugins.tsx (100%) rename core/{box => tool-box}/src/components/pages/Tasks.tsx (100%) rename core/{box => tool-box}/src/components/pages/shared.tsx (100%) rename core/{box => tool-box}/src/index.tsx (100%) rename core/{box => tool-box}/tsconfig.json (100%) diff --git a/core/box/bin/run b/core/tool-box/bin/run similarity index 100% rename from core/box/bin/run rename to core/tool-box/bin/run diff --git a/core/box/package.json b/core/tool-box/package.json similarity index 96% rename from core/box/package.json rename to core/tool-box/package.json index c72cbdc92..126852291 100644 --- a/core/box/package.json +++ b/core/tool-box/package.json @@ -1,5 +1,5 @@ { - "name": "@dotcom-tool-kit/box", + "name": "@dotcom-tool-kit/tool-box", "version": "0.0.0-development", "description": "", "main": "lib", diff --git a/core/box/src/components/DetailsBox.tsx b/core/tool-box/src/components/DetailsBox.tsx similarity index 100% rename from core/box/src/components/DetailsBox.tsx rename to core/tool-box/src/components/DetailsBox.tsx diff --git a/core/box/src/components/List.tsx b/core/tool-box/src/components/List.tsx similarity index 100% rename from core/box/src/components/List.tsx rename to core/tool-box/src/components/List.tsx diff --git a/core/box/src/components/SelectableBox.tsx b/core/tool-box/src/components/SelectableBox.tsx similarity index 100% rename from core/box/src/components/SelectableBox.tsx rename to core/tool-box/src/components/SelectableBox.tsx diff --git a/core/box/src/components/TabbedView.tsx b/core/tool-box/src/components/TabbedView.tsx similarity index 100% rename from core/box/src/components/TabbedView.tsx rename to core/tool-box/src/components/TabbedView.tsx diff --git a/core/box/src/components/pages/Hooks.tsx b/core/tool-box/src/components/pages/Hooks.tsx similarity index 100% rename from core/box/src/components/pages/Hooks.tsx rename to core/tool-box/src/components/pages/Hooks.tsx diff --git a/core/box/src/components/pages/Plugins.tsx b/core/tool-box/src/components/pages/Plugins.tsx similarity index 100% rename from core/box/src/components/pages/Plugins.tsx rename to core/tool-box/src/components/pages/Plugins.tsx diff --git a/core/box/src/components/pages/Tasks.tsx b/core/tool-box/src/components/pages/Tasks.tsx similarity index 100% rename from core/box/src/components/pages/Tasks.tsx rename to core/tool-box/src/components/pages/Tasks.tsx diff --git a/core/box/src/components/pages/shared.tsx b/core/tool-box/src/components/pages/shared.tsx similarity index 100% rename from core/box/src/components/pages/shared.tsx rename to core/tool-box/src/components/pages/shared.tsx diff --git a/core/box/src/index.tsx b/core/tool-box/src/index.tsx similarity index 100% rename from core/box/src/index.tsx rename to core/tool-box/src/index.tsx diff --git a/core/box/tsconfig.json b/core/tool-box/tsconfig.json similarity index 100% rename from core/box/tsconfig.json rename to core/tool-box/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 9ec951d40..43e26605e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "core/box": { "name": "@dotcom-tool-kit/box", "version": "0.0.0-development", + "extraneous": true, "license": "ISC", "dependencies": { "@dotcom-tool-kit/logger": "^2.1.2", @@ -195,6 +196,24 @@ "nodemon": "^2.0.15" } }, + "core/tool-box": { + "version": "0.0.0-development", + "license": "ISC", + "dependencies": { + "@dotcom-tool-kit/logger": "^2.1.2", + "dotcom-tool-kit": "^2.4.0", + "ink": "^3.2.0", + "react": "^17.0.2", + "winston": "^3.6.0" + }, + "bin": { + "dotcom-tool-kit": "bin/run" + }, + "devDependencies": { + "@dotcom-tool-kit/types": "^2.7.0", + "@types/react": "^17.0.52" + } + }, "lib/error": { "name": "@dotcom-tool-kit/error", "version": "2.0.1", @@ -2249,10 +2268,6 @@ "resolved": "plugins/backend-app", "link": true }, - "node_modules/@dotcom-tool-kit/box": { - "resolved": "core/box", - "link": true - }, "node_modules/@dotcom-tool-kit/circleci": { "resolved": "plugins/circleci", "link": true @@ -2365,6 +2380,10 @@ "resolved": "lib/state", "link": true }, + "node_modules/@dotcom-tool-kit/tool-box": { + "resolved": "core/tool-box", + "link": true + }, "node_modules/@dotcom-tool-kit/types": { "resolved": "lib/types", "link": true @@ -22587,18 +22606,6 @@ "@dotcom-tool-kit/npm": "^2.0.11" } }, - "@dotcom-tool-kit/box": { - "version": "file:core/box", - "requires": { - "@dotcom-tool-kit/logger": "^2.1.2", - "@dotcom-tool-kit/types": "*", - "@types/react": "^17.0.52", - "dotcom-tool-kit": "^2.4.0", - "ink": "^3.2.0", - "react": "^17.0.2", - "winston": "^3.6.0" - } - }, "@dotcom-tool-kit/circleci": { "version": "file:plugins/circleci", "requires": { @@ -23358,6 +23365,18 @@ } } }, + "@dotcom-tool-kit/tool-box": { + "version": "file:core/tool-box", + "requires": { + "@dotcom-tool-kit/logger": "^2.1.2", + "@dotcom-tool-kit/types": "^2.7.0", + "@types/react": "^17.0.52", + "dotcom-tool-kit": "^2.4.0", + "ink": "^3.2.0", + "react": "^17.0.2", + "winston": "^3.6.0" + } + }, "@dotcom-tool-kit/types": { "version": "file:lib/types", "requires": { diff --git a/release-please-config.json b/release-please-config.json index a315d36f1..5d9c51db6 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -8,6 +8,7 @@ "packages": { "core/cli": {}, "core/create": {}, + "core/tool-box": {}, "lib/error": {}, "lib/logger": {}, "lib/options": {}, diff --git a/tsconfig.json b/tsconfig.json index f8951d52a..3f77658d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -107,7 +107,7 @@ "path": "plugins/cypress" }, { - "path": "core/box" + "path": "core/tool-box" } ] }