sitevision-cli 1.0.0-beta.2 → 1.0.0-beta.21
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/dist/app.d.ts +1 -1
- package/dist/app.js +59 -8
- package/dist/cli.js +96 -39
- package/dist/commands/build.js +1 -1
- package/dist/commands/deploy.d.ts +2 -2
- package/dist/commands/deploy.js +135 -25
- package/dist/commands/dev.d.ts +8 -10
- package/dist/commands/dev.js +77 -366
- package/dist/commands/info.js +2 -2
- package/dist/commands/watch.js +5 -23
- package/dist/components/AnimatedLogo.js +8 -2
- package/dist/components/AuthLoginScreen.d.ts +21 -0
- package/dist/components/AuthLoginScreen.js +90 -0
- package/dist/components/DevPropertiesForm.d.ts +2 -1
- package/dist/components/DevPropertiesForm.js +198 -33
- package/dist/components/InfoScreen.js +2 -2
- package/dist/components/MainMenu.js +7 -2
- package/dist/components/PasswordInput.js +2 -1
- package/dist/components/SetupFlow.d.ts +2 -1
- package/dist/components/SetupFlow.js +100 -11
- package/dist/shell/AddonPicker.d.ts +14 -0
- package/dist/shell/AddonPicker.js +54 -0
- package/dist/shell/CommandPalette.d.ts +8 -0
- package/dist/shell/CommandPalette.js +63 -0
- package/dist/shell/ConfigForm.d.ts +36 -0
- package/dist/shell/ConfigForm.js +558 -0
- package/dist/shell/Frame.d.ts +59 -0
- package/dist/shell/Frame.js +134 -0
- package/dist/shell/Settings.d.ts +6 -0
- package/dist/shell/Settings.js +96 -0
- package/dist/shell/Shell.d.ts +9 -0
- package/dist/shell/Shell.js +586 -0
- package/dist/shell/Tabs.d.ts +36 -0
- package/dist/shell/Tabs.js +90 -0
- package/dist/shell/actions.d.ts +45 -0
- package/dist/shell/actions.js +0 -0
- package/dist/types/index.d.ts +44 -5
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.js +14 -0
- package/dist/utils/environments.d.ts +20 -0
- package/dist/utils/environments.js +74 -0
- package/dist/utils/i18n.d.ts +12 -0
- package/dist/utils/i18n.js +279 -0
- package/dist/utils/jsonc.d.ts +19 -0
- package/dist/utils/jsonc.js +74 -0
- package/dist/utils/keychain.d.ts +9 -0
- package/dist/utils/keychain.js +54 -0
- package/dist/utils/oauth2-auth.d.ts +64 -0
- package/dist/utils/oauth2-auth.js +242 -0
- package/dist/utils/password-prompt.d.ts +5 -0
- package/dist/utils/password-prompt.js +28 -0
- package/dist/utils/project-detection.d.ts +105 -6
- package/dist/utils/project-detection.js +411 -54
- package/dist/utils/session-cookie-auth.d.ts +35 -0
- package/dist/utils/session-cookie-auth.js +99 -0
- package/dist/utils/sitevision-api.d.ts +64 -5
- package/dist/utils/sitevision-api.js +195 -33
- package/dist/utils/tasks.d.ts +48 -0
- package/dist/utils/tasks.js +371 -0
- package/dist/utils/workspace.d.ts +17 -0
- package/dist/utils/workspace.js +67 -0
- package/package.json +3 -1
- package/readme.md +102 -121
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { appTypeOf, getPackageJsonSyncChanges, localizedText, } from '../utils/project-detection.js';
|
|
5
|
+
import { fuzzyMatch } from './actions.js';
|
|
6
|
+
import { t } from '../utils/i18n.js';
|
|
7
|
+
export const ACCENT = 'cyan';
|
|
8
|
+
export const NARROW_BELOW = 100;
|
|
9
|
+
/** Navigator width for a terminal: a quarter of the columns, within 32..48. */
|
|
10
|
+
export function navWidth(columns) {
|
|
11
|
+
return Math.min(48, Math.max(32, Math.floor(columns / 4)));
|
|
12
|
+
}
|
|
13
|
+
export function TopBar({ context, domain, auth, version, environment, }) {
|
|
14
|
+
return (_jsxs(Box, { paddingX: 1, justifyContent: "space-between", height: 1, children: [_jsx(Box, { flexShrink: 1, marginRight: 2, children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { bold: true, color: ACCENT, children: "svc" }), environment && (_jsxs(Text, { children: [' ', _jsx(Text, { backgroundColor: environment.color, color: "black", bold: true, children: ` ${environment.name.toUpperCase()} ` })] })), _jsxs(Text, { dimColor: true, children: [" ", context] })] }) }), _jsx(Box, { flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", children: [domain && _jsxs(Text, { dimColor: true, children: [domain, " "] }), _jsx(Text, { color: auth.ready ? 'green' : 'yellow', children: auth.ready ? '●' : '○' }), _jsxs(Text, { children: [" ", auth.label, " "] }), _jsxs(Text, { dimColor: true, children: ["v", version] })] }) })] }));
|
|
15
|
+
}
|
|
16
|
+
const TYPE_GLYPH = {
|
|
17
|
+
web: 'web',
|
|
18
|
+
widget: 'wgt',
|
|
19
|
+
rest: 'rst',
|
|
20
|
+
mcp: 'mcp',
|
|
21
|
+
};
|
|
22
|
+
/** Three-letter type marker; unknown manifest types show as "???". */
|
|
23
|
+
export function typeGlyph(manifest) {
|
|
24
|
+
const type = appTypeOf(manifest);
|
|
25
|
+
return type ? TYPE_GLYPH[type] : '???';
|
|
26
|
+
}
|
|
27
|
+
export function appStatus(project) {
|
|
28
|
+
const sync = getPackageJsonSyncChanges(project.root).length;
|
|
29
|
+
return {
|
|
30
|
+
deps: project.hasNodeModules,
|
|
31
|
+
config: Boolean(project.devProperties),
|
|
32
|
+
sync,
|
|
33
|
+
signing: project.hasSigningProperties,
|
|
34
|
+
scriptsWarning: undefined,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function Dots({ project }) {
|
|
38
|
+
const s = appStatus(project);
|
|
39
|
+
const dot = (ok, warn = false) => (_jsx(Text, { color: ok ? 'green' : warn ? 'yellow' : 'red', children: "\u25CF" }));
|
|
40
|
+
return (_jsxs(Text, { children: [dot(s.deps), dot(s.config), dot(s.sync === 0, s.sync > 0), dot(s.signing, !s.signing)] }));
|
|
41
|
+
}
|
|
42
|
+
/** The name shown for an app in the navigator, and what the filter matches. */
|
|
43
|
+
export function appLabel(app) {
|
|
44
|
+
return localizedText(app.manifest.name) || app.manifest.id;
|
|
45
|
+
}
|
|
46
|
+
/** Indices into `apps` whose label fuzzy-matches the filter. */
|
|
47
|
+
export function navMatches(apps, filter) {
|
|
48
|
+
return apps
|
|
49
|
+
.map((_, index) => index)
|
|
50
|
+
.filter(index => fuzzyMatch(filter, appLabel(apps[index])));
|
|
51
|
+
}
|
|
52
|
+
/** Next selectable index when moving by `delta`, wrapping at both ends. */
|
|
53
|
+
export function navMove(ring, selected, delta) {
|
|
54
|
+
if (ring.length === 0)
|
|
55
|
+
return selected;
|
|
56
|
+
const at = ring.indexOf(selected);
|
|
57
|
+
return ring[at === -1 ? 0 : (at + delta + ring.length) % ring.length];
|
|
58
|
+
}
|
|
59
|
+
export function Navigator({ apps, groupOf, selected, focused, tasks, height, single, settingsSelected = false, width, filter = '', }) {
|
|
60
|
+
// Row: marker(1) glyph(3) sp name sp version(6) sp dots(4) inside padding + border.
|
|
61
|
+
const rowWidth = width - 3;
|
|
62
|
+
const nameWidth = rowWidth - 17;
|
|
63
|
+
const running = tasks.filter(task => task.status === 'running');
|
|
64
|
+
// Every row is exactly one line; nothing may shrink or the rows overlap.
|
|
65
|
+
const rows = [];
|
|
66
|
+
const rowApp = [];
|
|
67
|
+
let lastGroup;
|
|
68
|
+
for (const [index, app] of apps.entries()) {
|
|
69
|
+
const group = groupOf(app);
|
|
70
|
+
if (!single && group !== lastGroup) {
|
|
71
|
+
rows.push(_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', group] }) }, `g-${index}-${group}`));
|
|
72
|
+
rowApp.push(-1);
|
|
73
|
+
lastGroup = group;
|
|
74
|
+
}
|
|
75
|
+
rowApp.push(index);
|
|
76
|
+
const active = index === selected;
|
|
77
|
+
const busy = running.some(task => task.appRoot === app.root);
|
|
78
|
+
const name = appLabel(app);
|
|
79
|
+
rows.push(_jsxs(Box, { width: rowWidth, height: 1, flexShrink: 0, children: [_jsxs(Text, { backgroundColor: active && focused ? ACCENT : undefined, color: active && focused ? 'black' : undefined, bold: active, wrap: "truncate", children: [active ? '▎' : ' ', _jsx(Text, { dimColor: !active, children: typeGlyph(app.manifest) }), ' ', name.padEnd(nameWidth).slice(0, nameWidth), ' ', _jsx(Text, { dimColor: true, children: app.manifest.version.padStart(6).slice(0, 6) }), ' '] }), busy ? (_jsx(Text, { color: ACCENT, children: _jsx(Spinner, { type: "dots" }) })) : (_jsx(Dots, { project: app }))] }, app.root));
|
|
80
|
+
}
|
|
81
|
+
if (rows.length === 0) {
|
|
82
|
+
rows.push(_jsx(Box, { height: 1, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: ' ' + t('no matches') }) }, "none"));
|
|
83
|
+
rowApp.push(-1);
|
|
84
|
+
}
|
|
85
|
+
// Window the list so the selected app stays visible; the lines outside
|
|
86
|
+
// are summarised as "… n more".
|
|
87
|
+
const fixed = 1 + 1 + (single ? 0 : 2) + (running.length > 0 ? running.length + 2 : 0);
|
|
88
|
+
const avail = Math.max(3, height - fixed);
|
|
89
|
+
let shown = rows;
|
|
90
|
+
if (rows.length > avail) {
|
|
91
|
+
const target = settingsSelected
|
|
92
|
+
? rows.length - 1
|
|
93
|
+
: rowApp.indexOf(selected);
|
|
94
|
+
const start = Math.max(0, Math.min(target - Math.floor(avail / 2), rows.length - avail));
|
|
95
|
+
const end = start + avail;
|
|
96
|
+
shown = rows.slice(start, end);
|
|
97
|
+
const more = (n, arrow) => (_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { dimColor: true, children: [' ', t('{arrow} {n} more', { arrow, n })] }) }, `more-${arrow}`));
|
|
98
|
+
if (start > 0)
|
|
99
|
+
shown[0] = more(start, '↑');
|
|
100
|
+
if (end < rows.length)
|
|
101
|
+
shown[shown.length - 1] = more(rows.length - end, '↓');
|
|
102
|
+
}
|
|
103
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderDimColor: true, borderTop: false, borderBottom: false, borderLeft: false, paddingX: 1, overflow: "hidden", children: [filter ? (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), filter, _jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { dimColor: true, children: [' ', apps.length === 1
|
|
104
|
+
? t('1 match')
|
|
105
|
+
: t('{n} matches', { n: apps.length })] })] })) : (_jsx(Text, { bold: true, dimColor: true, children: single
|
|
106
|
+
? t('APP')
|
|
107
|
+
: apps.length === 1
|
|
108
|
+
? t('WORKSPACE 1 app')
|
|
109
|
+
: t('WORKSPACE {n} apps', { n: apps.length }) })), shown, _jsx(Text, { dimColor: true, children: ' ' + t('deps·config·sync·signing') }), !single && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { backgroundColor: settingsSelected && focused ? ACCENT : undefined, color: settingsSelected && focused ? 'black' : undefined, bold: settingsSelected, children: [settingsSelected ? '▎' : ' ', "\u2699 ", t('Workspace settings')] }) })), running.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: t('TASKS') }), running.map(task => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: ACCENT, children: _jsx(Spinner, { type: "dots" }) }), ' ', task.label, " ", task.appName, " ", _jsx(Text, { dimColor: true, children: elapsed(task) })] }, task.id)))] }))] }));
|
|
110
|
+
}
|
|
111
|
+
export function NavigatorStrip({ apps, selected, focused, width, filter = '', }) {
|
|
112
|
+
const label = (app) => ` ${typeGlyph(app.manifest)} ${appLabel(app)} `;
|
|
113
|
+
// Scroll the strip so the selection stays on screen: walk left from it
|
|
114
|
+
// until the row is full.
|
|
115
|
+
const budget = width - 2 - (filter ? filter.length + 5 : 0);
|
|
116
|
+
const from = Math.min(Math.max(0, selected), apps.length - 1);
|
|
117
|
+
let start = from;
|
|
118
|
+
let used = 0;
|
|
119
|
+
for (let i = from; i >= 0; i--) {
|
|
120
|
+
used += label(apps[i]).length;
|
|
121
|
+
if (used > budget)
|
|
122
|
+
break;
|
|
123
|
+
start = i;
|
|
124
|
+
}
|
|
125
|
+
return (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { wrap: "truncate", children: [filter && (_jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), filter, _jsx(Text, { dimColor: true, children: " \u00B7" })] })), apps.slice(start).map((app, index) => (_jsx(Text, { backgroundColor: index + start === selected && focused ? ACCENT : undefined, color: index + start === selected && focused ? 'black' : undefined, bold: index + start === selected, children: label(app) }, app.root)))] }) }));
|
|
126
|
+
}
|
|
127
|
+
export function elapsed(task) {
|
|
128
|
+
const ms = (task.endedAt ?? Date.now()) - task.startedAt;
|
|
129
|
+
const s = Math.round(ms / 1000);
|
|
130
|
+
return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
|
|
131
|
+
}
|
|
132
|
+
export function BottomBar({ hints, right }) {
|
|
133
|
+
return (_jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsx(Text, { wrap: "truncate", children: hints.map(h => (_jsxs(Text, { children: [_jsx(Text, { bold: true, color: ACCENT, children: h.key }), _jsxs(Text, { dimColor: true, children: [" ", h.label, " "] })] }, h.key + h.label))) }), right] }));
|
|
134
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { getSettings, setSettings, settingsFile } from '../utils/config.js';
|
|
5
|
+
import { LANGUAGES, LANGUAGE_NAMES, setLanguage, t, } from '../utils/i18n.js';
|
|
6
|
+
import { ACCENT } from './Frame.js';
|
|
7
|
+
const ROWS = [
|
|
8
|
+
{
|
|
9
|
+
key: 'language',
|
|
10
|
+
label: 'Language',
|
|
11
|
+
options: LANGUAGES,
|
|
12
|
+
display: value => LANGUAGE_NAMES[value],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
key: 'introAnimation',
|
|
16
|
+
label: 'Intro animation',
|
|
17
|
+
options: ['on', 'off'],
|
|
18
|
+
display: value => t(value),
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
/** Global preferences, saved per field like the Config tab. */
|
|
22
|
+
export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
23
|
+
const [values, setValues] = useState(() => {
|
|
24
|
+
const s = getSettings();
|
|
25
|
+
return {
|
|
26
|
+
language: s.language,
|
|
27
|
+
introAnimation: s.introAnimation ? 'on' : 'off',
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
const [cursor, setCursor] = useState(0);
|
|
31
|
+
const [editing, setEditing] = useState(false);
|
|
32
|
+
const [draft, setDraft] = useState('');
|
|
33
|
+
const [note, setNote] = useState('');
|
|
34
|
+
const rowCount = ROWS.length + (onOpenWorkspace ? 1 : 0);
|
|
35
|
+
const onWorkspaceRow = cursor === ROWS.length;
|
|
36
|
+
const current = ROWS[Math.min(cursor, ROWS.length - 1)];
|
|
37
|
+
const commit = (value) => {
|
|
38
|
+
const next = { ...values, [current.key]: value };
|
|
39
|
+
setValues(next);
|
|
40
|
+
if (current.key === 'language')
|
|
41
|
+
setLanguage(value);
|
|
42
|
+
setSettings({
|
|
43
|
+
language: next.language,
|
|
44
|
+
introAnimation: next.introAnimation === 'on',
|
|
45
|
+
});
|
|
46
|
+
setNote(t('Saved.'));
|
|
47
|
+
onChanged();
|
|
48
|
+
};
|
|
49
|
+
useInput((input, key) => {
|
|
50
|
+
if (editing) {
|
|
51
|
+
if (key.escape) {
|
|
52
|
+
setEditing(false);
|
|
53
|
+
}
|
|
54
|
+
else if (key.return) {
|
|
55
|
+
setEditing(false);
|
|
56
|
+
if (draft !== values[current.key])
|
|
57
|
+
commit(draft);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const step = key.leftArrow || key.upArrow
|
|
61
|
+
? -1
|
|
62
|
+
: key.rightArrow || key.downArrow || input === ' '
|
|
63
|
+
? 1
|
|
64
|
+
: 0;
|
|
65
|
+
if (step !== 0) {
|
|
66
|
+
const i = current.options.indexOf(draft);
|
|
67
|
+
const n = current.options.length;
|
|
68
|
+
setDraft(current.options[(i + step + n) % n]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (key.escape)
|
|
74
|
+
onClose();
|
|
75
|
+
else if (key.downArrow || key.tab)
|
|
76
|
+
setCursor(c => (c + 1) % rowCount);
|
|
77
|
+
else if (key.upArrow)
|
|
78
|
+
setCursor(c => (c - 1 + rowCount) % rowCount);
|
|
79
|
+
else if (key.return) {
|
|
80
|
+
if (onWorkspaceRow) {
|
|
81
|
+
onOpenWorkspace?.();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setDraft(values[current.key]);
|
|
85
|
+
setEditing(true);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, width: 64, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Settings') }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", t('stored in {file}', { file: settingsFile() })] })] }), ROWS.map((row, i) => {
|
|
89
|
+
const focused = i === cursor && !onWorkspaceRow;
|
|
90
|
+
const typing = focused && editing;
|
|
91
|
+
const chosen = typing ? draft : values[row.key];
|
|
92
|
+
return (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: focused ? ACCENT : undefined, bold: focused, dimColor: !focused, children: (focused ? '▸ ' : ' ') + t(row.label).padEnd(18) }), _jsx(Text, { children: row.options.map((option, j) => (_jsxs(Text, { children: [_jsx(Text, { bold: option === chosen, color: option === chosen ? ACCENT : undefined, inverse: typing && option === chosen, dimColor: option !== chosen, children: row.display(option) }), j < row.options.length - 1 && _jsx(Text, { dimColor: true, children: " \u00B7 " })] }, option))) })] }, row.key));
|
|
93
|
+
}), onOpenWorkspace && (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: onWorkspaceRow ? ACCENT : undefined, bold: onWorkspaceRow, dimColor: !onWorkspaceRow, children: (onWorkspaceRow ? '▸ ' : ' ') + t('Workspace config').padEnd(18) }), _jsx(Text, { dimColor: true, children: t('shared auth and site config for every app') })] })), _jsx(Text, { color: "yellow", children: note }), _jsx(Text, { dimColor: true, children: editing
|
|
94
|
+
? t('←→ choose · Enter confirm · Esc cancel')
|
|
95
|
+
: t('↑↓ setting · Enter edit · Esc close') })] }));
|
|
96
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProjectInfo } from '../types/index.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
apps: ProjectInfo[];
|
|
4
|
+
workspaceRoot?: string;
|
|
5
|
+
version: string;
|
|
6
|
+
minimal?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function Shell({ apps: initialApps, workspaceRoot, version, minimal, }: Props): import("react").JSX.Element;
|
|
9
|
+
export {};
|