sitevision-cli 1.0.0-beta.16 → 1.0.0-beta.18
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/components/AuthLoginScreen.js +3 -1
- package/dist/shell/ConfigForm.js +9 -3
- package/dist/shell/Frame.d.ts +8 -1
- package/dist/shell/Frame.js +30 -6
- package/dist/shell/Shell.js +105 -58
- package/dist/utils/i18n.js +13 -0
- package/dist/utils/project-detection.d.ts +2 -0
- package/dist/utils/project-detection.js +14 -1
- package/dist/utils/session-cookie-auth.d.ts +1 -0
- package/dist/utils/session-cookie-auth.js +1 -1
- package/dist/utils/workspace.js +4 -1
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ export function AuthLoginScreen({ method, devProperties, onComplete, onError, on
|
|
|
15
15
|
const [phase, setPhase] = React.useState('starting');
|
|
16
16
|
const [authUrl, setAuthUrl] = React.useState('');
|
|
17
17
|
const [note, setNote] = React.useState('');
|
|
18
|
+
const [loginUrl, setLoginUrl] = React.useState('');
|
|
18
19
|
const cookieRef = React.useRef(null);
|
|
19
20
|
const cancelOAuthRef = React.useRef(null);
|
|
20
21
|
React.useEffect(() => {
|
|
@@ -45,6 +46,7 @@ export function AuthLoginScreen({ method, devProperties, onComplete, onError, on
|
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
cookieRef.current = session;
|
|
49
|
+
setLoginUrl(session.loginUrl);
|
|
48
50
|
setPhase('awaiting');
|
|
49
51
|
}
|
|
50
52
|
})();
|
|
@@ -84,5 +86,5 @@ export function AuthLoginScreen({ method, devProperties, onComplete, onError, on
|
|
|
84
86
|
})();
|
|
85
87
|
}
|
|
86
88
|
});
|
|
87
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: method === 'oauth2' ? t('OAuth2 login') : t('Session cookie login') }) }), method === 'oauth2' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", t('Waiting for you to finish login in the browser…')] })] }), authUrl && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: t("If the browser didn't open, visit:") }), _jsx(Text, { children: authUrl })] }))] })) : (_jsxs(Box, { flexDirection: "column", children: [phase === 'starting' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", t('Opening browser…')] })] })), phase === 'awaiting' && (_jsx(Text, { children: t('Log in
|
|
89
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: method === 'oauth2' ? t('OAuth2 login') : t('Session cookie login') }) }), method === 'oauth2' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", t('Waiting for you to finish login in the browser…')] })] }), authUrl && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: t("If the browser didn't open, visit:") }), _jsx(Text, { children: authUrl })] }))] })) : (_jsxs(Box, { flexDirection: "column", children: [phase === 'starting' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", t('Opening browser…')] })] })), phase === 'awaiting' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: t('A Chrome window is open at:') }), _jsx(Text, { color: "cyan", children: loginUrl }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: t('1. Log in to the site there, single sign-on included.') }), _jsx(Text, { children: t('2. Wait until the site itself has finished loading.') }), _jsx(Text, { children: t('3. Come back here and press Enter.') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('Leave the browser window open; it closes once the session is captured.') }) })] })), phase === 'capturing' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", t('Capturing session…')] })] })), note && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: note }) }))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('Press Esc to cancel.') }) })] }));
|
|
88
90
|
}
|
package/dist/shell/ConfigForm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { getPackageJsonSyncChanges, readInheritedDevProperties, writeDevProperties, } from '../utils/project-detection.js';
|
|
4
|
+
import { getPackageJsonSyncChanges, normalizeDomain, readInheritedDevProperties, writeDevProperties, } from '../utils/project-detection.js';
|
|
5
5
|
import { setDeployPassword, deleteDeployPassword, getOAuth2ClientSecret, setOAuth2ClientSecret, deleteOAuth2ClientSecret, getSigningPassword, setSigningPassword, deleteSigningPassword, } from '../utils/keychain.js';
|
|
6
6
|
import { DEFAULT_SCOPES, discoverOAuth2Config } from '../utils/oauth2-auth.js';
|
|
7
7
|
import { ACCENT } from './Frame.js';
|
|
@@ -298,10 +298,16 @@ export function ConfigForm({ project, active, width, height, pickAddon, onSaved,
|
|
|
298
298
|
: [];
|
|
299
299
|
// Write one field to disk (and the keychain for secrets) right away.
|
|
300
300
|
const commit = (key, value, label = current.label) => {
|
|
301
|
-
const
|
|
301
|
+
const clean = key === 'domain' ? normalizeDomain(value) : value;
|
|
302
|
+
const next = { ...values, [key]: clean };
|
|
302
303
|
setValues(next);
|
|
303
304
|
saveConfig(project, next, new Set([key]));
|
|
304
|
-
setNote(
|
|
305
|
+
setNote(clean === value
|
|
306
|
+
? t('Saved {label}.', { label: t(label) })
|
|
307
|
+
: t('Saved {label} as {value} — a domain is a host only.', {
|
|
308
|
+
label: t(label),
|
|
309
|
+
value: clean,
|
|
310
|
+
}));
|
|
305
311
|
onSaved();
|
|
306
312
|
};
|
|
307
313
|
// Auto-fill OAuth2 endpoints from the site's OpenID configuration.
|
package/dist/shell/Frame.d.ts
CHANGED
|
@@ -28,6 +28,12 @@ export declare function appStatus(project: ProjectInfo): {
|
|
|
28
28
|
signing: boolean;
|
|
29
29
|
scriptsWarning: string | undefined;
|
|
30
30
|
};
|
|
31
|
+
/** The name shown for an app in the navigator, and what the filter matches. */
|
|
32
|
+
export declare function appLabel(app: ProjectInfo): string;
|
|
33
|
+
/** Indices into `apps` whose label fuzzy-matches the filter. */
|
|
34
|
+
export declare function navMatches(apps: ProjectInfo[], filter: string): number[];
|
|
35
|
+
/** Next selectable index when moving by `delta`, wrapping at both ends. */
|
|
36
|
+
export declare function navMove(ring: number[], selected: number, delta: number): number;
|
|
31
37
|
export interface NavigatorProps {
|
|
32
38
|
apps: ProjectInfo[];
|
|
33
39
|
groupOf: (app: ProjectInfo) => string;
|
|
@@ -38,8 +44,9 @@ export interface NavigatorProps {
|
|
|
38
44
|
single: boolean;
|
|
39
45
|
settingsSelected?: boolean;
|
|
40
46
|
width: number;
|
|
47
|
+
filter?: string;
|
|
41
48
|
}
|
|
42
|
-
export declare function Navigator({ apps, groupOf, selected, focused, tasks, height, single, settingsSelected, width, }: NavigatorProps): import("react").JSX.Element;
|
|
49
|
+
export declare function Navigator({ apps, groupOf, selected, focused, tasks, height, single, settingsSelected, width, filter, }: NavigatorProps): import("react").JSX.Element;
|
|
43
50
|
export declare function NavigatorStrip({ apps, selected, focused, }: Pick<NavigatorProps, 'apps' | 'selected' | 'focused'>): import("react").JSX.Element;
|
|
44
51
|
export declare function elapsed(task: Task): string;
|
|
45
52
|
export interface Hint {
|
package/dist/shell/Frame.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import Spinner from 'ink-spinner';
|
|
4
4
|
import { appTypeOf, getPackageJsonSyncChanges, localizedText, } from '../utils/project-detection.js';
|
|
5
|
+
import { fuzzyMatch } from './actions.js';
|
|
5
6
|
import { t } from '../utils/i18n.js';
|
|
6
7
|
export const ACCENT = 'cyan';
|
|
7
8
|
export const NARROW_BELOW = 100;
|
|
@@ -40,7 +41,24 @@ function Dots({ project }) {
|
|
|
40
41
|
const dot = (ok, warn = false) => (_jsx(Text, { color: ok ? 'green' : warn ? 'yellow' : 'red', children: "\u25CF" }));
|
|
41
42
|
return (_jsxs(Text, { children: [dot(s.deps), dot(s.config), dot(s.sync === 0, s.sync > 0), dot(s.signing, !s.signing)] }));
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
/** The name shown for an app in the navigator, and what the filter matches. */
|
|
45
|
+
export function appLabel(app) {
|
|
46
|
+
return localizedText(app.manifest.name) || app.manifest.id;
|
|
47
|
+
}
|
|
48
|
+
/** Indices into `apps` whose label fuzzy-matches the filter. */
|
|
49
|
+
export function navMatches(apps, filter) {
|
|
50
|
+
return apps
|
|
51
|
+
.map((_, index) => index)
|
|
52
|
+
.filter(index => fuzzyMatch(filter, appLabel(apps[index])));
|
|
53
|
+
}
|
|
54
|
+
/** Next selectable index when moving by `delta`, wrapping at both ends. */
|
|
55
|
+
export function navMove(ring, selected, delta) {
|
|
56
|
+
if (ring.length === 0)
|
|
57
|
+
return selected;
|
|
58
|
+
const at = ring.indexOf(selected);
|
|
59
|
+
return ring[at === -1 ? 0 : (at + delta + ring.length) % ring.length];
|
|
60
|
+
}
|
|
61
|
+
export function Navigator({ apps, groupOf, selected, focused, tasks, height, single, settingsSelected = false, width, filter = '', }) {
|
|
44
62
|
// Row: marker(1) glyph(3) sp name sp version(6) sp dots(4) inside the padding.
|
|
45
63
|
const nameWidth = width - 2 - 17;
|
|
46
64
|
const running = tasks.filter(task => task.status === 'running');
|
|
@@ -51,16 +69,20 @@ export function Navigator({ apps, groupOf, selected, focused, tasks, height, sin
|
|
|
51
69
|
for (const [index, app] of apps.entries()) {
|
|
52
70
|
const group = groupOf(app);
|
|
53
71
|
if (!single && group !== lastGroup) {
|
|
54
|
-
rows.push(_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', group] }) }, `g-${group}`));
|
|
72
|
+
rows.push(_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', group] }) }, `g-${index}-${group}`));
|
|
55
73
|
rowApp.push(-1);
|
|
56
74
|
lastGroup = group;
|
|
57
75
|
}
|
|
58
76
|
rowApp.push(index);
|
|
59
77
|
const active = index === selected;
|
|
60
78
|
const busy = running.some(task => task.appRoot === app.root);
|
|
61
|
-
const name =
|
|
79
|
+
const name = appLabel(app);
|
|
62
80
|
rows.push(_jsxs(Box, { width: width - 2, 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));
|
|
63
81
|
}
|
|
82
|
+
if (rows.length === 0) {
|
|
83
|
+
rows.push(_jsx(Box, { height: 1, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: ' ' + t('no matches') }) }, "none"));
|
|
84
|
+
rowApp.push(-1);
|
|
85
|
+
}
|
|
64
86
|
// Window the list so the selected app stays visible; the lines outside
|
|
65
87
|
// are summarised as "… n more".
|
|
66
88
|
const fixed = 1 + 1 + (single ? 0 : 2) + (running.length > 0 ? running.length + 2 : 0);
|
|
@@ -79,14 +101,16 @@ export function Navigator({ apps, groupOf, selected, focused, tasks, height, sin
|
|
|
79
101
|
if (end < rows.length)
|
|
80
102
|
shown[shown.length - 1] = more(rows.length - end, '↓');
|
|
81
103
|
}
|
|
82
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderDimColor: true, borderTop: false, borderBottom: false, borderLeft: false, paddingX: 1, overflow: "hidden", children: [_jsx(Text, {
|
|
104
|
+
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
|
|
105
|
+
? t('1 match')
|
|
106
|
+
: t('{n} matches', { n: apps.length })] })] })) : (_jsx(Text, { bold: true, dimColor: true, children: single
|
|
83
107
|
? t('APP')
|
|
84
108
|
: apps.length === 1
|
|
85
109
|
? t('WORKSPACE 1 app')
|
|
86
|
-
: 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
|
+
: 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)))] }))] }));
|
|
87
111
|
}
|
|
88
112
|
export function NavigatorStrip({ apps, selected, focused, }) {
|
|
89
|
-
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { wrap: "truncate", children: apps.map((app, index) => (_jsxs(Text, { backgroundColor: index === selected && focused ? ACCENT : undefined, color: index === selected && focused ? 'black' : undefined, bold: index === selected, children: [' ', typeGlyph(app.manifest),
|
|
113
|
+
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { wrap: "truncate", children: apps.map((app, index) => (_jsxs(Text, { backgroundColor: index === selected && focused ? ACCENT : undefined, color: index === selected && focused ? 'black' : undefined, bold: index === selected, children: [' ', typeGlyph(app.manifest), " ", appLabel(app), ' '] }, app.root))) }) }));
|
|
90
114
|
}
|
|
91
115
|
export function elapsed(task) {
|
|
92
116
|
const ms = (task.endedAt ?? Date.now()) - task.startedAt;
|
package/dist/shell/Shell.js
CHANGED
|
@@ -9,7 +9,7 @@ import { listAddons, listExecutables, } from '../utils/sitevision-api.js';
|
|
|
9
9
|
import { useTasks, runningTasks, startActivate, getTasks, } from '../utils/tasks.js';
|
|
10
10
|
import { PasswordInput } from '../components/PasswordInput.js';
|
|
11
11
|
import { AuthLoginScreen } from '../components/AuthLoginScreen.js';
|
|
12
|
-
import { TopBar, Navigator, NavigatorStrip, BottomBar, navWidth, NARROW_BELOW, ACCENT, } from './Frame.js';
|
|
12
|
+
import { TopBar, Navigator, NavigatorStrip, BottomBar, navMatches, navMove, appLabel, navWidth, NARROW_BELOW, ACCENT, } from './Frame.js';
|
|
13
13
|
import { TabBar, TABS, Overview, Versions, Log, } from './Tabs.js';
|
|
14
14
|
import { CommandPalette } from './CommandPalette.js';
|
|
15
15
|
import { ConfigForm } from './ConfigForm.js';
|
|
@@ -43,6 +43,7 @@ export function Shell({ apps: initialApps, workspaceRoot, version }) {
|
|
|
43
43
|
const [tab, setTab] = useState('overview');
|
|
44
44
|
const [focus, setFocus] = useState(workspaceRoot && !onboard ? 'nav' : 'content');
|
|
45
45
|
const [overlay, setOverlay] = useState(null);
|
|
46
|
+
const [filter, setFilter] = useState('');
|
|
46
47
|
const [versions, setVersions] = useState({});
|
|
47
48
|
const [versionRow, setVersionRow] = useState(0);
|
|
48
49
|
const [logScroll, setLogScroll] = useState(0);
|
|
@@ -78,6 +79,10 @@ export function Shell({ apps: initialApps, workspaceRoot, version }) {
|
|
|
78
79
|
[workspaceRoot, apps, env]);
|
|
79
80
|
const narrow = columns < NARROW_BELOW;
|
|
80
81
|
const sidebar = navWidth(columns);
|
|
82
|
+
// The navigator shows the fuzzy matches; `selected` stays an index into
|
|
83
|
+
// `apps` (with `apps.length` meaning the workspace settings row).
|
|
84
|
+
const matches = useMemo(() => navMatches(apps, filter), [apps, filter]);
|
|
85
|
+
const ring = single ? matches : [...matches, apps.length];
|
|
81
86
|
const running = runningTasks();
|
|
82
87
|
// Re-render once a second while something runs so elapsed times move.
|
|
83
88
|
useEffect(() => {
|
|
@@ -254,6 +259,10 @@ export function Shell({ apps: initialApps, workspaceRoot, version }) {
|
|
|
254
259
|
? raw.toUpperCase()
|
|
255
260
|
: raw;
|
|
256
261
|
if (key.escape) {
|
|
262
|
+
if (focus === 'nav' && filter) {
|
|
263
|
+
setFilter('');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
257
266
|
// Esc backs out of the workspace settings pane, not just its focus.
|
|
258
267
|
if (settings)
|
|
259
268
|
setSelected(0);
|
|
@@ -265,9 +274,40 @@ export function Shell({ apps: initialApps, workspaceRoot, version }) {
|
|
|
265
274
|
return;
|
|
266
275
|
}
|
|
267
276
|
if (key.tab) {
|
|
277
|
+
setFilter('');
|
|
268
278
|
setFocus(f => f === 'nav' && !single ? 'content' : single ? 'content' : 'nav');
|
|
269
279
|
return;
|
|
270
280
|
}
|
|
281
|
+
// Navigator: typing searches, so no action key fires until Enter has
|
|
282
|
+
// moved the focus into the content pane.
|
|
283
|
+
if (focus === 'nav') {
|
|
284
|
+
const move = (delta) => {
|
|
285
|
+
setSelected(navMove(ring, selected, delta));
|
|
286
|
+
};
|
|
287
|
+
const search = (next) => {
|
|
288
|
+
setFilter(next);
|
|
289
|
+
const found = navMatches(apps, next);
|
|
290
|
+
if (found.length > 0 && !found.includes(selected))
|
|
291
|
+
setSelected(found[0]);
|
|
292
|
+
};
|
|
293
|
+
if (key.upArrow)
|
|
294
|
+
move(-1);
|
|
295
|
+
else if (key.downArrow)
|
|
296
|
+
move(1);
|
|
297
|
+
else if (key.return) {
|
|
298
|
+
if (matches.length > 0 || settings) {
|
|
299
|
+
setFilter('');
|
|
300
|
+
setFocus('content');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else if (key.backspace || key.delete)
|
|
304
|
+
search(filter.slice(0, -1));
|
|
305
|
+
else if (input === 'q' && !filter)
|
|
306
|
+
quit();
|
|
307
|
+
else if (input?.length === 1 && input >= ' ' && !key.ctrl && !key.meta)
|
|
308
|
+
search(filter + input);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
271
311
|
if (input === 'a' && tab !== 'versions') {
|
|
272
312
|
setTab('versions');
|
|
273
313
|
setFocus('content');
|
|
@@ -292,16 +332,7 @@ export function Shell({ apps: initialApps, workspaceRoot, version }) {
|
|
|
292
332
|
quit();
|
|
293
333
|
return;
|
|
294
334
|
}
|
|
295
|
-
if (
|
|
296
|
-
const last = single ? apps.length - 1 : apps.length;
|
|
297
|
-
if (key.upArrow)
|
|
298
|
-
setSelected(s => (s > 0 ? s - 1 : last));
|
|
299
|
-
if (key.downArrow)
|
|
300
|
-
setSelected(s => (s < last ? s + 1 : 0));
|
|
301
|
-
if (key.return)
|
|
302
|
-
setFocus('content');
|
|
303
|
-
}
|
|
304
|
-
else if (tab === 'versions') {
|
|
335
|
+
if (tab === 'versions') {
|
|
305
336
|
const count = versions[versionsKey]?.executables?.length ?? 0;
|
|
306
337
|
if (key.upArrow)
|
|
307
338
|
setVersionRow(r => Math.max(0, r - 1));
|
|
@@ -336,7 +367,7 @@ export function Shell({ apps: initialApps, workspaceRoot, version }) {
|
|
|
336
367
|
const mainHeight = frameRows - 2;
|
|
337
368
|
const contentHeight = mainHeight - 1 - (narrow ? 1 : 0);
|
|
338
369
|
const groupOf = (app) => workspaceRoot ? appGroup(workspaceRoot, app.root) : '.';
|
|
339
|
-
const appName =
|
|
370
|
+
const appName = appLabel(project);
|
|
340
371
|
const tabName = t(TABS.find(entry => entry.id === tab).label).toLowerCase();
|
|
341
372
|
const contextLabel = settings
|
|
342
373
|
? `${t('workspace')} ▸ ${t('settings')}`
|
|
@@ -361,61 +392,77 @@ export function Shell({ apps: initialApps, workspaceRoot, version }) {
|
|
|
361
392
|
['↑↓', 'apps'],
|
|
362
393
|
['q', 'quit'],
|
|
363
394
|
]);
|
|
395
|
+
const navHints = filter
|
|
396
|
+
? h([
|
|
397
|
+
['↑↓', 'move'],
|
|
398
|
+
['Enter', 'select'],
|
|
399
|
+
['Esc', 'clear'],
|
|
400
|
+
['/', 'commands'],
|
|
401
|
+
])
|
|
402
|
+
: h([
|
|
403
|
+
['a–z', 'search'],
|
|
404
|
+
['↑↓', 'move'],
|
|
405
|
+
['Enter', 'select'],
|
|
406
|
+
['/', 'commands'],
|
|
407
|
+
['q', 'quit'],
|
|
408
|
+
]);
|
|
364
409
|
const hints = overlay
|
|
365
410
|
? h([['Esc', 'cancel']])
|
|
366
|
-
: settings
|
|
367
|
-
?
|
|
368
|
-
:
|
|
369
|
-
?
|
|
370
|
-
|
|
371
|
-
['R', 'refresh'],
|
|
372
|
-
['p', 'deploy'],
|
|
373
|
-
['/', 'commands'],
|
|
374
|
-
['q', 'quit'],
|
|
375
|
-
])
|
|
376
|
-
: tab === 'log'
|
|
411
|
+
: focus === 'nav' && !settings
|
|
412
|
+
? navHints
|
|
413
|
+
: settings
|
|
414
|
+
? settingsHints
|
|
415
|
+
: tab === 'versions'
|
|
377
416
|
? h([
|
|
378
|
-
['
|
|
379
|
-
['
|
|
380
|
-
['K', 'stop'],
|
|
417
|
+
['a', 'activate'],
|
|
418
|
+
['R', 'refresh'],
|
|
381
419
|
['p', 'deploy'],
|
|
382
420
|
['/', 'commands'],
|
|
383
421
|
['q', 'quit'],
|
|
384
422
|
])
|
|
385
|
-
: tab === '
|
|
386
|
-
?
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
])
|
|
391
|
-
: formActive
|
|
392
|
-
? h([
|
|
393
|
-
['↑↓', 'field'],
|
|
394
|
-
['Enter', 'edit'],
|
|
395
|
-
['^O', 'pick addon'],
|
|
396
|
-
['y', 'sync'],
|
|
397
|
-
['/', 'commands'],
|
|
398
|
-
['q', 'quit'],
|
|
399
|
-
])
|
|
400
|
-
: h([
|
|
401
|
-
['Tab', 'edit'],
|
|
402
|
-
['y', 'sync'],
|
|
403
|
-
['l', 'login'],
|
|
404
|
-
['/', 'commands'],
|
|
405
|
-
['q', 'quit'],
|
|
406
|
-
])
|
|
407
|
-
: h([
|
|
408
|
-
['d', 'dev'],
|
|
409
|
-
['w', 'watch'],
|
|
410
|
-
['b', 'build'],
|
|
411
|
-
['s', 'sign'],
|
|
423
|
+
: tab === 'log'
|
|
424
|
+
? h([
|
|
425
|
+
['f', 'follow'],
|
|
426
|
+
['x', 'wrap'],
|
|
427
|
+
['K', 'stop'],
|
|
412
428
|
['p', 'deploy'],
|
|
413
|
-
['a', 'activate'],
|
|
414
|
-
['E', 'env'],
|
|
415
|
-
['i', 'install'],
|
|
416
429
|
['/', 'commands'],
|
|
417
430
|
['q', 'quit'],
|
|
418
|
-
])
|
|
431
|
+
])
|
|
432
|
+
: tab === 'config'
|
|
433
|
+
? editing
|
|
434
|
+
? h([
|
|
435
|
+
['Enter', 'save'],
|
|
436
|
+
['Esc', 'cancel'],
|
|
437
|
+
])
|
|
438
|
+
: formActive
|
|
439
|
+
? h([
|
|
440
|
+
['↑↓', 'field'],
|
|
441
|
+
['Enter', 'edit'],
|
|
442
|
+
['^O', 'pick addon'],
|
|
443
|
+
['y', 'sync'],
|
|
444
|
+
['/', 'commands'],
|
|
445
|
+
['q', 'quit'],
|
|
446
|
+
])
|
|
447
|
+
: h([
|
|
448
|
+
['Tab', 'edit'],
|
|
449
|
+
['y', 'sync'],
|
|
450
|
+
['l', 'login'],
|
|
451
|
+
['/', 'commands'],
|
|
452
|
+
['q', 'quit'],
|
|
453
|
+
])
|
|
454
|
+
: h([
|
|
455
|
+
['d', 'dev'],
|
|
456
|
+
['w', 'watch'],
|
|
457
|
+
['b', 'build'],
|
|
458
|
+
['s', 'sign'],
|
|
459
|
+
['p', 'deploy'],
|
|
460
|
+
['a', 'activate'],
|
|
461
|
+
['E', 'env'],
|
|
462
|
+
['i', 'install'],
|
|
463
|
+
['/', 'commands'],
|
|
464
|
+
['q', 'quit'],
|
|
465
|
+
]);
|
|
419
466
|
const right = running.length > 0 ? (_jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: _jsx(Spinner, { type: "dots" }) }), ' ', running[0].label, " ", running[0].appName, running.length > 1 && (_jsxs(Text, { dimColor: true, children: [" \u00B7 ", t('{n} tasks', { n: running.length })] }))] })) : notice ? (_jsx(Text, { color: { info: undefined, ok: 'green', warn: 'yellow', error: 'red' }[notice.level], wrap: "truncate", children: notice.text })) : (_jsx(Text, { dimColor: true, children: t('idle') }));
|
|
420
467
|
const closeOverlay = () => setOverlay(null);
|
|
421
468
|
const content = overlay ? (renderOverlay(overlay, {
|
|
@@ -448,7 +495,7 @@ export function Shell({ apps: initialApps, workspaceRoot, version }) {
|
|
|
448
495
|
return (_jsxs(Box, { flexDirection: "column", width: columns, height: frameRows, children: [_jsx(TopBar, { context: contextLabel, domain: project.devProperties?.domain, auth: authState(project), environment: {
|
|
449
496
|
name: env,
|
|
450
497
|
color: environmentColor(env, rawProject.devProperties),
|
|
451
|
-
}, version: version }), _jsxs(Box, { flexGrow: 1, height: mainHeight, borderStyle: "single", borderDimColor: true, borderLeft: false, borderRight: false, borderBottom: false, children: [!narrow && (_jsx(Navigator, { apps: apps, groupOf: groupOf, selected: selected, focused: focus === 'nav', tasks: tasks, height: mainHeight - 1, single: single, settingsSelected: settings, width: sidebar })), _jsxs(Box, { flexDirection: "column", width: narrow ? columns : columns - sidebar, overflow: "hidden", children: [narrow && !single && (_jsx(NavigatorStrip, { apps: apps, selected: selected, focused: focus === 'nav' })), settings ? (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { bold: true, color: ACCENT, children: t('Workspace settings') }), _jsx(Text, { dimColor: true, children: onboard && configIncomplete(workspaceTarget?.base)
|
|
498
|
+
}, version: version }), _jsxs(Box, { flexGrow: 1, height: mainHeight, borderStyle: "single", borderDimColor: true, borderLeft: false, borderRight: false, borderBottom: false, children: [!narrow && (_jsx(Navigator, { apps: matches.map(i => apps[i]), groupOf: groupOf, selected: matches.indexOf(selected), focused: focus === 'nav', tasks: tasks, height: mainHeight - 1, single: single, settingsSelected: settings, width: sidebar, filter: filter })), _jsxs(Box, { flexDirection: "column", width: narrow ? columns : columns - sidebar, overflow: "hidden", children: [narrow && !single && (_jsx(NavigatorStrip, { apps: matches.map(i => apps[i]), selected: matches.indexOf(selected), focused: focus === 'nav' })), settings ? (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { bold: true, color: ACCENT, children: t('Workspace settings') }), _jsx(Text, { dimColor: true, children: onboard && configIncomplete(workspaceTarget?.base)
|
|
452
499
|
? t(' · new workspace: fill in once, every app inherits · Esc skips')
|
|
453
500
|
: t(' · shared .dev_properties.json at the root') })] })) : (_jsx(TabBar, { tab: tab, narrow: narrow, focused: focus === 'content' })), _jsx(Box, { height: contentHeight, overflow: "hidden", alignItems: "flex-start", children: content })] })] }), _jsx(BottomBar, { hints: hints, right: right })] }));
|
|
454
501
|
}
|
package/dist/utils/i18n.js
CHANGED
|
@@ -203,6 +203,13 @@ const sv = {
|
|
|
203
203
|
edit: 'redigera',
|
|
204
204
|
back: 'tillbaka',
|
|
205
205
|
quit: 'avsluta',
|
|
206
|
+
search: 'sök',
|
|
207
|
+
move: 'flytta',
|
|
208
|
+
select: 'välj',
|
|
209
|
+
clear: 'rensa',
|
|
210
|
+
'no matches': 'inga träffar',
|
|
211
|
+
'{n} matches': '{n} träffar',
|
|
212
|
+
'1 match': '1 träff',
|
|
206
213
|
'edit settings': 'redigera inställningar',
|
|
207
214
|
apps: 'appar',
|
|
208
215
|
activate: 'aktivera',
|
|
@@ -223,6 +230,7 @@ const sv = {
|
|
|
223
230
|
idle: 'inaktiv',
|
|
224
231
|
'workspace config saved': 'arbetsytans konfig sparad',
|
|
225
232
|
'config saved': 'konfig sparad',
|
|
233
|
+
'Saved {label} as {value} — a domain is a host only.': 'Sparade {label} som {value} — en domän är enbart ett värdnamn.',
|
|
226
234
|
' · shared .dev_properties.json at the root': ' · delad .dev_properties.json i roten',
|
|
227
235
|
' · new workspace: fill in once, every app inherits · Esc skips': ' · ny arbetsyta: fyll i en gång, alla appar ärver · Esc hoppar över',
|
|
228
236
|
'y confirm · n cancel': 'y bekräfta · n avbryt',
|
|
@@ -260,5 +268,10 @@ const sv = {
|
|
|
260
268
|
'Opening browser…': 'Öppnar webbläsare…',
|
|
261
269
|
'Log in in the opened browser, then press Enter to capture the session.': 'Logga in i webbläsaren och tryck sedan Enter för att fånga sessionen.',
|
|
262
270
|
'Capturing session…': 'Fångar session…',
|
|
271
|
+
'A Chrome window is open at:': 'Ett Chrome-fönster är öppet på:',
|
|
272
|
+
'1. Log in to the site there, single sign-on included.': '1. Logga in på webbplatsen där, även med enkel inloggning.',
|
|
273
|
+
'2. Wait until the site itself has finished loading.': '2. Vänta tills själva webbplatsen har laddat klart.',
|
|
274
|
+
'3. Come back here and press Enter.': '3. Kom tillbaka hit och tryck Enter.',
|
|
275
|
+
'Leave the browser window open; it closes once the session is captured.': 'Låt webbläsarfönstret vara öppet; det stängs när sessionen har hämtats.',
|
|
263
276
|
'Press Esc to cancel.': 'Tryck Esc för att avbryta.',
|
|
264
277
|
};
|
|
@@ -22,6 +22,8 @@ export declare function findDevPropertiesPath(root: string): string | null;
|
|
|
22
22
|
* Get the default dev properties path (for creating new files)
|
|
23
23
|
*/
|
|
24
24
|
export declare function getDefaultDevPropertiesPath(root: string): string;
|
|
25
|
+
/** A bare host: no scheme, no path, no stray spaces. */
|
|
26
|
+
export declare function normalizeDomain(value: string): string;
|
|
25
27
|
/**
|
|
26
28
|
* The dev properties a workspace root defines (its own file merged over any
|
|
27
29
|
* ancestors'), with the deploy password resolved from the keychain like an
|
|
@@ -81,12 +81,25 @@ function ancestorDirs(root) {
|
|
|
81
81
|
}
|
|
82
82
|
return dirs;
|
|
83
83
|
}
|
|
84
|
+
/** A bare host: no scheme, no path, no stray spaces. */
|
|
85
|
+
export function normalizeDomain(value) {
|
|
86
|
+
const host = value
|
|
87
|
+
.trim()
|
|
88
|
+
.replace(/^[a-z]+:\/\//i, '')
|
|
89
|
+
.replaceAll(/\s/g, '');
|
|
90
|
+
const slash = host.indexOf('/');
|
|
91
|
+
return slash === -1 ? host : host.slice(0, slash);
|
|
92
|
+
}
|
|
84
93
|
function readDevPropertiesFile(dir) {
|
|
85
94
|
const file = findDevPropertiesPath(dir);
|
|
86
95
|
if (!file)
|
|
87
96
|
return null;
|
|
88
97
|
try {
|
|
89
|
-
|
|
98
|
+
const dev = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
99
|
+
// ponytail: top level only; a hand-edited environment override keeps its
|
|
100
|
+
// scheme until it is saved from the config form.
|
|
101
|
+
dev.domain &&= normalizeDomain(dev.domain);
|
|
102
|
+
return dev;
|
|
90
103
|
}
|
|
91
104
|
catch {
|
|
92
105
|
return null;
|
package/dist/utils/workspace.js
CHANGED
|
@@ -39,7 +39,10 @@ export function discoverApps(root) {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
walk(root, 1);
|
|
42
|
-
|
|
42
|
+
// Group first, then path: a deeper folder (webapps/nested) must not split
|
|
43
|
+
// its parent's run of apps, or the same group heading renders twice.
|
|
44
|
+
return found.toSorted((a, b) => path.dirname(a.root).localeCompare(path.dirname(b.root)) ||
|
|
45
|
+
a.root.localeCompare(b.root));
|
|
43
46
|
}
|
|
44
47
|
/** Group label for an app: its parent folder relative to the workspace root. */
|
|
45
48
|
export function appGroup(root, appRoot) {
|