sitevision-cli 1.0.0-beta.32 → 1.0.0-beta.33
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 +50 -0
- package/LICENSE +21 -0
- package/dist/cli.js +56 -35
- package/dist/commands/TaskScreen.js +32 -0
- package/dist/commands/build.js +6 -185
- package/dist/commands/deploy.js +85 -238
- package/dist/commands/dev.js +34 -37
- package/dist/commands/info.js +1 -1
- package/dist/commands/setup-signing.js +43 -28
- package/dist/commands/sign.js +9 -86
- package/dist/commands/use-finish.js +17 -0
- package/dist/commands/watch.js +0 -8
- package/dist/components/AuthLoginScreen.js +27 -12
- package/dist/shell/ConfigForm.js +90 -29
- package/dist/shell/Frame.js +4 -1
- package/dist/shell/Help.js +2 -1
- package/dist/shell/Settings.js +59 -6
- package/dist/shell/Shell.js +124 -41
- package/dist/shell/Tabs.js +8 -2
- package/dist/shell/actions.js +0 -0
- package/dist/utils/config.js +46 -3
- package/dist/utils/create-app.js +9 -2
- package/dist/utils/i18n.js +36 -13
- package/dist/utils/jsonc.js +2 -1
- package/dist/utils/keychain.js +44 -4
- package/dist/utils/oauth2-auth.js +35 -7
- package/dist/utils/password-prompt.js +23 -3
- package/dist/utils/process-runner.js +4 -2
- package/dist/utils/project-detection.js +84 -33
- package/dist/utils/session-cookie-auth.js +5 -4
- package/dist/utils/sitevision-api.js +21 -14
- package/dist/utils/sitevision-scripts-runner.js +32 -15
- package/dist/utils/tasks.js +130 -64
- package/dist/utils/version-check.js +3 -3
- package/dist/utils/webpack-runner.js +6 -5
- package/dist/utils/workspace.js +41 -3
- package/dist/utils/zip.js +18 -19
- package/package.json +24 -3
- package/readme.md +11 -4
- package/dist/app.d.ts +0 -6
- package/dist/app.js +0 -321
- package/dist/cli.d.ts +0 -2
- package/dist/commands/build.d.ts +0 -12
- package/dist/commands/deploy.d.ts +0 -17
- package/dist/commands/dev.d.ts +0 -14
- package/dist/commands/index.d.ts +0 -4
- package/dist/commands/info.d.ts +0 -2
- package/dist/commands/setup-signing.d.ts +0 -2
- package/dist/commands/sign.d.ts +0 -14
- package/dist/commands/types.d.ts +0 -18
- package/dist/commands/watch.d.ts +0 -2
- package/dist/components/AnimatedLogo.d.ts +0 -10
- package/dist/components/AuthLoginScreen.d.ts +0 -21
- package/dist/components/DevPropertiesForm.d.ts +0 -11
- package/dist/components/DevPropertiesForm.js +0 -246
- package/dist/components/InfoScreen.d.ts +0 -7
- package/dist/components/InfoScreen.js +0 -14
- package/dist/components/KeychainPasswordChoice.d.ts +0 -8
- package/dist/components/KeychainPasswordChoice.js +0 -30
- package/dist/components/MainMenu.d.ts +0 -7
- package/dist/components/MainMenu.js +0 -88
- package/dist/components/PasswordInput.d.ts +0 -10
- package/dist/components/ProcessOutput.d.ts +0 -6
- package/dist/components/ProcessOutput.js +0 -30
- package/dist/components/SetupFlow.d.ts +0 -8
- package/dist/components/SetupFlow.js +0 -233
- package/dist/components/SigningPropertiesForm.d.ts +0 -7
- package/dist/components/SigningPropertiesForm.js +0 -38
- package/dist/components/StatusIndicator.d.ts +0 -8
- package/dist/components/TextInput.d.ts +0 -10
- package/dist/components/TextInput.js +0 -29
- package/dist/components/WelcomeScreen.d.ts +0 -14
- package/dist/scaffold/inquirer-stub.d.ts +0 -6
- package/dist/scaffold/register.d.ts +0 -1
- package/dist/shell/AddonPicker.d.ts +0 -14
- package/dist/shell/Changelog.d.ts +0 -10
- package/dist/shell/CommandPalette.d.ts +0 -12
- package/dist/shell/ConfigForm.d.ts +0 -42
- package/dist/shell/Frame.d.ts +0 -59
- package/dist/shell/Help.d.ts +0 -9
- package/dist/shell/Popover.d.ts +0 -12
- package/dist/shell/Settings.d.ts +0 -6
- package/dist/shell/Shell.d.ts +0 -11
- package/dist/shell/Tabs.d.ts +0 -36
- package/dist/shell/actions.d.ts +0 -49
- package/dist/types/index.d.ts +0 -295
- package/dist/utils/branding.d.ts +0 -28
- package/dist/utils/config.d.ts +0 -27
- package/dist/utils/create-app.d.ts +0 -53
- package/dist/utils/environments.d.ts +0 -20
- package/dist/utils/i18n.d.ts +0 -12
- package/dist/utils/jsonc.d.ts +0 -19
- package/dist/utils/keychain.d.ts +0 -15
- package/dist/utils/oauth2-auth.d.ts +0 -64
- package/dist/utils/password-prompt.d.ts +0 -14
- package/dist/utils/process-runner.d.ts +0 -32
- package/dist/utils/project-detection.d.ts +0 -210
- package/dist/utils/session-cookie-auth.d.ts +0 -35
- package/dist/utils/signing-password.d.ts +0 -12
- package/dist/utils/signing-step.d.ts +0 -15
- package/dist/utils/signing-step.js +0 -16
- package/dist/utils/sitevision-api.d.ts +0 -155
- package/dist/utils/sitevision-scripts-runner.d.ts +0 -84
- package/dist/utils/tasks.d.ts +0 -61
- package/dist/utils/version-check.d.ts +0 -9
- package/dist/utils/webpack-runner.d.ts +0 -76
- package/dist/utils/workspace.d.ts +0 -17
- package/dist/utils/zip.d.ts +0 -71
package/dist/shell/Settings.js
CHANGED
|
@@ -1,36 +1,73 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { getSettings, setSettings, settingsFile } from '../utils/config.js';
|
|
4
|
+
import { configProblem, getGlobalSigning, getSettings, setGlobalSigning, setSettings, settingsFile, } from '../utils/config.js';
|
|
5
5
|
import { LANGUAGES, LANGUAGE_NAMES, setLanguage, t, } from '../utils/i18n.js';
|
|
6
6
|
import { ACCENT } from './Frame.js';
|
|
7
7
|
const ROWS = [
|
|
8
8
|
{
|
|
9
9
|
key: 'language',
|
|
10
10
|
label: 'Language',
|
|
11
|
+
help: 'The language svc itself speaks.',
|
|
11
12
|
options: LANGUAGES,
|
|
12
13
|
display: value => LANGUAGE_NAMES[value],
|
|
13
14
|
},
|
|
14
15
|
{
|
|
15
16
|
key: 'introAnimation',
|
|
16
17
|
label: 'Intro animation',
|
|
18
|
+
help: 'Play the logo animation when the shell starts.',
|
|
17
19
|
options: ['on', 'off'],
|
|
18
20
|
display: value => t(value),
|
|
19
21
|
},
|
|
22
|
+
{
|
|
23
|
+
key: 'updateCheck',
|
|
24
|
+
label: 'Update check',
|
|
25
|
+
help: 'Look for a newer svc on npm at start.',
|
|
26
|
+
options: ['on', 'off'],
|
|
27
|
+
display: value => t(value),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'addonNameDriftWarning',
|
|
31
|
+
label: 'Addon name warning',
|
|
32
|
+
help: 'Mark the addon name with ≠ manifest when it is none of the names in manifest.json.',
|
|
33
|
+
options: ['on', 'off'],
|
|
34
|
+
display: value => t(value),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: 'signingUsername',
|
|
38
|
+
label: 'Default signing user',
|
|
39
|
+
help: 'The developer.sitevision.se account that signs in every project without a signing user of its own.',
|
|
40
|
+
options: [],
|
|
41
|
+
display: value => value,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: 'certificateName',
|
|
45
|
+
label: 'Default certificate',
|
|
46
|
+
help: 'Which certificate signs when the account has several. Empty = the account default.',
|
|
47
|
+
options: [],
|
|
48
|
+
display: value => value,
|
|
49
|
+
},
|
|
20
50
|
];
|
|
21
51
|
/** Global preferences, saved per field like the Config tab. */
|
|
22
52
|
export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
23
53
|
const [values, setValues] = useState(() => {
|
|
24
54
|
const s = getSettings();
|
|
55
|
+
const signing = getGlobalSigning();
|
|
25
56
|
return {
|
|
26
57
|
language: s.language,
|
|
27
58
|
introAnimation: s.introAnimation ? 'on' : 'off',
|
|
59
|
+
updateCheck: s.updateCheck ? 'on' : 'off',
|
|
60
|
+
addonNameDriftWarning: s.addonNameDriftWarning ? 'on' : 'off',
|
|
61
|
+
signingUsername: signing.signingUsername ?? '',
|
|
62
|
+
certificateName: signing.certificateName ?? '',
|
|
28
63
|
};
|
|
29
64
|
});
|
|
30
65
|
const [cursor, setCursor] = useState(0);
|
|
31
66
|
const [editing, setEditing] = useState(false);
|
|
32
67
|
const [draft, setDraft] = useState('');
|
|
33
|
-
const [note, setNote] = useState(
|
|
68
|
+
const [note, setNote] = useState(() => configProblem()
|
|
69
|
+
? t('The settings file does not parse and is ignored until it is fixed.')
|
|
70
|
+
: '');
|
|
34
71
|
const rowCount = ROWS.length + (onOpenWorkspace ? 1 : 0);
|
|
35
72
|
const onWorkspaceRow = cursor === ROWS.length;
|
|
36
73
|
const current = ROWS[Math.min(cursor, ROWS.length - 1)];
|
|
@@ -42,8 +79,16 @@ export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
|
42
79
|
setSettings({
|
|
43
80
|
language: next.language,
|
|
44
81
|
introAnimation: next.introAnimation === 'on',
|
|
82
|
+
updateCheck: next.updateCheck === 'on',
|
|
83
|
+
addonNameDriftWarning: next.addonNameDriftWarning === 'on',
|
|
84
|
+
});
|
|
85
|
+
setGlobalSigning({
|
|
86
|
+
signingUsername: next.signingUsername.trim() || undefined,
|
|
87
|
+
certificateName: next.certificateName.trim() || undefined,
|
|
45
88
|
});
|
|
46
|
-
setNote(
|
|
89
|
+
setNote(configProblem()
|
|
90
|
+
? t('Not saved: the settings file does not parse.')
|
|
91
|
+
: t('Saved.'));
|
|
47
92
|
onChanged();
|
|
48
93
|
};
|
|
49
94
|
useInput((input, key) => {
|
|
@@ -56,6 +101,12 @@ export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
|
56
101
|
if (draft !== values[current.key])
|
|
57
102
|
commit(draft);
|
|
58
103
|
}
|
|
104
|
+
else if (current.options.length === 0) {
|
|
105
|
+
if (key.backspace || key.delete)
|
|
106
|
+
setDraft(d => d.slice(0, -1));
|
|
107
|
+
else if (input && !key.ctrl && !key.meta)
|
|
108
|
+
setDraft(d => d + input);
|
|
109
|
+
}
|
|
59
110
|
else {
|
|
60
111
|
const step = key.leftArrow || key.upArrow
|
|
61
112
|
? -1
|
|
@@ -89,8 +140,10 @@ export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
|
89
140
|
const focused = i === cursor && !onWorkspaceRow;
|
|
90
141
|
const typing = focused && editing;
|
|
91
142
|
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(
|
|
93
|
-
}), onOpenWorkspace && (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: onWorkspaceRow ? ACCENT : undefined, bold: onWorkspaceRow, dimColor: !onWorkspaceRow, children: (onWorkspaceRow ? '▸ ' : ' ') + t('Workspace config').padEnd(
|
|
94
|
-
?
|
|
143
|
+
return (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: focused ? ACCENT : undefined, bold: focused, dimColor: !focused, children: (focused ? '▸ ' : ' ') + t(row.label).padEnd(24) }), row.options.length === 0 && (_jsxs(Text, { dimColor: !typing && !chosen, children: [chosen || (typing ? '' : t('not set')), typing && _jsx(Text, { inverse: true, children: " " })] })), _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));
|
|
144
|
+
}), onOpenWorkspace && (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: onWorkspaceRow ? ACCENT : undefined, bold: onWorkspaceRow, dimColor: !onWorkspaceRow, children: (onWorkspaceRow ? '▸ ' : ' ') + t('Workspace config').padEnd(24) }), _jsx(Text, { dimColor: true, children: t('shared auth and site config for every app') })] })), _jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, italic: true, children: onWorkspaceRow ? ' ' : t(current.help) }) }), _jsx(Text, { color: "yellow", children: note }), _jsx(Text, { dimColor: true, children: editing
|
|
145
|
+
? current.options.length === 0
|
|
146
|
+
? t('Enter confirm · Esc cancel')
|
|
147
|
+
: t('←→ choose · Enter confirm · Esc cancel')
|
|
95
148
|
: t('↑↓ setting · Enter edit · Esc close') })] }));
|
|
96
149
|
}
|
package/dist/shell/Shell.js
CHANGED
|
@@ -8,7 +8,7 @@ import { detectProject, appTypeOf, localizedText, readWorkspaceDevProperties, re
|
|
|
8
8
|
import { appGroup, configIncomplete, discoverApps, needsOnboarding, } from '../utils/workspace.js';
|
|
9
9
|
import { createAppInTerminal, isEmptyOrMissing, seedNewApp, startCreateApp, appNameAdvice, appNameProblem, } from '../utils/create-app.js';
|
|
10
10
|
import { listAddons, listExecutables, } from '../utils/sitevision-api.js';
|
|
11
|
-
import { useTasks, runningTasks, startActivate, getTasks, } from '../utils/tasks.js';
|
|
11
|
+
import { useTasks, runningTasks, startActivate, getTasks, scaffoldRunning, } from '../utils/tasks.js';
|
|
12
12
|
import { PasswordInput } from '../components/PasswordInput.js';
|
|
13
13
|
import { AuthLoginScreen } from '../components/AuthLoginScreen.js';
|
|
14
14
|
import { TopBar, Navigator, NavigatorStrip, BottomBar, navMatches, navMove, appLabel, navWidth, NARROW_BELOW, ACCENT, } from './Frame.js';
|
|
@@ -22,6 +22,7 @@ import { Popover } from './Popover.js';
|
|
|
22
22
|
import { ChangelogPanel } from './Changelog.js';
|
|
23
23
|
import { baseEnvironment, environmentColor, environmentNames, environmentProject, isProductionEnvironment, resolveEnvironment, } from '../utils/environments.js';
|
|
24
24
|
import { t } from '../utils/i18n.js';
|
|
25
|
+
import { onKeychainSaveFailed } from '../utils/keychain.js';
|
|
25
26
|
import { actionForKey, actions, authState, resolveDeployConfig, } from './actions.js';
|
|
26
27
|
function useSize() {
|
|
27
28
|
const { stdout } = useStdout();
|
|
@@ -37,7 +38,7 @@ function useSize() {
|
|
|
37
38
|
}, [stdout]);
|
|
38
39
|
return size;
|
|
39
40
|
}
|
|
40
|
-
export function Shell({ apps: initialApps, workspaceRoot, version, minimal = false, updatedFrom, handover, }) {
|
|
41
|
+
export function Shell({ apps: initialApps, workspaceRoot, version, minimal = false, updatedFrom, handover, skipped, }) {
|
|
41
42
|
const { exit } = useApp();
|
|
42
43
|
const { columns, rows } = useSize();
|
|
43
44
|
const tasks = useTasks();
|
|
@@ -47,14 +48,31 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
47
48
|
const [selected, setSelected] = useState(onboard ? initialApps.length : 0);
|
|
48
49
|
const [tab, setTab] = useState('overview');
|
|
49
50
|
const [focus, setFocus] = useState(workspaceRoot && !onboard ? 'nav' : 'content');
|
|
50
|
-
const [
|
|
51
|
+
const [overlays, setOverlays] = useState(updatedFrom ? [{ kind: 'changelog', since: updatedFrom }] : []);
|
|
52
|
+
const setOverlay = useCallback((next) => {
|
|
53
|
+
setOverlays(stack => overlayStack(stack, next));
|
|
54
|
+
}, []);
|
|
55
|
+
const [editing, setEditing] = useState(false);
|
|
56
|
+
// A confirm that turns up mid-edit stays hidden until the field is done.
|
|
57
|
+
const top = overlays.at(-1) ?? null;
|
|
58
|
+
const overlay = top?.kind === 'confirm' && editing ? null : top;
|
|
51
59
|
const [filter, setFilter] = useState('');
|
|
52
60
|
const [versions, setVersions] = useState({});
|
|
53
61
|
const [versionRow, setVersionRow] = useState(0);
|
|
54
62
|
const [logScroll, setLogScroll] = useState(0);
|
|
55
63
|
const [logWrap, setLogWrap] = useState(false);
|
|
56
64
|
const [offerAddon, setOfferAddon] = useState();
|
|
57
|
-
const [notice, setNotice] = useState(
|
|
65
|
+
const [notice, setNotice] = useState(skipped?.length
|
|
66
|
+
? {
|
|
67
|
+
text: skipped.length === 1
|
|
68
|
+
? t('Skipped: {reason}', { reason: skipped[0] })
|
|
69
|
+
: t('Skipped {n} apps. First: {reason}', {
|
|
70
|
+
n: skipped.length,
|
|
71
|
+
reason: skipped[0],
|
|
72
|
+
}),
|
|
73
|
+
level: 'warn',
|
|
74
|
+
}
|
|
75
|
+
: null);
|
|
58
76
|
const [, tick] = useReducer((n) => n + 1, 0);
|
|
59
77
|
// In workspace mode the row after the last app is "Workspace settings".
|
|
60
78
|
const settings = Boolean(workspaceRoot) && selected === apps.length;
|
|
@@ -117,12 +135,35 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
117
135
|
const notify = useCallback((text, level = 'info') => {
|
|
118
136
|
setNotice({ text, level });
|
|
119
137
|
}, []);
|
|
138
|
+
// "Save to keychain" that did not save would otherwise just ask again.
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
onKeychainSaveFailed(() => {
|
|
141
|
+
notify(t('Could not save to the OS keychain; you will be asked again next time.'), 'warn');
|
|
142
|
+
});
|
|
143
|
+
}, [notify]);
|
|
120
144
|
const quit = useCallback(() => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
task.
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
const leave = () => {
|
|
146
|
+
for (const task of getTasks())
|
|
147
|
+
if (task.status === 'running')
|
|
148
|
+
task.stop();
|
|
149
|
+
exit();
|
|
150
|
+
};
|
|
151
|
+
// Between the scaffolder's questions nothing is open, so q is live:
|
|
152
|
+
// quitting then would leave a half-made app behind without a word.
|
|
153
|
+
if (scaffoldRunning()) {
|
|
154
|
+
setOverlay({
|
|
155
|
+
kind: 'confirm',
|
|
156
|
+
message: t('A new app is still being created. Stop it and quit?'),
|
|
157
|
+
resolve(yes) {
|
|
158
|
+
if (yes)
|
|
159
|
+
leave();
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
leave();
|
|
165
|
+
}
|
|
166
|
+
}, [exit, setOverlay]);
|
|
126
167
|
const context = useMemo(() => ({
|
|
127
168
|
project,
|
|
128
169
|
reload,
|
|
@@ -173,8 +214,18 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
173
214
|
notify(t('environment {env} added', { env: clean }), 'ok');
|
|
174
215
|
},
|
|
175
216
|
async createApp() {
|
|
217
|
+
// Only what this flow opened is closed when the scaffolder ends.
|
|
218
|
+
const asked = new Set();
|
|
219
|
+
const openOwn = (next) => {
|
|
220
|
+
asked.add(next);
|
|
221
|
+
setOverlay(next);
|
|
222
|
+
};
|
|
223
|
+
if (scaffoldRunning()) {
|
|
224
|
+
notify(t('An app is already being created.'), 'warn');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
176
227
|
const promptText = async (label, initial, validate, error, advise) => new Promise(resolve => {
|
|
177
|
-
|
|
228
|
+
openOwn({
|
|
178
229
|
kind: 'prompt',
|
|
179
230
|
label,
|
|
180
231
|
initial,
|
|
@@ -210,7 +261,7 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
210
261
|
].toSorted((a, b) => a.localeCompare(b));
|
|
211
262
|
// eslint-disable-next-line no-await-in-loop
|
|
212
263
|
const folderPick = await new Promise(resolve => {
|
|
213
|
-
|
|
264
|
+
openOwn({
|
|
214
265
|
kind: 'choice',
|
|
215
266
|
label: t('Create it in folder'),
|
|
216
267
|
choices: [...folders, t('Other folder…')],
|
|
@@ -258,20 +309,20 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
258
309
|
: question.message;
|
|
259
310
|
if (question.type === 'confirm') {
|
|
260
311
|
const value = await new Promise(resolve => {
|
|
261
|
-
|
|
312
|
+
openOwn({ kind: 'confirm', message: label, resolve });
|
|
262
313
|
});
|
|
263
314
|
return { value };
|
|
264
315
|
}
|
|
265
316
|
if (question.type === 'password') {
|
|
266
317
|
const secret = await new Promise(resolve => {
|
|
267
|
-
|
|
318
|
+
openOwn({ kind: 'password', label, resolve });
|
|
268
319
|
});
|
|
269
320
|
return secret && { value: secret.password };
|
|
270
321
|
}
|
|
271
322
|
if (question.choices.length > 0) {
|
|
272
323
|
const multi = question.type === 'checkbox';
|
|
273
324
|
const picked = await new Promise(resolve => {
|
|
274
|
-
|
|
325
|
+
openOwn({
|
|
275
326
|
kind: 'choice',
|
|
276
327
|
label,
|
|
277
328
|
choices: question.choices,
|
|
@@ -290,7 +341,7 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
290
341
|
setTab('log');
|
|
291
342
|
const { done } = startCreateApp({ name, parentDir, ask });
|
|
292
343
|
let outcome = await done;
|
|
293
|
-
|
|
344
|
+
setOverlays(stack => withoutOverlays(stack, asked));
|
|
294
345
|
if (outcome === 'unmanaged') {
|
|
295
346
|
if (!handover) {
|
|
296
347
|
notify(t('The scaffolder could not be run from here'), 'error');
|
|
@@ -323,7 +374,10 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
323
374
|
notify(t('{app} created in {dir}', { app: name, dir: appDir }), 'ok');
|
|
324
375
|
return;
|
|
325
376
|
}
|
|
326
|
-
const
|
|
377
|
+
const left = [];
|
|
378
|
+
const found = discoverApps(workspaceRoot, left);
|
|
379
|
+
if (left[0])
|
|
380
|
+
notify(t('Skipped: {reason}', { reason: left[0] }), 'warn');
|
|
327
381
|
setApps(found);
|
|
328
382
|
setFilter('');
|
|
329
383
|
setSelected(Math.max(0, found.findIndex(app => app.root === appDir)));
|
|
@@ -366,7 +420,7 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
366
420
|
action.run(context).catch((error) => {
|
|
367
421
|
notify(error instanceof Error ? error.message : String(error), 'error');
|
|
368
422
|
});
|
|
369
|
-
}, [context, project, notify]);
|
|
423
|
+
}, [context, project, notify, setOverlay]);
|
|
370
424
|
const fetchVersions = useCallback(async (fresh = false) => {
|
|
371
425
|
const key = versionsKey;
|
|
372
426
|
const config = await resolveDeployConfig(context, fresh);
|
|
@@ -407,13 +461,20 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
407
461
|
await wait();
|
|
408
462
|
notify(task.error ?? t('{v} activated', { v: executable.appVersion }), task.error ? 'error' : 'ok');
|
|
409
463
|
await fetchVersions();
|
|
410
|
-
}, [
|
|
464
|
+
}, [
|
|
465
|
+
versions,
|
|
466
|
+
versionsKey,
|
|
467
|
+
project,
|
|
468
|
+
versionRow,
|
|
469
|
+
context,
|
|
470
|
+
notify,
|
|
471
|
+
fetchVersions,
|
|
472
|
+
]);
|
|
411
473
|
// A popover owns the keyboard; the form stays mounted underneath it.
|
|
412
474
|
const formActive = (tab === 'config' || settings) && focus === 'content' && !overlay;
|
|
413
|
-
const [editing, setEditing] = useState(false);
|
|
414
475
|
const pickAddon = useCallback(async () => new Promise(resolve => {
|
|
415
476
|
setOverlay({ kind: 'picker', resolve });
|
|
416
|
-
}), []);
|
|
477
|
+
}), [setOverlay]);
|
|
417
478
|
// A freshly created app: once it is the selected project, offer its addon.
|
|
418
479
|
useEffect(() => {
|
|
419
480
|
if (!offerAddon || project.root !== offerAddon || overlay)
|
|
@@ -434,7 +495,7 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
434
495
|
});
|
|
435
496
|
}, [offerAddon, project, overlay, context, run]);
|
|
436
497
|
const loadAddons = useCallback(async () => {
|
|
437
|
-
const config = await resolveDeployConfig(context);
|
|
498
|
+
const config = await resolveDeployConfig(context, false, { addon: false });
|
|
438
499
|
return config ? listAddons(config) : { error: t('No credentials.') };
|
|
439
500
|
}, [context]);
|
|
440
501
|
const appTasks = tasks.filter(task => task.appRoot === project.root);
|
|
@@ -464,6 +525,9 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
464
525
|
setFocus(single ? 'content' : 'nav');
|
|
465
526
|
return;
|
|
466
527
|
}
|
|
528
|
+
// Ctrl+D is not d: only plain keys are shortcuts.
|
|
529
|
+
if (key.ctrl || key.meta)
|
|
530
|
+
return;
|
|
467
531
|
if (input === '/') {
|
|
468
532
|
setOverlay({ kind: 'palette' });
|
|
469
533
|
return;
|
|
@@ -472,6 +536,11 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
472
536
|
setOverlay({ kind: 'help' });
|
|
473
537
|
return;
|
|
474
538
|
}
|
|
539
|
+
// Reachable from the navigator's search and the workspace form too.
|
|
540
|
+
if (input === ',') {
|
|
541
|
+
setOverlay({ kind: 'settings' });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
475
544
|
// The config form uses Tab/Shift+Tab to move between fields.
|
|
476
545
|
if (key.tab && !formActive) {
|
|
477
546
|
setFilter('');
|
|
@@ -508,6 +577,23 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
508
577
|
search(filter + input);
|
|
509
578
|
return;
|
|
510
579
|
}
|
|
580
|
+
if (settings && focus === 'content') {
|
|
581
|
+
// Settings pane: the form owns everything but q, y and Tab/Esc above.
|
|
582
|
+
if (input === 'q')
|
|
583
|
+
quit();
|
|
584
|
+
else if (input === 'y') {
|
|
585
|
+
try {
|
|
586
|
+
if (syncDevPropertiesToPackageJson(workspaceRoot)) {
|
|
587
|
+
reload();
|
|
588
|
+
notify(t('package.json updated'), 'ok');
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
notify(error instanceof Error ? error.message : String(error), 'error');
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
511
597
|
if (input === 'a' && tab !== 'versions') {
|
|
512
598
|
setTab('versions');
|
|
513
599
|
setFocus('content');
|
|
@@ -526,23 +612,6 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
|
|
|
526
612
|
setTab(TABS[(i + (key.rightArrow ? 1 : TABS.length - 1)) % TABS.length].id);
|
|
527
613
|
return;
|
|
528
614
|
}
|
|
529
|
-
if (settings && focus === 'content') {
|
|
530
|
-
// Settings pane: the form owns everything but q, y and Tab/Esc above.
|
|
531
|
-
if (input === 'q')
|
|
532
|
-
quit();
|
|
533
|
-
else if (input === 'y') {
|
|
534
|
-
try {
|
|
535
|
-
if (syncDevPropertiesToPackageJson(workspaceRoot)) {
|
|
536
|
-
reload();
|
|
537
|
-
notify(t('package.json updated'), 'ok');
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
catch (error) {
|
|
541
|
-
notify(error instanceof Error ? error.message : String(error), 'error');
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
615
|
if (tab === 'versions') {
|
|
547
616
|
const count = versions[versionsKey]?.executables?.length ?? 0;
|
|
548
617
|
if (key.upArrow)
|
|
@@ -795,11 +864,25 @@ function renderOverlay(overlay, env) {
|
|
|
795
864
|
} }));
|
|
796
865
|
}
|
|
797
866
|
}
|
|
798
|
-
|
|
799
|
-
|
|
867
|
+
/**
|
|
868
|
+
* The open overlays, last one shown. `null` closes the shown one. A new overlay
|
|
869
|
+
* covers the current one instead of replacing it, so whoever awaits the covered
|
|
870
|
+
* one still gets an answer. A confirm goes underneath: background tasks raise
|
|
871
|
+
* them, and on top it would take the keys being typed into a prompt.
|
|
872
|
+
*/
|
|
873
|
+
export function overlayStack(stack, next) {
|
|
874
|
+
if (next === null)
|
|
875
|
+
return stack.slice(0, -1);
|
|
876
|
+
return next.kind === 'confirm' ? [next, ...stack] : [...stack, next];
|
|
877
|
+
}
|
|
878
|
+
export function withoutOverlays(stack, gone) {
|
|
879
|
+
return stack.filter(overlay => !gone.has(overlay));
|
|
880
|
+
}
|
|
881
|
+
export function Confirm({ message, onAnswer, }) {
|
|
882
|
+
useInput((input, key) => {
|
|
800
883
|
if (input === 'y' || input === 'Y')
|
|
801
884
|
onAnswer(true);
|
|
802
|
-
else if (input === 'n' || input === 'N')
|
|
885
|
+
else if (input === 'n' || input === 'N' || key.escape)
|
|
803
886
|
onAnswer(false);
|
|
804
887
|
});
|
|
805
888
|
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { children: message }), _jsx(Text, { dimColor: true, children: t('y confirm · n cancel') })] }));
|
package/dist/shell/Tabs.js
CHANGED
|
@@ -5,6 +5,7 @@ import { appTypeOf, getPackageJsonSyncChanges, readAncestorDevProperties, locali
|
|
|
5
5
|
import { checkSitevisionScriptsCompatibility } from '../utils/sitevision-scripts-runner.js';
|
|
6
6
|
import { ACCENT, elapsed } from './Frame.js';
|
|
7
7
|
import { t } from '../utils/i18n.js';
|
|
8
|
+
import { getGlobalSigning } from '../utils/config.js';
|
|
8
9
|
export const TABS = [
|
|
9
10
|
{ id: 'overview', label: 'Overview', short: 'Ovw' },
|
|
10
11
|
{ id: 'config', label: 'Config', short: 'Cfg' },
|
|
@@ -40,11 +41,16 @@ export function Overview({ project, tasks, height, }) {
|
|
|
40
41
|
const dev = project.devProperties;
|
|
41
42
|
const inherited = new Set(project.inheritedKeys);
|
|
42
43
|
const ancestors = readAncestorDevProperties(project.root);
|
|
44
|
+
const globalSigning = getGlobalSigning();
|
|
43
45
|
const src = (key) => inherited.has(key)
|
|
44
46
|
? Object.hasOwn(ancestors, key)
|
|
45
47
|
? t('↑ root')
|
|
46
48
|
: 'package.json'
|
|
47
|
-
: undefined
|
|
49
|
+
: globalSigning[key] !== undefined &&
|
|
50
|
+
globalSigning[key] ===
|
|
51
|
+
dev?.[key]
|
|
52
|
+
? t('global')
|
|
53
|
+
: undefined;
|
|
48
54
|
const notSet = t('not set');
|
|
49
55
|
const sync = getPackageJsonSyncChanges(project.root).length;
|
|
50
56
|
const scripts = checkSitevisionScriptsCompatibility(project.root);
|
|
@@ -64,7 +70,7 @@ export function Versions({ project, state, selected, }) {
|
|
|
64
70
|
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "yellow", children: t('⚠ Configure dev properties first (e).') }) }));
|
|
65
71
|
}
|
|
66
72
|
if (!state) {
|
|
67
|
-
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: t('Press
|
|
73
|
+
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: t('Press r to fetch versions from {domain}.', {
|
|
68
74
|
domain: project.devProperties.domain,
|
|
69
75
|
}) }) }));
|
|
70
76
|
}
|
package/dist/shell/actions.js
CHANGED
|
Binary file
|
package/dist/utils/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
+
import { parseJsonc } from './jsonc.js';
|
|
4
5
|
function configDir() {
|
|
5
6
|
const base = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
|
6
7
|
return path.join(base, 'sitevision-cli');
|
|
@@ -10,13 +11,33 @@ function configFile() {
|
|
|
10
11
|
}
|
|
11
12
|
function readConfig() {
|
|
12
13
|
try {
|
|
13
|
-
return
|
|
14
|
+
return parseJsonc(fs.readFileSync(configFile(), 'utf8'));
|
|
14
15
|
}
|
|
15
16
|
catch {
|
|
16
17
|
return {};
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
/** Set when the file exists but does not parse; it is then left untouched. */
|
|
21
|
+
export function configProblem() {
|
|
22
|
+
let text;
|
|
23
|
+
try {
|
|
24
|
+
text = fs.readFileSync(configFile(), 'utf8');
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
parseJsonc(text);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return error instanceof Error ? error.message : String(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
19
37
|
function writeConfig(config) {
|
|
38
|
+
// A hand-edited file with a typo must not be replaced by defaults.
|
|
39
|
+
if (configProblem())
|
|
40
|
+
return;
|
|
20
41
|
try {
|
|
21
42
|
fs.mkdirSync(configDir(), { recursive: true });
|
|
22
43
|
fs.writeFileSync(configFile(), JSON.stringify(config, null, 2));
|
|
@@ -31,10 +52,30 @@ export function getSettings() {
|
|
|
31
52
|
return {
|
|
32
53
|
language: config.language ?? 'en',
|
|
33
54
|
introAnimation: config.introAnimation ?? true,
|
|
55
|
+
updateCheck: config.updateCheck ?? true,
|
|
56
|
+
addonNameDriftWarning: config.warnings?.addonNameDrift ?? true,
|
|
34
57
|
};
|
|
35
58
|
}
|
|
36
59
|
export function setSettings(patch) {
|
|
37
|
-
|
|
60
|
+
const { addonNameDriftWarning, ...rest } = patch;
|
|
61
|
+
const config = { ...readConfig(), ...rest };
|
|
62
|
+
if (addonNameDriftWarning !== undefined) {
|
|
63
|
+
config.warnings = {
|
|
64
|
+
...config.warnings,
|
|
65
|
+
addonNameDrift: addonNameDriftWarning,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
writeConfig(config);
|
|
69
|
+
}
|
|
70
|
+
export function getGlobalSigning() {
|
|
71
|
+
const { signingUsername, certificateName } = readConfig();
|
|
72
|
+
return {
|
|
73
|
+
...(signingUsername && { signingUsername }),
|
|
74
|
+
...(certificateName && { certificateName }),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function setGlobalSigning(signing) {
|
|
78
|
+
writeConfig({ ...readConfig(), ...signing });
|
|
38
79
|
}
|
|
39
80
|
/** Path of the config file, for display. */
|
|
40
81
|
export function settingsFile() {
|
|
@@ -44,7 +85,7 @@ export function settingsFile() {
|
|
|
44
85
|
* True until the user has completed the first-run welcome at least once.
|
|
45
86
|
*/
|
|
46
87
|
export function isFirstRun() {
|
|
47
|
-
return !readConfig().firstRunCompleted;
|
|
88
|
+
return !configProblem() && !readConfig().firstRunCompleted;
|
|
48
89
|
}
|
|
49
90
|
/**
|
|
50
91
|
* Persist that the first-run welcome has been seen.
|
|
@@ -66,6 +107,8 @@ export function getLastSeenVersion() {
|
|
|
66
107
|
*/
|
|
67
108
|
export function setLastSeenVersion(version) {
|
|
68
109
|
const config = readConfig();
|
|
110
|
+
if (config.lastSeenVersion === version)
|
|
111
|
+
return;
|
|
69
112
|
config.lastSeenVersion = version;
|
|
70
113
|
writeConfig(config);
|
|
71
114
|
}
|
package/dist/utils/create-app.js
CHANGED
|
@@ -154,7 +154,8 @@ export function startCreateApp(options) {
|
|
|
154
154
|
/** Run the scaffolder on the real terminal; resolves when it exits. */
|
|
155
155
|
export async function createAppInTerminal(name, parentDir) {
|
|
156
156
|
await new Promise(resolve => {
|
|
157
|
-
|
|
157
|
+
// `name` is validated by the caller; joined to avoid DEP0190.
|
|
158
|
+
const child = spawn([...CREATE_COMMAND, name].join(' '), {
|
|
158
159
|
cwd: parentDir,
|
|
159
160
|
stdio: 'inherit',
|
|
160
161
|
shell: true,
|
|
@@ -162,7 +163,13 @@ export async function createAppInTerminal(name, parentDir) {
|
|
|
162
163
|
child.on('close', () => resolve());
|
|
163
164
|
child.on('error', () => resolve());
|
|
164
165
|
});
|
|
165
|
-
|
|
166
|
+
try {
|
|
167
|
+
return Boolean(readManifest(path.join(parentDir, name)));
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// A half-written manifest is a failed scaffold, not a crash.
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
166
173
|
}
|
|
167
174
|
/**
|
|
168
175
|
* Replace the template's placeholders with what is known, and fold the config
|