UNPKG

npm-check-extras

Version:

CLI app to check for outdated and unused dependencies, and run update/delete action over selected ones

349 lines (348 loc) 18 kB
import fs from 'node:fs'; import React, { useEffect } from 'react'; import { Box, Text, useInput } from 'ink'; import Spinner from 'ink-spinner'; import { useStore } from '@nanostores/react'; import * as R from 'ramda'; import figureSet from 'figures'; import { format } from 'date-fns'; import { SuccessText, ErrorText } from './components/TextItem.js'; import { getButtonBgColor, getButtonColor, getCommandFromSentence, getId, getPanelColor, isAvailableChar, removeUrl, } from './helpers.js'; import { $availableActions } from './store.js'; import { $actionStatus, $allItems, packageActionsManager as PAM, } from './store/packages.js'; import { $panelStepper } from './store/panel-stepper.js'; import { optionsManager } from './store/options.js'; import { $userInput, inputActions } from './store/user-input.js'; import { $status, statusesManager, setStatusText } from './store/status.js'; import { packagesActions } from './actions/packages.js'; import { $focusableItems, $activeFocusableItems, focusableItemsManager as FIM, } from './store/focusable-items.js'; import RU from './ramda-utils.js'; import OperationsHistory from './components/OperationsHistory.js'; import { $historyItems, historyActionsManager as HAM, } from './store/history-items.js'; import { $travelItems, travelItemsManager as TIM } from './store/travel-items.js'; import Travel from './travel.js'; import { optionsSelectors as OPS } from './selectors/options-selectors.js'; import { traveller } from './interactions/traveller.js'; import { travelStatusesManager } from './store/travel-status.js'; export default function App({ isShowPackages, isDevOnly, isProduction, isGlobal, isStoreHistory, isSkipUnused, isShowHistory, isRevertUpdates, }) { const availableActions = useStore($availableActions); const allItems = useStore($allItems); const historyItems = useStore($historyItems); const travelItems = useStore($travelItems); const submitButtonText = useStore(OPS.submitButtonText); useStore($focusableItems); const activeFocusableItems = useStore($activeFocusableItems); const panelStepper = useStore($panelStepper); const userInput = useStore($userInput); useStore($actionStatus); const status = useStore($status); useEffect(() => { if (isStoreHistory) optionsManager.selectByName('store-history'); if (isSkipUnused) optionsManager.selectWithChecks('skip-unused'); if (isGlobal) optionsManager.selectWithChecks('global'); else if (isProduction) optionsManager.selectWithChecks('production'); else if (isDevOnly) optionsManager.selectWithChecks('dev-only'); else if (isRevertUpdates) optionsManager.selectWithChecks('revert-updates'); if (isShowPackages) { /* eslint-disable no-inner-declarations */ async function fetchData() { await PAM.checkPackages(); packagesActions.activateFirstPackage(); FIM.activateByName('packages-panel'); } /* eslint-enable no-inner-declarations */ fetchData(); // eslint-disable-line @typescript-eslint/no-floating-promises } else if (isShowHistory) { HAM.fetchHistory(); } else if (isRevertUpdates) { statusesManager.setTravel(); TIM.fetchTravelItems(); FIM.activateNext(); travelStatusesManager.setWaiting(); } const unsubsribeUserInput = $userInput.subscribe(value => { packagesActions.selectPackagesByFilter(value.value); }); $focusableItems.subscribe(van => { fs.writeFileSync(`tmp/at_${format(new Date(), 'MM_dd__hh_mm_ss')}.json`, JSON.stringify(van, null, 2)); }); return () => { unsubsribeUserInput(); }; }, [ isShowPackages, isDevOnly, isProduction, isGlobal, isStoreHistory, isSkipUnused, isShowHistory, isRevertUpdates, ]); /* eslint-disable complexity */ useInput(async (input, key) => { if (key.return) { if (FIM.isPanelActive('options-panel')) { if (optionsManager.isSelectedByName('show-history')) { statusesManager.setInfo(); // FIM.unmarkInViewByName('packages-panel'); // FIM.markInViewByName('history-panel'); // FIM.activateByName('history-panel'); HAM.fetchHistory(); // FIM.activateByName('packages-panel'); } else if (optionsManager.isSelectedByName('revert-updates')) { statusesManager.setTravel(); TIM.fetchTravelItems(); // FIM.activateByName('packages-panel'); } else { // FIM.activateByName('packages-panel'); // FIM.unmarkInViewByName('history-panel'); // FIM.markInViewByName('packages-panel'); inputActions.clear(); travelStatusesManager.setWaiting(); PAM.setWaiting(); await PAM.checkPackages(); packagesActions.activateFirstPackage(); } FIM.activateNext(); } else if (FIM.isPanelActive('packages-panel')) { if (statusesManager.isTravel()) { const selectedMilestone = R.find(R.propEq(true, 'isSelected'), travelItems); // Services.updatePacksonEntries(selectedMilestone); if (!R.isNil(selectedMilestone)) { const updatables = R.reduce((acc, curr) => { const currKey = curr.kindOfDependencyKey; const packageWithSemver = R.objOf(curr.name, curr.semverValue); if (R.has(currKey, acc)) { acc = R.modify(currKey, (entry) => { return R.mergeDeepLeft(packageWithSemver, entry); }, acc); } else { acc = R.assoc(currKey, packageWithSemver, acc); } return acc; }, {}, selectedMilestone.items); // const result = await Services.updatePacksonEntries(updatables); await traveller.revertEntriesInPackageJson(updatables); } } else if (R.count(RU.isSelected, allItems) > 0) { await PAM.runUpdate(); if (!PAM.isFailed()) packagesActions.removeSelectedPackagesFromList(); } } } else if (key.backspace) { if (panelStepper.isCurrent(1)) { inputActions.deletePrevious(); } } else if (key.delete) { if (panelStepper.isCurrent(1)) { inputActions.deleteCurrent(); } } else if (key.tab) { if (!statusesManager.isWaiting()) { FIM.activateNext(); } } else if (key.leftArrow) { inputActions.moveLeft(); } else if (key.rightArrow) { inputActions.moveRight(); } else if (key.downArrow) { if (FIM.isPanelActive('options-panel')) { optionsManager.activateNextOption(); } else if (FIM.isPanelActive('packages-panel')) { if (statusesManager.isInfo()) { HAM.showNext(); } else if (statusesManager.isTravel()) { TIM.activateNextTravelItem(); } else { packagesActions.activateNextPackage(); } } } else if (key.upArrow) { if (FIM.isPanelActive('options-panel')) { optionsManager.activatePreviousOption(); } else if (FIM.isPanelActive('packages-panel')) { if (statusesManager.isInfo()) { HAM.showPrevious(); } else if (statusesManager.isTravel()) { TIM.activatePreviousTravelItem(); } else { packagesActions.activatePreviousPackage(); } } } else if (input === ' ') { if (FIM.isPanelActive('packages-panel')) { if (statusesManager.isTravel()) { TIM.toggleActiveOption(); } else { packagesActions.toggleActive(); } } else { setStatusText('Updating current options'); optionsManager.toggle(); } } else if (isAvailableChar(input)) { inputActions.addCharAtCurrentPoition(input); } }); /* eslint-enable complexity */ const getDisplayValues = () => { return allItems.map(item => (React.createElement(Box, { key: getId() }, React.createElement(Text, { color: "#e7b178" }, item.isActive ? figureSet.pointer : ' ', "\u00A0"), React.createElement(Text, null, item.isSelected ? (PAM.isRunning() ? (React.createElement(Spinner, { type: "dots9" })) : (figureSet.squareSmallFilled)) : (figureSet.squareSmall)), React.createElement(Box, { flexDirection: "row" }, PAM.isRunning() && packagesActions.isSelected(item) ? (React.createElement(Text, null, "\u00A0", item.name, "\u00A0\u00A0", React.createElement(Text, { color: "green" }, getCommandFromSentence(item.actionInfo)))) : (React.createElement(Text, null, React.createElement(Text, { color: "cyan" }, "\u00A0", item.name), React.createElement(Text, null, "\u00A0", removeUrl(item.message)), React.createElement(Text, { italic: true }, " ", getCommandFromSentence(item.actionInfo)))))))); }; const perhapsPointer = (item) => { return item.isActive && React.createElement(Text, { color: "cyan" }, figureSet.pointer); }; const displayStatus = (item) => { return item.isSelected ? (React.createElement(Text, null, figureSet.squareSmallFilled, " -")) : (React.createElement(Text, null, figureSet.squareSmall, " -")); }; function SpinnerItem({ text, kind = 'dots9', }) { return (React.createElement(Text, { color: "green" }, React.createElement(Spinner, { type: kind }), " ", text)); } const { value: inputValue, focusedChar } = userInput; const textPrefix = () => { if (statusesManager.isFetching()) { return React.createElement(Spinner, { type: "bluePulse" }); } if (statusesManager.isDone() && !R.equals(status.text, 'Updating current options')) { return React.createElement(SuccessText, null); } return undefined; }; return (React.createElement(Box, { flexDirection: "column" }, React.createElement(Box, { flexDirection: "column", borderStyle: "single", borderColor: FIM.isPanelActive('options-panel') ? 'green' : 'orange' }, React.createElement(Text, null, textPrefix(), React.createElement(Text, { italic: true }, status.text)), React.createElement(Box, { flexDirection: "column" }), React.createElement(Text, null, React.createElement(Text, { bold: true }, figureSet.arrowUp), " and", ' ', React.createElement(Text, { bold: true }, figureSet.arrowDown), " to activate the next option", React.createElement(Text, { bold: true }, " <space> - toggle active option"), React.createElement(Text, { bold: true }, " <Enter> - submit")), availableActions.map(action => (React.createElement(Box, { key: `ac-${action.id}` }, React.createElement(Text, null, perhapsPointer(action), action.isActive ? ' ' : ' '), React.createElement(Text, null, displayStatus(action), " ", action.value.displayName)))), React.createElement(Text, null, " "), React.createElement(Box, { marginLeft: 2 }, statusesManager.isFetching() ? (React.createElement(SpinnerItem, { text: "Checking", kind: "dots3" })) : (React.createElement(Text, { bold: true, color: getButtonColor('check-packages'), backgroundColor: getButtonBgColor('check-packages') }, submitButtonText)))), (statusesManager.isDone() || statusesManager.isFailed()) && !statusesManager.isFetching() ? (React.createElement(Box, { borderStyle: "single", borderColor: FIM.isPanelActive('packages-panel') ? 'green' : 'gray', flexDirection: "column" }, React.createElement(Box, { flexDirection: "column" }, !statusesManager.isFetching() && allItems.length > 0 && (React.createElement(Text, null, React.createElement(Text, { bold: true }, figureSet.arrowUp), " and", ' ', React.createElement(Text, { bold: true }, figureSet.arrowDown), " to activate the next package", React.createElement(Text, { bold: true }, ' ', "<space> - select/unselect active package\u00A0"), FIM.isActive('update') ? (React.createElement(Text, { bold: true }, " <Enter> - submit")) : (React.createElement(Text, null, React.createElement(Text, { bold: true }, "<tab>"), " - activate", ' ', React.createElement(Text, { bold: true }, "Submit"), " button")))), statusesManager.isFetching() ? (React.createElement(SpinnerItem, { text: "Running", kind: "dots9" })) : allItems.length > 0 ? (React.createElement(Text, null, figureSet.triangleDown, " The following packages could be updated/deleted ", figureSet.triangleDown, "\u00A0Select necessary ones and submit to update/delete them.")) : (!PAM.isWaiting() && (React.createElement(Text, null, "There are no packages to update/delete.")))), React.createElement(Box, { flexDirection: "column" }, React.createElement(Box, { flexDirection: "column", marginTop: 1 }, getDisplayValues()), React.createElement(Box, { flexDirection: "column", marginTop: 1 }, React.createElement(Box, null, React.createElement(Text, { color: "#90cba5", italic: FIM.isActive('packages-panel') }, "Type something to select items by filter query:\u00A0"), focusedChar === 0 ? (React.createElement(Text, null, React.createElement(Text, { inverse: true }, inputValue.slice(0, 1) || ' '), React.createElement(Text, null, inputValue.slice(1, inputValue.length)))) : (React.createElement(Text, null, inputValue.slice(0, focusedChar), React.createElement(Text, { inverse: true }, focusedChar === inputValue.length ? ' ' : R.nth(focusedChar, inputValue)), focusedChar < inputValue.length - 1 && (React.createElement(Text, null, inputValue.slice(focusedChar + 1, inputValue.length))))))), React.createElement(Box, null, React.createElement(Text, { color: getButtonColor('update'), backgroundColor: getButtonBgColor('update') }, PAM.isRunning() ? (React.createElement(SpinnerItem, { text: "Submitting..." })) : ('Submit'))), React.createElement(Box, null, PAM.isSuccess() ? (React.createElement(SuccessText, null)) : PAM.isFailed() ? (React.createElement(ErrorText, null)) : null, React.createElement(Text, null, R.cond([ [R.invoker(0, 'isWaiting'), R.always('')], [ R.invoker(0, 'isRunning'), R.always('Updating selected packages...'), ], [ R.invoker(0, 'isSuccess'), R.always('Actions over selected packages have been performed.'), ], [ R.invoker(0, 'isFailed'), R.always('Some error occured while performing actions.'), ], [R.T, R.always('')], ])(PAM)))))) : statusesManager.isTravel() || statusesManager.isInfo() ? (React.createElement(Box, { borderStyle: "single", borderColor: getPanelColor(R.any((a) => a.name === 'packages-panel', activeFocusableItems)) }, statusesManager.isTravel() ? (React.createElement(Travel, null)) : HAM.hasHistory() ? (React.createElement(OperationsHistory, { data: historyItems })) : (React.createElement(Text, null, "There is no history yet.")))) : null)); }