sitevision-cli 1.0.0-beta.26 → 1.0.0-beta.27

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/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0-beta.25
4
+
5
+ - Deploying to an addon that does not exist yet now offers to create it and
6
+ deploy again. If the addon list cannot be read, the error suggests logging in
7
+ again instead, since an expired session fails the same way.
8
+ - A Create addon action appears in the command palette when a deploy has found
9
+ the addon missing.
10
+ - Creating a RESTApp or MCPServer addon no longer sends a category, which only
11
+ WebApp and widget addons use.
12
+
13
+ ## 1.0.0-beta.24
14
+
15
+ - Deploying to a non-production environment uploads the signed zip when it is at
16
+ least as new as the build. The log names the zip that was uploaded.
17
+ - `x` in the Log tab wraps long lines immediately, and wrapped lines no longer
18
+ push the newest line out of view.
19
+ - Keys: switching environment moved from `E` to `v`, and refreshing versions
20
+ from `R` to `r`. Tab in the Config form moves between fields without leaving
21
+ the form.
22
+ - The bottom bar now shows force deploy (`P`), stop, config, login and settings.
23
+ - Running commands are stopped when `svc` exits.
@@ -47,7 +47,7 @@ export function AddonPicker({ domain, appType, initialQuery, load, onSelect, onC
47
47
  });
48
48
  const visible = Math.max(3, height - 6);
49
49
  const start = Math.max(0, Math.min(index - visible + 1, matches.length - visible));
50
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, width: 64, height: Math.min(height, matches.length + 6), children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Addon Repository') }), _jsxs(Text, { dimColor: true, children: [" ", domain] })] }), _jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), query, _jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { dimColor: true, children: [' ', addons ? t('{n} of {m}', { n: matches.length, m: addons.length }) : ''] })] }), !addons && !error && (_jsxs(Text, { color: ACCENT, children: [_jsx(Spinner, { type: "dots" }), " ", _jsx(Text, { dimColor: true, children: t('fetching addons') })] })), error && _jsx(Text, { color: "red", children: error }), matches.slice(start, start + visible).map((a, i) => {
50
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Addon Repository') }), _jsxs(Text, { dimColor: true, children: [" ", domain] })] }), _jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), query, _jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { dimColor: true, children: [' ', addons ? t('{n} of {m}', { n: matches.length, m: addons.length }) : ''] })] }), !addons && !error && (_jsxs(Text, { color: ACCENT, children: [_jsx(Spinner, { type: "dots" }), " ", _jsx(Text, { dimColor: true, children: t('fetching addons') })] })), error && _jsx(Text, { color: "red", children: error }), matches.slice(start, start + visible).map((a, i) => {
51
51
  const selected = start + i === index;
52
52
  return (_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Box, { flexShrink: 1, marginRight: 1, children: _jsxs(Text, { backgroundColor: selected ? ACCENT : undefined, color: selected ? 'black' : undefined, wrap: "truncate", children: [' ', a.name] }) }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, children: a.appType ?? a.type }) })] }, a.id));
53
53
  }), _jsx(Text, { dimColor: true, children: t('↑↓ move · Enter select · Esc cancel') })] }));
@@ -0,0 +1,6 @@
1
+ /** CHANGELOG.md without its title, or undefined when it isn't shipped. */
2
+ export declare function readChangelog(): string[] | undefined;
3
+ export declare function ChangelogPanel({ height, onClose, }: {
4
+ height: number;
5
+ onClose: () => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { readFileSync } from 'node:fs';
3
+ import { useMemo, useState } from 'react';
4
+ import { Box, Text, useInput } from 'ink';
5
+ import { t } from '../utils/i18n.js';
6
+ import { ACCENT } from './Frame.js';
7
+ /** CHANGELOG.md without its title, or undefined when it isn't shipped. */
8
+ export function readChangelog() {
9
+ try {
10
+ return readFileSync(new URL('../../CHANGELOG.md', import.meta.url), 'utf8')
11
+ .replace(/^# .*\n/, '')
12
+ .trim()
13
+ .split('\n');
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ export function ChangelogPanel({ height, onClose, }) {
20
+ const lines = useMemo(() => readChangelog(), []);
21
+ const [top, setTop] = useState(0);
22
+ const visible = Math.max(1, height - 1);
23
+ const max = Math.max(0, (lines?.length ?? 0) - visible);
24
+ useInput((input, key) => {
25
+ if (key.escape || key.return || input === 'q')
26
+ onClose();
27
+ else if (key.upArrow)
28
+ setTop(n => Math.max(0, n - 1));
29
+ else if (key.downArrow)
30
+ setTop(n => Math.min(max, n + 1));
31
+ else if (key.pageUp)
32
+ setTop(n => Math.max(0, n - visible));
33
+ else if (key.pageDown)
34
+ setTop(n => Math.min(max, n + visible));
35
+ });
36
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Changelog') }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", t('↑↓ scroll · Esc close')] })] }), lines ? (lines.slice(top, top + visible).map((line, i) => line.startsWith('## ') ? (_jsx(Text, { bold: true, color: ACCENT, children: line.slice(3) }, top + i)) : (_jsx(Text, { wrap: "truncate", children: line || ' ' }, top + i)))) : (_jsx(Text, { dimColor: true, children: t('No changelog found.') }))] }));
37
+ }
@@ -1,5 +1,9 @@
1
1
  import type { ProjectInfo } from '../types/index.js';
2
2
  import { type Action } from './actions.js';
3
+ export declare const GROUPS: {
4
+ id: Action['group'];
5
+ label: string;
6
+ }[];
3
7
  export declare function CommandPalette({ project, onRun, onClose, height, }: {
4
8
  project: ProjectInfo;
5
9
  onRun: (action: Action) => void;
@@ -4,7 +4,7 @@ import { Box, Text, useInput } from 'ink';
4
4
  import { ACCENT } from './Frame.js';
5
5
  import { actions, fuzzyMatch } from './actions.js';
6
6
  import { t } from '../utils/i18n.js';
7
- const GROUPS = [
7
+ export const GROUPS = [
8
8
  { id: 'app', label: 'APP' },
9
9
  { id: 'setup', label: 'SETUP' },
10
10
  { id: 'auth', label: 'AUTH' },
@@ -59,5 +59,5 @@ export function CommandPalette({ project, onRun, onClose, height, }) {
59
59
  const detail = action.detail?.(project);
60
60
  rows.push(_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Box, { flexShrink: 1, marginRight: 1, children: _jsxs(Text, { backgroundColor: i === index ? ACCENT : undefined, color: i === index ? 'black' : undefined, dimColor: !enabled && i !== index, wrap: "truncate", children: [' ', t(action.label), detail && _jsxs(Text, { dimColor: i !== index, children: [" \u00B7 ", detail] })] }) }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: ACCENT, children: action.key ?? '—' }) })] }, action.id));
61
61
  }
62
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, width: 64, height: rows.length + 5, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Commands') }), _jsxs(Text, { dimColor: true, children: [" ", project.manifest.id] })] }), _jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), query, _jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { dimColor: true, children: [" ", t('{n} actions', { n: ordered.length })] })] }), rows, _jsx(Text, { dimColor: true, children: t('type to filter · ↑↓ move · Enter run · Esc close') })] }));
62
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Commands') }), _jsxs(Text, { dimColor: true, children: [" ", project.manifest.id] })] }), _jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), query, _jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { dimColor: true, children: [" ", t('{n} actions', { n: ordered.length })] })] }), rows, _jsx(Text, { dimColor: true, children: t('type to filter · ↑↓ move · Enter run · Esc close') })] }));
63
63
  }
@@ -0,0 +1,6 @@
1
+ import { type Hint } from './Frame.js';
2
+ export declare function HelpPanel({ here, height, onClose, }: {
3
+ here: Hint[];
4
+ height: number;
5
+ onClose: () => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { t } from '../utils/i18n.js';
4
+ import { ACCENT } from './Frame.js';
5
+ import { actions } from './actions.js';
6
+ import { GROUPS } from './CommandPalette.js';
7
+ const MOVE = [
8
+ ['Tab', 'switch pane'],
9
+ ['1–4', 'tabs'],
10
+ ['←→', 'tabs'],
11
+ ['/', 'commands'],
12
+ ['?', 'help'],
13
+ ['Esc', 'back'],
14
+ ];
15
+ function Row({ keys, label }) {
16
+ return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { bold: true, color: ACCENT, children: keys.padEnd(6) }), label] }));
17
+ }
18
+ export function HelpPanel({ here, height, onClose, }) {
19
+ useInput((input, key) => {
20
+ if (key.escape || key.return || input === '?' || input === 'q')
21
+ onClose();
22
+ });
23
+ // Only keys not already listed elsewhere, e.g. f and x on the Log tab.
24
+ const local = here.filter(hint => actions.every(action => action.key !== hint.key) &&
25
+ MOVE.every(([keys]) => keys !== hint.key));
26
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: height, overflow: "hidden", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Keys') }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", t('Esc close')] })] }), _jsxs(Box, { flexWrap: "wrap", columnGap: 4, children: [_jsx(Box, { flexDirection: "column", marginTop: 1, children: GROUPS.map(group => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: t(group.label) }), actions.flatMap(action => action.group === group.id && action.key
27
+ ? [
28
+ _jsx(Row, { keys: action.key, label: t(action.label) }, action.id),
29
+ ]
30
+ : [])] }, group.id))) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [local.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: t('Here') }), local.map(hint => (_jsx(Row, { keys: hint.key, label: hint.label }, hint.key + hint.label)))] })), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: t('Move around') }), MOVE.map(([keys, label]) => (_jsx(Row, { keys: keys, label: t(label) }, keys + label)))] })] })] })] }));
31
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * A bordered box floating centred over the frame. Render it as the last child
4
+ * of the root so it draws on top; Ink paints in tree order.
5
+ */
6
+ export declare function Popover({ columns, rows, width, height, children, }: {
7
+ columns: number;
8
+ rows: number;
9
+ width: number;
10
+ height: number;
11
+ children: ReactNode;
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { ACCENT } from './Frame.js';
4
+ /**
5
+ * A bordered box floating centred over the frame. Render it as the last child
6
+ * of the root so it draws on top; Ink paints in tree order.
7
+ */
8
+ export function Popover({ columns, rows, width, height, children, }) {
9
+ const w = Math.max(20, Math.min(width, columns - 4));
10
+ const h = Math.max(6, Math.min(height, rows - 2));
11
+ // Ink only overwrites cells it writes to, so blank the interior first or the
12
+ // content underneath shows through around shorter lines.
13
+ const blank = ' '.repeat(w - 2);
14
+ return (_jsxs(Box, { position: "absolute", top: Math.floor((rows - h) / 2), left: Math.floor((columns - w) / 2), width: w, height: h, flexDirection: "column", borderStyle: "round", borderColor: ACCENT, children: [_jsx(Box, { position: "absolute", flexDirection: "column", children: Array.from({ length: h - 2 }, (_, i) => (_jsx(Text, { children: blank }, i))) }), _jsx(Box, { flexDirection: "column", height: h - 2, overflow: "hidden", children: children })] }));
15
+ }
@@ -85,7 +85,7 @@ export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
85
85
  setEditing(true);
86
86
  }
87
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) => {
88
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, 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
89
  const focused = i === cursor && !onWorkspaceRow;
90
90
  const typing = focused && editing;
91
91
  const chosen = typing ? draft : values[row.key];
@@ -15,6 +15,9 @@ import { CommandPalette } from './CommandPalette.js';
15
15
  import { ConfigForm } from './ConfigForm.js';
16
16
  import { AddonPicker } from './AddonPicker.js';
17
17
  import { SettingsScreen } from './Settings.js';
18
+ import { HelpPanel } from './Help.js';
19
+ import { Popover } from './Popover.js';
20
+ import { ChangelogPanel } from './Changelog.js';
18
21
  import { baseEnvironment, environmentColor, environmentNames, environmentProject, isProductionEnvironment, resolveEnvironment, } from '../utils/environments.js';
19
22
  import { t } from '../utils/i18n.js';
20
23
  import { actionForKey, authState, resolveDeployConfig, } from './actions.js';
@@ -129,6 +132,9 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
129
132
  openSettings() {
130
133
  setOverlay({ kind: 'settings' });
131
134
  },
135
+ openChangelog() {
136
+ setOverlay({ kind: 'changelog' });
137
+ },
132
138
  environment: env,
133
139
  isProduction,
134
140
  cycleEnvironment() {
@@ -183,6 +189,9 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
183
189
  confirm: message => new Promise(resolve => {
184
190
  setOverlay({ kind: 'confirm', message, resolve });
185
191
  }),
192
+ openHelp() {
193
+ setOverlay({ kind: 'help' });
194
+ },
186
195
  }),
187
196
  // eslint-disable-next-line react-hooks/exhaustive-deps
188
197
  [project, reload, notify, quit, workspaceRoot, apps.length, env, envList]);
@@ -237,7 +246,8 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
237
246
  notify(task.error ?? t('{v} activated', { v: executable.appVersion }), task.error ? 'error' : 'ok');
238
247
  await fetchVersions();
239
248
  }, [versions, project, versionRow, context, notify, fetchVersions]);
240
- const formActive = (tab === 'config' || settings) && focus === 'content';
249
+ // A popover owns the keyboard; the form stays mounted underneath it.
250
+ const formActive = (tab === 'config' || settings) && focus === 'content' && !overlay;
241
251
  const [editing, setEditing] = useState(false);
242
252
  const pickAddon = useCallback(async () => new Promise(resolve => {
243
253
  setOverlay({ kind: 'picker', resolve });
@@ -270,6 +280,10 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
270
280
  setOverlay({ kind: 'palette' });
271
281
  return;
272
282
  }
283
+ if (input === '?') {
284
+ setOverlay({ kind: 'help' });
285
+ return;
286
+ }
273
287
  // The config form uses Tab/Shift+Tab to move between fields.
274
288
  if (key.tab && !formActive) {
275
289
  setFilter('');
@@ -418,88 +432,96 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
418
432
  ['/', 'commands'],
419
433
  ['q', 'quit'],
420
434
  ]);
421
- const hints = overlay
422
- ? h([['Esc', 'cancel']])
423
- : focus === 'nav' && !settings
424
- ? navHints
425
- : settings
426
- ? settingsHints
427
- : tab === 'versions'
435
+ const hereHints = focus === 'nav' && !settings
436
+ ? navHints
437
+ : settings
438
+ ? settingsHints
439
+ : tab === 'versions'
440
+ ? h([
441
+ ['a', 'activate'],
442
+ ['r', 'refresh'],
443
+ ['p', 'deploy'],
444
+ ['P', 'force'],
445
+ ['/', 'commands'],
446
+ ['q', 'quit'],
447
+ ])
448
+ : tab === 'log'
428
449
  ? h([
429
- ['a', 'activate'],
430
- ['r', 'refresh'],
450
+ ['f', 'follow'],
451
+ ['x', 'wrap'],
452
+ ['K', 'stop'],
431
453
  ['p', 'deploy'],
432
454
  ['P', 'force'],
433
455
  ['/', 'commands'],
434
456
  ['q', 'quit'],
435
457
  ])
436
- : tab === 'log'
437
- ? h([
438
- ['f', 'follow'],
439
- ['x', 'wrap'],
440
- ['K', 'stop'],
458
+ : tab === 'config'
459
+ ? editing
460
+ ? h([
461
+ ['Enter', 'save'],
462
+ ['Esc', 'cancel'],
463
+ ])
464
+ : formActive
465
+ ? h([
466
+ ['↑↓', 'field'],
467
+ ['Enter', 'edit'],
468
+ ['^O', 'pick addon'],
469
+ ['y', 'sync'],
470
+ ['/', 'commands'],
471
+ ['q', 'quit'],
472
+ ])
473
+ : h([
474
+ ['Tab', 'edit'],
475
+ ['y', 'sync'],
476
+ ['l', 'login'],
477
+ ['/', 'commands'],
478
+ ['q', 'quit'],
479
+ ])
480
+ : h([
481
+ ['d', 'dev'],
482
+ ['w', 'watch'],
483
+ ['b', 'build'],
484
+ ['s', 'sign'],
441
485
  ['p', 'deploy'],
442
486
  ['P', 'force'],
487
+ ['v', 'env'],
488
+ ['K', 'stop'],
489
+ ['a', 'versions'],
490
+ ['e', 'config'],
491
+ ['i', 'install'],
492
+ ['l', 'login'],
493
+ [',', 'settings'],
443
494
  ['/', 'commands'],
444
495
  ['q', 'quit'],
445
- ])
446
- : tab === 'config'
447
- ? editing
448
- ? h([
449
- ['Enter', 'save'],
450
- ['Esc', 'cancel'],
451
- ])
452
- : formActive
453
- ? h([
454
- ['↑↓', 'field'],
455
- ['Enter', 'edit'],
456
- ['^O', 'pick addon'],
457
- ['y', 'sync'],
458
- ['/', 'commands'],
459
- ['q', 'quit'],
460
- ])
461
- : h([
462
- ['Tab', 'edit'],
463
- ['y', 'sync'],
464
- ['l', 'login'],
465
- ['/', 'commands'],
466
- ['q', 'quit'],
467
- ])
468
- : h([
469
- ['d', 'dev'],
470
- ['w', 'watch'],
471
- ['b', 'build'],
472
- ['s', 'sign'],
473
- ['p', 'deploy'],
474
- ['P', 'force'],
475
- ['v', 'env'],
476
- ['K', 'stop'],
477
- ['a', 'versions'],
478
- ['e', 'config'],
479
- ['i', 'install'],
480
- ['l', 'login'],
481
- [',', 'settings'],
482
- ['/', 'commands'],
483
- ['q', 'quit'],
484
- ]);
496
+ ]);
497
+ const hints = overlay
498
+ ? h([['Esc', 'cancel']])
499
+ : editing
500
+ ? hereHints
501
+ : [...hereHints, ...h([['?', 'help']])];
485
502
  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') }));
486
503
  const closeOverlay = () => setOverlay(null);
487
- const content = overlay ? (renderOverlay(overlay, {
488
- project,
489
- closeOverlay,
490
- run,
491
- notify,
492
- loadAddons,
493
- height: contentHeight,
494
- rerender: tick,
495
- openWorkspace: workspaceRoot
496
- ? () => {
497
- setOverlay(null);
498
- setSelected(apps.length);
499
- setFocus('content');
500
- }
501
- : undefined,
502
- })) : settings && workspaceTarget ? (_jsx(ConfigForm, { project: workspaceTarget, active: formActive, width: narrow ? columns : columns - sidebar, height: contentHeight, pickAddon: async () => null, onSaved: () => {
504
+ const popoverWidth = Math.min(96, columns - 8);
505
+ const popoverHeight = Math.min(30, frameRows - 4);
506
+ const popover = overlay &&
507
+ renderOverlay(overlay, {
508
+ project,
509
+ here: hereHints,
510
+ closeOverlay,
511
+ run,
512
+ notify,
513
+ loadAddons,
514
+ height: popoverHeight - 2,
515
+ rerender: tick,
516
+ openWorkspace: workspaceRoot
517
+ ? () => {
518
+ setOverlay(null);
519
+ setSelected(apps.length);
520
+ setFocus('content');
521
+ }
522
+ : undefined,
523
+ });
524
+ const content = settings && workspaceTarget ? (_jsx(ConfigForm, { project: workspaceTarget, active: formActive, width: narrow ? columns : columns - sidebar, height: contentHeight, pickAddon: async () => null, onSaved: () => {
503
525
  reload();
504
526
  notify(t('workspace config saved'), 'ok');
505
527
  }, onEditingChange: setEditing }, "workspace")) : (_jsxs(_Fragment, { children: [tab === 'overview' && (_jsx(Overview, { project: project, tasks: tasks, height: contentHeight })), tab === 'config' && (_jsx(ConfigForm, { project: {
@@ -517,10 +539,10 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
517
539
  color: environmentColor(env, rawProject.devProperties),
518
540
  }, version: version }), _jsxs(Box, { flexGrow: 1, height: mainHeight, children: [!narrow && (_jsx(Navigator, { apps: matches.map(i => apps[i]), groupOf: groupOf, selected: matches.indexOf(selected), focused: focus === 'nav', tasks: tasks, height: mainHeight, single: single, settingsSelected: settings, width: sidebar, filter: filter })), _jsxs(Box, { flexDirection: "column", width: narrow ? columns : columns - sidebar, overflow: "hidden", borderStyle: "single", borderLeft: false, borderRight: false, borderBottom: false, borderColor: focus === 'content' ? ACCENT : undefined, borderDimColor: focus !== 'content', children: [narrow && !single && (_jsx(NavigatorStrip, { apps: matches.map(i => apps[i]), selected: matches.indexOf(selected), focused: focus === 'nav', width: columns, filter: filter })), 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)
519
541
  ? t(' · new workspace: fill in once, every app inherits · Esc skips')
520
- : t(' · shared .dev_properties.json at the root') })] })) : (_jsx(TabBar, { tab: tab, narrow: narrow, focused: focus === 'content' })), _jsx(Box, { flexDirection: "column", height: contentHeight, overflow: "hidden", alignItems: "flex-start", children: content })] })] }), _jsx(BottomBar, { hints: hints, right: right })] }));
542
+ : t(' · shared .dev_properties.json at the root') })] })) : (_jsx(TabBar, { tab: tab, narrow: narrow, focused: focus === 'content' })), _jsx(Box, { flexDirection: "column", height: contentHeight, overflow: "hidden", alignItems: "flex-start", children: content })] })] }), _jsx(BottomBar, { hints: hints, right: right }), popover && (_jsx(Popover, { columns: columns, rows: frameRows, width: popoverWidth, height: popoverHeight, children: popover }))] }));
521
543
  }
522
544
  function renderOverlay(overlay, env) {
523
- const { project, closeOverlay, run, notify, loadAddons, height, rerender, openWorkspace, } = env;
545
+ const { project, here, closeOverlay, run, notify, loadAddons, height, rerender, openWorkspace, } = env;
524
546
  switch (overlay.kind) {
525
547
  case 'palette':
526
548
  return (_jsx(CommandPalette, { project: project, onRun: run, onClose: closeOverlay, height: height }));
@@ -557,6 +579,10 @@ function renderOverlay(overlay, env) {
557
579
  closeOverlay();
558
580
  overlay.resolve(null);
559
581
  } }));
582
+ case 'help':
583
+ return _jsx(HelpPanel, { here: here, height: height, onClose: closeOverlay });
584
+ case 'changelog':
585
+ return _jsx(ChangelogPanel, { height: height, onClose: closeOverlay });
560
586
  case 'settings':
561
587
  return (_jsx(SettingsScreen, { onChanged: rerender, onClose: closeOverlay, onOpenWorkspace: openWorkspace }));
562
588
  case 'picker':
@@ -576,7 +602,7 @@ function Confirm({ message, onAnswer, }) {
576
602
  else if (input === 'n' || input === 'N')
577
603
  onAnswer(false);
578
604
  });
579
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { children: message }), _jsx(Text, { dimColor: true, children: t('y confirm · n cancel') })] }));
605
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { children: message }), _jsx(Text, { dimColor: true, children: t('y confirm · n cancel') })] }));
580
606
  }
581
607
  function TextPrompt({ label, onSubmit, onCancel, }) {
582
608
  const [value, setValue] = useState('');
@@ -590,5 +616,5 @@ function TextPrompt({ label, onSubmit, onCancel, }) {
590
616
  else if (input && !key.ctrl && !key.meta)
591
617
  setValue(v => v + input);
592
618
  });
593
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, children: [_jsx(Text, { bold: true, children: label }), _jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), value, _jsx(Text, { inverse: true, children: " " })] }), _jsx(Text, { dimColor: true, children: t('Press Enter to submit, Esc to cancel') })] }));
619
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, children: label }), _jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), value, _jsx(Text, { inverse: true, children: " " })] }), _jsx(Text, { dimColor: true, children: t('Press Enter to submit, Esc to cancel') })] }));
594
620
  }
@@ -16,6 +16,8 @@ export interface ActionContext {
16
16
  setTab: (tab: Tab) => void;
17
17
  openWorkspaceSettings?: () => void;
18
18
  openSettings: () => void;
19
+ openHelp: () => void;
20
+ openChangelog: () => void;
19
21
  environment: string;
20
22
  isProduction: boolean;
21
23
  cycleEnvironment: () => void;
Binary file
@@ -94,6 +94,20 @@ const sv = {
94
94
  'to {env}': 'till {env}',
95
95
  'Switch environment': 'Byt miljö',
96
96
  'Add environment': 'Lägg till miljö',
97
+ Help: 'Hjälp',
98
+ 'every key in one place': 'alla tangenter på ett ställe',
99
+ Keys: 'Tangenter',
100
+ 'Esc close': 'Esc stäng',
101
+ Here: 'Här',
102
+ 'Move around': 'Förflytta dig',
103
+ 'switch pane': 'byt panel',
104
+ tabs: 'flikar',
105
+ help: 'hjälp',
106
+ "What's new": 'Nyheter',
107
+ 'changelog for every release': 'ändringslogg för varje version',
108
+ Changelog: 'Ändringslogg',
109
+ '↑↓ scroll · Esc close': '↑↓ scrolla · Esc stäng',
110
+ 'No changelog found.': 'Ingen ändringslogg hittades.',
97
111
  'Create addon': 'Skapa tillägg',
98
112
  'Create addon failed': 'Kunde inte skapa tillägget',
99
113
  'addon {addon} created': 'tillägget {addon} skapat',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sitevision-cli",
3
- "version": "1.0.0-beta.26",
3
+ "version": "1.0.0-beta.27",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "svc": "dist/cli.js"
@@ -17,7 +17,8 @@
17
17
  "release:beta": "./scripts/publish-beta.sh"
18
18
  },
19
19
  "files": [
20
- "dist"
20
+ "dist",
21
+ "CHANGELOG.md"
21
22
  ],
22
23
  "dependencies": {
23
24
  "@napi-rs/keyring": "^1.3.0",
package/readme.md CHANGED
@@ -59,6 +59,7 @@ and `p` to deploy and activate.
59
59
  | `K` | Stop running tasks |
60
60
  | `1`–`4` | Overview · Config · Versions · Log |
61
61
  | `/` | Command palette |
62
+ | `?` | Help: every key in one place |
62
63
  | `,` | Settings (language, intro animation) |
63
64
  | `Tab` / `Esc` | Switch pane / back |
64
65
  | `q` | Quit |