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
JavaScript
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));
}