@tdk-landscape/tdk-cli-core 1.3.114 → 1.3.116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/dist/commands/networks.js +1 -1
- package/cli/dist/commands/resources.js +1 -1
- package/cli/dist/commands/stacks.js +2 -2
- package/cli/dist/commands/ui.js +16 -8
- package/cli/dist/components/ResourceSelectInput.js +10 -5
- package/cli/dist/components/ResourceTable.js +15 -6
- package/cli/dist/components/TabBar.js +13 -5
- package/cli/dist/utils/errors.js +1 -3
- package/cli/dist/utils/services.js +3 -2
- package/cli/dist/utils/terminal-layout.js +20 -0
- package/cli/package.json +1 -1
- package/package.json +1 -1
|
@@ -168,7 +168,7 @@ export const networksCommand = new Command("networks")
|
|
|
168
168
|
const action = async () => {
|
|
169
169
|
const projectRoot = options.json || options.jsonLegacy ? findProjectRoot() : requireProjectRoot();
|
|
170
170
|
if (!projectRoot) {
|
|
171
|
-
throw
|
|
171
|
+
throw errorFactories.notInProject();
|
|
172
172
|
}
|
|
173
173
|
const discovery = createDiscoveryContext();
|
|
174
174
|
if (options.stack && !stackExists(options.stack, discovery.resources)) {
|
|
@@ -20,7 +20,7 @@ export const resourcesCommand = new Command("resources")
|
|
|
20
20
|
const action = async () => {
|
|
21
21
|
if (options.json) {
|
|
22
22
|
if (!findProjectRoot()) {
|
|
23
|
-
writeMachineError(
|
|
23
|
+
writeMachineError(errorFactories.notInProject());
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { createDiscoveryContext } from "../utils/discovery-context.js";
|
|
4
|
-
import { runCommand } from "../utils/errors.js";
|
|
4
|
+
import { errorFactories, runCommand } from "../utils/errors.js";
|
|
5
5
|
import { formatCount, showDetail, showEmptyState, showStep } from "../utils/formatting.js";
|
|
6
6
|
import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
|
|
7
7
|
import { findProjectRoot } from "../utils/paths.js";
|
|
@@ -14,7 +14,7 @@ export const stacksCommand = new Command("stacks")
|
|
|
14
14
|
.action(async (options) => {
|
|
15
15
|
const action = async () => {
|
|
16
16
|
if (options.json && !findProjectRoot()) {
|
|
17
|
-
writeMachineError(
|
|
17
|
+
writeMachineError(errorFactories.notInProject());
|
|
18
18
|
}
|
|
19
19
|
const discovery = createDiscoveryContext();
|
|
20
20
|
const verbose = options.verbose || stacksCommand.parent?.opts().verbose === true;
|
package/cli/dist/commands/ui.js
CHANGED
|
@@ -8,6 +8,7 @@ import { findProjectRoot } from "../utils/paths.js";
|
|
|
8
8
|
import { describeSearch } from "../utils/search-status.js";
|
|
9
9
|
import { clearMetadataCache, discoverResources, discoverStacks, getResourceMetadata, getStackMetadata, } from "../utils/services.js";
|
|
10
10
|
import { createStatusMessageController } from "../utils/status-message.js";
|
|
11
|
+
import { getListRowFromMouseY, getTerminalRuleWidth } from "../utils/terminal-layout.js";
|
|
11
12
|
import { isTiltAvailable } from "../utils/tilt.js";
|
|
12
13
|
// biome-ignore lint/correctness/noUnusedFunctionParameters: reserved callback prop kept in the component API
|
|
13
14
|
const HelpPanel = ({ onClose }) => (_jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 2, paddingY: 1, flexDirection: "column", width: 60, children: [_jsx(Text, { bold: true, color: "cyan", children: "Keyboard Shortcuts" }), _jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, underline: true, children: "Navigation" }), _jsx(Text, { children: " \u2191/\u2193 Navigate list items" }), _jsx(Text, { children: " Enter Select item / Open detail" }), _jsx(Text, { children: " Space Toggle expand (tree view)" }), _jsx(Text, { children: " Tab Next tab" }), _jsx(Text, { children: " 1-5 Direct tab access" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, underline: true, children: "Actions" }) }), _jsx(Text, { children: " m Toggle mouse support" }), _jsx(Text, { children: " t Toggle tooltips" }), _jsx(Text, { children: " e Toggle enabled/disabled services" }), _jsx(Text, { children: " r Refresh data" }), _jsx(Text, { children: " / Search/filter" }), _jsx(Text, { children: " ? Show this help" }), _jsx(Text, { children: " q Quit \u2502 Esc Back" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press any key to close..." }) })] }));
|
|
@@ -40,6 +41,7 @@ const TUIApp = ({ animated = true }) => {
|
|
|
40
41
|
const [terminalWidth, setTerminalWidth] = useState(stdout.columns || 120);
|
|
41
42
|
const [selectedFile, setSelectedFile] = useState(null);
|
|
42
43
|
const [mouseEnabled, setMouseEnabled] = useState(true);
|
|
44
|
+
const [listTop, setListTop] = useState(null);
|
|
43
45
|
const [loading, setLoading] = useState(true);
|
|
44
46
|
const [error, setError] = useState(null);
|
|
45
47
|
const [showTooltips, setShowTooltips] = useState(true);
|
|
@@ -160,6 +162,13 @@ const TUIApp = ({ animated = true }) => {
|
|
|
160
162
|
return [];
|
|
161
163
|
}, [activeTab, filteredStacks, filteredServices, selectedStackData, selectedServiceData]);
|
|
162
164
|
const items = getItems();
|
|
165
|
+
const selectableListVisible = !loading &&
|
|
166
|
+
!error &&
|
|
167
|
+
!showHelp &&
|
|
168
|
+
(activeTab === "overview" ||
|
|
169
|
+
(activeTab === "resources" && !selectedStackData) ||
|
|
170
|
+
(activeTab === "files" && !selectedServiceData) ||
|
|
171
|
+
(activeTab === "config" && !selectedServiceData));
|
|
163
172
|
// Only the stack and service lists are filtered by the query; drilled-in views are not.
|
|
164
173
|
const searchList = activeTab === "overview" || (activeTab === "resources" && !selectedStackData)
|
|
165
174
|
? { total: stacks.length }
|
|
@@ -234,8 +243,7 @@ const TUIApp = ({ animated = true }) => {
|
|
|
234
243
|
// Check if it's a left click (btn & 0b11 == 0 means left button)
|
|
235
244
|
const isLeftClick = (btn & 0b11) === 0;
|
|
236
245
|
if (isLeftClick && !release) {
|
|
237
|
-
|
|
238
|
-
const listRow = y - 7; // Adjust for header, tabs, and borders
|
|
246
|
+
const listRow = selectableListVisible && listTop !== null ? getListRowFromMouseY(y, listTop) : -1;
|
|
239
247
|
if (listRow >= 0 && listRow < items.length) {
|
|
240
248
|
setHighlightedIndex(listRow);
|
|
241
249
|
const item = items[listRow];
|
|
@@ -252,7 +260,7 @@ const TUIApp = ({ animated = true }) => {
|
|
|
252
260
|
stdin.off("data", handleMouseData);
|
|
253
261
|
stdin.removeAllListeners("data");
|
|
254
262
|
};
|
|
255
|
-
}, [stdin, items, mouseEnabled, handleSelect]);
|
|
263
|
+
}, [stdin, items, listTop, selectableListVisible, mouseEnabled, handleSelect]);
|
|
256
264
|
const handleResize = useCallback(() => {
|
|
257
265
|
setTerminalWidth(stdout.columns || 120);
|
|
258
266
|
}, [stdout.columns]);
|
|
@@ -418,8 +426,7 @@ const TUIApp = ({ animated = true }) => {
|
|
|
418
426
|
}));
|
|
419
427
|
}, [selectedServiceData, services]);
|
|
420
428
|
const showSidebar = terminalWidth > 100;
|
|
421
|
-
const
|
|
422
|
-
const compact = terminalWidth < 80;
|
|
429
|
+
const mainPanelWidth = showSidebar ? terminalWidth - 45 : terminalWidth - 4;
|
|
423
430
|
if (loading) {
|
|
424
431
|
return _jsx(LoadingScreen, { message: "Discovering resources...", animated: animated });
|
|
425
432
|
}
|
|
@@ -429,7 +436,7 @@ const TUIApp = ({ animated = true }) => {
|
|
|
429
436
|
if (services.length === 0) {
|
|
430
437
|
return _jsx(EmptyState, { message: message });
|
|
431
438
|
}
|
|
432
|
-
return (_jsxs(Box, { flexDirection: "column", height: stdout.rows || 24, children: [_jsx(Box, { paddingX: 1, paddingY: 0, children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "\u2593\u2592\u2591 TDK NEON EDITION \u2591\u2592\u2593" }), _jsx(Text, { color: "gray", children: " \u2502 " }), _jsx(Text, { color: "white", children: projectRoot }), _jsx(Text, { color: "gray", children: " \u2502 " }), _jsxs(Text, { color: "green", children: [services.length, " services ready"] })] }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "gray", children: "─".repeat(
|
|
439
|
+
return (_jsxs(Box, { flexDirection: "column", height: stdout.rows || 24, children: [_jsx(Box, { paddingX: 1, paddingY: 0, children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "\u2593\u2592\u2591 TDK NEON EDITION \u2591\u2592\u2593" }), _jsx(Text, { color: "gray", children: " \u2502 " }), _jsx(Text, { color: "white", children: projectRoot }), _jsx(Text, { color: "gray", children: " \u2502 " }), _jsxs(Text, { color: "green", children: [services.length, " services ready"] })] }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "gray", children: "─".repeat(getTerminalRuleWidth(terminalWidth)) }) }), isSearching && (_jsxs(Box, { paddingX: 1, height: 1, children: [_jsxs(Text, { color: "yellow", children: ["Search: ", searchQuery, "_"] }), searchStatus && _jsx(Text, { color: "gray", dimColor: true, children: ` ${searchStatus.summary}` })] })), searchStatus?.emptyMessage && (_jsx(Box, { paddingX: 1, height: 1, children: _jsx(Text, { color: "gray", children: searchStatus.emptyMessage }) })), !isSearching && message && (_jsx(Box, { paddingX: 1, height: 1, children: _jsxs(Text, { color: "cyan", children: ["\u2593\u2592\u2591 ", message, " \u2591\u2592\u2593"] }) })), !isSearching && !message && showTooltips && !showHelp && (_jsx(Box, { paddingX: 1, height: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: activeTab === "overview" && selectedStack
|
|
433
440
|
? `Stack "${selectedStack}" selected. [Enter] view │ [Esc] back │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help`
|
|
434
441
|
: activeTab === "overview" && !selectedStack
|
|
435
442
|
? `[↑/↓] Navigate │ [Enter] Select │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help`
|
|
@@ -443,7 +450,7 @@ const TUIApp = ({ animated = true }) => {
|
|
|
443
450
|
? `Select service to view files │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help`
|
|
444
451
|
: activeTab === "config"
|
|
445
452
|
? `View configurations │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help`
|
|
446
|
-
: `[Tab] Next │ [1-5] Tabs │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help │ [q] Quit` }) })), showHelp && (_jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(HelpPanel, { onClose: () => setShowHelp(false) }) })), !showHelp && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(TabBar, { activeTab: activeTab, onTabChange: setActiveTab,
|
|
453
|
+
: `[Tab] Next │ [1-5] Tabs │ [e] ${showEnabledOnly ? "show all" : "enabled only"} │ [?] help │ [q] Quit` }) })), showHelp && (_jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(HelpPanel, { onClose: () => setShowHelp(false) }) })), !showHelp && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(TabBar, { activeTab: activeTab, onTabChange: setActiveTab, terminalWidth: terminalWidth }) }), _jsxs(Box, { flexDirection: "row", paddingX: 1, flexGrow: 1, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, width: mainPanelWidth, children: [activeTab === "overview" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Stacks \u2500" }) }), _jsx(Box, { marginTop: 1, flexGrow: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, width: mainPanelWidth, onLayout: setListTop }) })] })), activeTab === "resources" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Resources \u2500" }) }), selectedStackData ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: ["Stack: ", selectedStackData.stack.name] }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceTable, { resources: selectedStackData.metadata.resources, maxWidth: terminalWidth - (showSidebar ? 50 : 10) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a stack to view resources" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, width: mainPanelWidth, onLayout: setListTop }) })] }))] })), activeTab === "events" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Events \u2500" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Events tab not yet implemented" }) })] })), activeTab === "files" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Autogenerated Files \u2500" }) }), selectedServiceData ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: ["Service: ", selectedServiceData.service.name] }), _jsx(Box, { marginTop: 1, children: _jsx(FileTree, { nodes: fileTreeNodes, selectedPath: selectedFile || undefined }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a service to view files" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, width: mainPanelWidth, onLayout: setListTop }) })] }))] })), activeTab === "config" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "gray", children: "\u250C\u2500 Configuration \u2500" }) }), selectedServiceData ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: selectedServiceData.service.configPath }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", wrap: "wrap", children: JSON.stringify(selectedServiceData.service.config, null, 2).slice(0, 1000) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Select a service to view configuration" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, width: mainPanelWidth, onLayout: setListTop }) })] }))] }))] }), showSidebar && (_jsx(Box, { marginLeft: 2, children: _jsx(DetailPanel, { stack: selectedStackData?.stack || null, service: selectedServiceData?.service || null, stackMetadata: selectedStackData?.metadata || null, visible: !!selectedStack || !!selectedService }) }))] }), _jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, height: 3, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: "cyan", bold: true, children: ["\u2593\u2592\u2591 ", activeTab] }), _jsxs(Text, { color: "green", children: ["\u25CF ", services.filter((s) => s.stack).length, " in stack"] }), _jsxs(Text, { color: "yellow", children: ["\u25CB ", services.filter((s) => !s.stack).length, " no stack"] })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: "gray", children: ["Stacks: ", stacks.length] }), _jsxs(Text, { color: "gray", children: ["Services: ", services.length] }), _jsxs(Text, { color: "gray", children: ["\uD83D\uDDB1\uFE0F ", mouseEnabled ? "ON" : "OFF", " \u2502 \u2139\uFE0F ", showTooltips ? "ON" : "OFF", " \u2502", _jsx(Text, { color: showEnabledOnly ? "green" : "yellow", children: showEnabledOnly ? "✓ enabled" : "✓ all" }), " │ ", "[?] Help \u2502 [q] Quit"] })] })] })] }))] }));
|
|
447
454
|
};
|
|
448
455
|
export const uiCommand = new Command("ui")
|
|
449
456
|
.description("Interactive TUI for managing stacks and services (Neon Edition)")
|
|
@@ -457,6 +464,7 @@ export const uiCommand = new Command("ui")
|
|
|
457
464
|
errorFactories.tiltNotInstalled().exit();
|
|
458
465
|
}
|
|
459
466
|
requireProjectRoot();
|
|
460
|
-
|
|
467
|
+
// The app has no Static content, so its live layout starts at row 1 in the alternate screen.
|
|
468
|
+
render(_jsx(TUIApp, { animated: options.animations }), { alternateScreen: true });
|
|
461
469
|
});
|
|
462
470
|
//# sourceMappingURL=ui.js.map
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { Box, measureElement, Text, useInput } from "ink";
|
|
3
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
// Keys match what ink-select-input handled: up/down or k/j (wrapping), 1-9 to pick, Enter to select.
|
|
5
|
-
export const ResourceSelectInput = ({ items, onSelect, highlightedIndex, }) => {
|
|
5
|
+
export const ResourceSelectInput = ({ items, onSelect, highlightedIndex, width, onLayout, }) => {
|
|
6
|
+
const listRef = useRef(null);
|
|
6
7
|
const [selected, setSelected] = useState(() => Math.max(0, Math.min(highlightedIndex, items.length - 1)));
|
|
7
8
|
const itemKeys = items.map((item) => item.value).join("\0");
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
if (listRef.current)
|
|
11
|
+
onLayout?.(measureElement(listRef.current).y);
|
|
12
|
+
});
|
|
8
13
|
// biome-ignore lint/correctness/useExhaustiveDependencies: reset only when the item list changes
|
|
9
14
|
useEffect(() => setSelected(0), [itemKeys]);
|
|
10
15
|
useInput((input, key) => {
|
|
@@ -22,9 +27,9 @@ export const ResourceSelectInput = ({ items, onSelect, highlightedIndex, }) => {
|
|
|
22
27
|
if (key.return)
|
|
23
28
|
onSelect(items[selected]);
|
|
24
29
|
});
|
|
25
|
-
return (_jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
|
|
30
|
+
return (_jsx(Box, { ref: listRef, flexDirection: "column", width: width, children: items.map((item, index) => {
|
|
26
31
|
const isSelected = index === selected;
|
|
27
|
-
return (
|
|
32
|
+
return (_jsx(Box, { width: width, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: isSelected ? "cyan" : undefined, children: isSelected ? "\u2593\u2592\u2591 " : " " }), _jsx(Text, { color: isSelected ? "cyan" : "white", bold: isSelected, backgroundColor: isSelected ? "black" : undefined, children: item.label })] }) }, item.value));
|
|
28
33
|
}) }));
|
|
29
34
|
};
|
|
30
35
|
//# sourceMappingURL=ResourceSelectInput.js.map
|
|
@@ -6,13 +6,22 @@ export const ResourceTable = ({ resources, maxWidth = 100 }) => {
|
|
|
6
6
|
return (_jsx(Box, { paddingY: 1, children: _jsx(Text, { color: "gray", children: "No resources found" }) }));
|
|
7
7
|
}
|
|
8
8
|
const narrowMode = maxWidth < 80;
|
|
9
|
-
|
|
9
|
+
const tableContentWidth = Math.max(1, Math.floor(maxWidth) - 4);
|
|
10
|
+
const extraWidth = tableContentWidth - (narrowMode ? 19 : 26);
|
|
11
|
+
const nameWidth = 4 + Math.floor(extraWidth * (narrowMode ? 0.37 : 0.29));
|
|
12
|
+
const stackWidth = 5 + Math.floor(extraWidth * (narrowMode ? 0.22 : 0.2));
|
|
13
|
+
const typeWidth = 4 + Math.floor(extraWidth * (narrowMode ? 0.2 : 0.13));
|
|
14
|
+
const statusWidth = narrowMode
|
|
15
|
+
? tableContentWidth - nameWidth - stackWidth - typeWidth
|
|
16
|
+
: 6 + Math.floor(extraWidth * 0.17);
|
|
17
|
+
const createdWidth = narrowMode
|
|
18
|
+
? 0
|
|
19
|
+
: tableContentWidth - nameWidth - stackWidth - typeWidth - statusWidth;
|
|
20
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Box, { width: nameWidth, children: _jsx(Text, { bold: true, children: "Name" }) }), _jsx(Box, { width: stackWidth, children: _jsx(Text, { bold: true, children: "Stack" }) }), _jsx(Box, { width: typeWidth, children: _jsx(Text, { bold: true, children: "Type" }) }), _jsx(Box, { width: statusWidth, children: _jsx(Text, { bold: true, children: "Status" }) }), !narrowMode && (_jsx(Box, { width: createdWidth, children: _jsx(Text, { bold: true, children: "Created" }) }))] }), resources.map((resource) => {
|
|
10
21
|
const statusColor = getStatusColor(resource.status);
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
return (_jsxs(Box, { flexDirection: "row", paddingX: 1, children: [_jsx(Box, { width:
|
|
14
|
-
? truncate(`${resource.stack}/${resource.name}`, 18)
|
|
15
|
-
: truncate(resource.name, 18) }) })), _jsx(Box, { width: 12, children: _jsx(Text, { color: "cyan", children: resource.type }) }), _jsx(Box, { width: 15, children: _jsxs(Text, { color: statusColor, children: [statusIcon, " ", resource.status] }) }), !narrowMode && (_jsx(Box, { width: 20, children: _jsx(Text, { color: "gray", children: formatShortDate(resource.createdAt) }) }))] }, resource.name));
|
|
22
|
+
const statusLabel = `${getStatusIcon(resource.status)} ${resource.status}`;
|
|
23
|
+
const stackName = resource.stack && resource.stack !== "unknown" ? resource.stack : "-";
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "row", paddingX: 1, children: [_jsx(Box, { width: nameWidth, children: _jsx(Text, { children: truncate(resource.name, Math.max(3, nameWidth - 1)) }) }), _jsx(Box, { width: stackWidth, children: _jsx(Text, { color: "gray", children: truncate(stackName, Math.max(3, stackWidth - 1)) }) }), _jsx(Box, { width: typeWidth, children: _jsx(Text, { color: "cyan", children: truncate(resource.type, Math.max(3, typeWidth - 1)) }) }), _jsx(Box, { width: statusWidth, children: _jsx(Text, { color: statusColor, children: truncate(statusLabel, Math.max(3, statusWidth - 1)) }) }), !narrowMode && (_jsx(Box, { width: createdWidth, children: _jsx(Text, { color: "gray", children: truncate(formatShortDate(resource.createdAt), Math.max(3, createdWidth - 1)) }) }))] }, resource.name));
|
|
16
25
|
})] }));
|
|
17
26
|
};
|
|
18
27
|
//# sourceMappingURL=ResourceTable.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import { getTabBarDensity, getTerminalRuleWidth } from "../utils/terminal-layout.js";
|
|
3
4
|
const TABS = [
|
|
4
5
|
{ id: "overview", label: "OVERVIEW", shortcut: "1" },
|
|
5
6
|
{ id: "resources", label: "RESOURCES", shortcut: "2" },
|
|
@@ -7,11 +8,18 @@ const TABS = [
|
|
|
7
8
|
{ id: "files", label: "FILES", shortcut: "4" },
|
|
8
9
|
{ id: "config", label: "CONFIG", shortcut: "5" },
|
|
9
10
|
];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
export const TabBar = ({ activeTab, compact = false, terminalWidth }) => {
|
|
12
|
+
const responsive = terminalWidth !== undefined;
|
|
13
|
+
const density = terminalWidth === undefined ? "wide" : getTabBarDensity(terminalWidth);
|
|
14
|
+
const compactLayout = responsive && density === "compact";
|
|
15
|
+
const wide = !responsive || density === "wide";
|
|
16
|
+
const ruleWidth = responsive ? getTerminalRuleWidth(terminalWidth) : compact ? 60 : 80;
|
|
17
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "gray", children: "\u2500".repeat(ruleWidth) }) }), _jsx(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 1, children: TABS.map((tab) => {
|
|
13
18
|
const isActive = activeTab === tab.id;
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
const label = compactLayout || (!responsive && compact && !isActive)
|
|
20
|
+
? tab.label.slice(0, 4)
|
|
21
|
+
: tab.label;
|
|
22
|
+
return (_jsx(Box, { children: compactLayout ? (_jsxs(Text, { color: isActive ? "cyan" : "gray", bold: isActive, children: ["[", tab.shortcut, "] ", label] })) : isActive ? (_jsx(Box, { borderStyle: "single", borderColor: "cyan", paddingX: wide ? 1 : 0, backgroundColor: "black", children: _jsxs(Text, { children: [wide && _jsx(Text, { color: "cyan", children: "\u2593\u2592\u2591" }), _jsxs(Text, { color: "cyan", bold: true, children: [" ", "[", tab.shortcut, "] ", label, " "] }), wide && _jsx(Text, { color: "cyan", children: "\u2591\u2592\u2593" })] }) })) : (_jsx(Box, { paddingX: wide ? 1 : 0, children: _jsxs(Text, { color: "gray", dimColor: true, children: ["[", tab.shortcut, "] ", label] }) })) }, tab.id));
|
|
23
|
+
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "\u2500".repeat(ruleWidth) }) })] }));
|
|
16
24
|
};
|
|
17
25
|
//# sourceMappingURL=TabBar.js.map
|
package/cli/dist/utils/errors.js
CHANGED
|
@@ -71,9 +71,7 @@ export const errorFactories = {
|
|
|
71
71
|
export function requireProjectRoot() {
|
|
72
72
|
const projectRoot = findProjectRoot();
|
|
73
73
|
if (!projectRoot) {
|
|
74
|
-
|
|
75
|
-
console.error(chalk.gray("Run `tdk project --yes` to initialize a new project."));
|
|
76
|
-
process.exit(1);
|
|
74
|
+
return errorFactories.notInProject().exit();
|
|
77
75
|
}
|
|
78
76
|
return projectRoot;
|
|
79
77
|
}
|
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { Cache, createCacheValidator } from "./cache.js";
|
|
4
4
|
import { AUTOGENERATED_FILE_PATTERNS, SKIP_DIRECTORIES } from "./constants.js";
|
|
5
|
+
import { errorFactories } from "./errors.js";
|
|
5
6
|
import { formatCount } from "./formatting.js";
|
|
6
7
|
import { findProjectRoot } from "./paths.js";
|
|
7
8
|
import { validateServiceManifest } from "./service-manifest.js";
|
|
@@ -133,14 +134,14 @@ export function discoverResourcesFromRoot(projectRoot) {
|
|
|
133
134
|
export function discoverResources() {
|
|
134
135
|
const projectRoot = findProjectRoot();
|
|
135
136
|
if (!projectRoot) {
|
|
136
|
-
throw
|
|
137
|
+
throw errorFactories.notInProject();
|
|
137
138
|
}
|
|
138
139
|
return discoverResourcesFromRoot(projectRoot);
|
|
139
140
|
}
|
|
140
141
|
export function discoverResourcesStrict() {
|
|
141
142
|
const projectRoot = findProjectRoot();
|
|
142
143
|
if (!projectRoot) {
|
|
143
|
-
throw
|
|
144
|
+
throw errorFactories.notInProject();
|
|
144
145
|
}
|
|
145
146
|
const { resources, problems } = discoverResourcesWithProblems(projectRoot);
|
|
146
147
|
if (problems.length > 0) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const COMPACT_TAB_WIDTH = 76;
|
|
2
|
+
const WIDE_TAB_WIDTH = 100;
|
|
3
|
+
export function getTerminalRuleWidth(terminalWidth) {
|
|
4
|
+
return Math.max(0, Math.min(terminalWidth - 4, 100));
|
|
5
|
+
}
|
|
6
|
+
export function getTabBarDensity(terminalWidth) {
|
|
7
|
+
if (terminalWidth < COMPACT_TAB_WIDTH)
|
|
8
|
+
return "compact";
|
|
9
|
+
if (terminalWidth < WIDE_TAB_WIDTH)
|
|
10
|
+
return "standard";
|
|
11
|
+
return "wide";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Convert an SGR mouse row (1-based) to an Ink layout row (0-based).
|
|
15
|
+
* The UI uses Ink's alternate screen with no Static content, so the live region starts at row 1.
|
|
16
|
+
*/
|
|
17
|
+
export function getListRowFromMouseY(mouseY, listTop) {
|
|
18
|
+
return mouseY - 1 - listTop;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=terminal-layout.js.map
|
package/cli/package.json
CHANGED