sitevision-cli 0.4.0-beta.1 → 0.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -1,20 +1,48 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useMemo, useState } from 'react';
3
3
  import { MainMenu } from './components/MainMenu.js';
4
4
  import { InfoScreen } from './components/InfoScreen.js';
5
5
  import { SetupFlow } from './components/SetupFlow.js';
6
6
  import { PasswordInput } from './components/PasswordInput.js';
7
+ import { KeychainPasswordChoice } from './components/KeychainPasswordChoice.js';
8
+ import { decideSigningStep } from './utils/signing-step.js';
7
9
  import { DevScreen } from './commands/dev.js';
8
10
  import { BuildScreen } from './commands/build.js';
9
11
  import { DeployScreen } from './commands/deploy.js';
10
12
  import { SignScreen } from './commands/sign.js';
11
13
  import { SigningPropertiesForm } from './components/SigningPropertiesForm.js';
12
- import { setDeployPassword as saveDeployPassword, setSigningPassword as saveSigningPassword, } from './utils/keychain.js';
14
+ import { getSigningPassword, setDeployPassword as saveDeployPassword, setSigningPassword as saveSigningPassword, } from './utils/keychain.js';
13
15
  export default function App({ project }) {
14
16
  const [state, setState] = useState('setup');
15
17
  const [currentCommand, setCurrentCommand] = useState('');
16
18
  const [signingPassword, setSigningPassword] = useState('');
17
19
  const [devPassword, setDevPassword] = useState('');
20
+ // When true, skip the keychain "use saved / enter new" choice and go straight
21
+ // to manual entry (e.g. the saved password just failed and we're retrying).
22
+ const [signingRetry, setSigningRetry] = useState(false);
23
+ // Read the saved signing password from the keychain once (keychain access is
24
+ // slow and this component re-renders frequently).
25
+ const signingUsername = project.devProperties?.signingUsername;
26
+ const storedSigningPassword = useMemo(() => (signingUsername ? getSigningPassword(signingUsername) : null), [signingUsername]);
27
+ // Decide the next step once a signing password is needed: proceed if we already
28
+ // have one this session, offer the keychain choice if one is saved, otherwise
29
+ // prompt for manual entry.
30
+ const routeToSigningStep = (command = currentCommand) => {
31
+ const step = decideSigningStep({
32
+ hasSessionPassword: Boolean(signingPassword),
33
+ hasStoredPassword: Boolean(storedSigningPassword),
34
+ isRetry: signingRetry,
35
+ });
36
+ if (step === 'proceed') {
37
+ setState(command === 'dev-signed' ? 'dev' : 'sign');
38
+ }
39
+ else if (step === 'choice') {
40
+ setState('signing-password-choice');
41
+ }
42
+ else {
43
+ setState('signing-password-input');
44
+ }
45
+ };
18
46
  // Check if dev password is available (either from file or session)
19
47
  const hasDevPassword = Boolean(project.devProperties?.password || devPassword);
20
48
  // Get effective dev properties with session password if needed
@@ -35,8 +63,8 @@ export default function App({ project }) {
35
63
  }
36
64
  // Continue to the intended command
37
65
  if (currentCommand === 'dev' || currentCommand === 'dev-signed') {
38
- if (currentCommand === 'dev-signed' && !signingPassword) {
39
- setState('signing-password-input');
66
+ if (currentCommand === 'dev-signed') {
67
+ routeToSigningStep();
40
68
  }
41
69
  else {
42
70
  setState('dev');
@@ -46,6 +74,15 @@ export default function App({ project }) {
46
74
  setState('deploy');
47
75
  }
48
76
  };
77
+ const handleUseSavedSigning = () => {
78
+ if (storedSigningPassword) {
79
+ setSigningPassword(storedSigningPassword);
80
+ }
81
+ setState(currentCommand === 'dev-signed' ? 'dev' : 'sign');
82
+ };
83
+ const handleEnterNewSigning = () => {
84
+ setState('signing-password-input');
85
+ };
49
86
  const handleSigningPasswordSubmit = (password, remember) => {
50
87
  setSigningPassword(password);
51
88
  if (remember && project.devProperties?.signingUsername && password) {
@@ -60,6 +97,8 @@ export default function App({ project }) {
60
97
  };
61
98
  const handleCommandSelect = (command) => {
62
99
  setCurrentCommand(command);
100
+ // Fresh selection from the menu — re-offer the saved keychain password.
101
+ setSigningRetry(false);
63
102
  switch (command) {
64
103
  case 'info':
65
104
  setState('info');
@@ -92,11 +131,8 @@ export default function App({ project }) {
92
131
  if (!hasDevPassword) {
93
132
  setState('dev-password-input');
94
133
  }
95
- else if (!signingPassword) {
96
- setState('signing-password-input');
97
- }
98
134
  else {
99
- setState('dev');
135
+ routeToSigningStep(command);
100
136
  }
101
137
  break;
102
138
  case 'sign':
@@ -108,12 +144,7 @@ export default function App({ project }) {
108
144
  console.log('\x1b[31mSigning credentials not configured. Run svc setup-signing first.\x1b[0m');
109
145
  return;
110
146
  }
111
- if (signingPassword) {
112
- setState('sign');
113
- }
114
- else {
115
- setState('signing-password-input');
116
- }
147
+ routeToSigningStep(command);
117
148
  break;
118
149
  case 'build':
119
150
  setState('build');
@@ -146,8 +177,13 @@ export default function App({ project }) {
146
177
  if (state === 'dev-password-input') {
147
178
  return (_jsx(PasswordInput, { label: "Enter Development Password (usually Sitevision Cloud Password)", showRememberOption: Boolean(project.devProperties?.domain && project.devProperties?.username), onSubmit: handleDevPasswordSubmit, onCancel: () => setState('menu') }, "dev-password"));
148
179
  }
180
+ if (state === 'signing-password-choice') {
181
+ return (_jsx(KeychainPasswordChoice, { onUseSaved: handleUseSavedSigning, onEnterNew: handleEnterNewSigning, onCancel: () => setState('menu') }, "signing-password-choice"));
182
+ }
149
183
  if (state === 'signing-password-input') {
150
- return (_jsx(PasswordInput, { label: "Enter Signing Password (developer.sitevision.se)", showRememberOption: Boolean(project.devProperties?.signingUsername), onSubmit: handleSigningPasswordSubmit, onCancel: () => setState('menu') }, "signing-password"));
184
+ return (_jsx(PasswordInput, { label: "Enter Signing Password (developer.sitevision.se)", showRememberOption: Boolean(project.devProperties?.signingUsername), defaultRemember: Boolean(storedSigningPassword), rememberLabel: storedSigningPassword
185
+ ? 'Update saved password in OS keychain: '
186
+ : 'Save to OS keychain: ', onSubmit: handleSigningPasswordSubmit, onCancel: () => setState('menu') }, "signing-password"));
151
187
  }
152
188
  if (state === 'dev') {
153
189
  return (_jsx(DevScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: getEffectiveDevProperties(), signed: currentCommand === 'dev-signed', onBack: () => setState('menu'), onRetryCredentials: () => {
@@ -156,6 +192,8 @@ export default function App({ project }) {
156
192
  project.devProperties.password = undefined;
157
193
  if (currentCommand === 'dev-signed') {
158
194
  setSigningPassword('');
195
+ // The saved password may be what failed — don't re-offer it.
196
+ setSigningRetry(true);
159
197
  }
160
198
  setState('dev-password-input');
161
199
  }, signingCredentials: currentCommand === 'dev-signed' &&
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ label?: string;
3
+ onUseSaved: () => void;
4
+ onEnterNew: () => void;
5
+ onCancel: () => void;
6
+ }
7
+ export declare function KeychainPasswordChoice({ label, onUseSaved, onEnterNew, onCancel, }: Props): import("react").JSX.Element;
8
+ export {};
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ const OPTIONS = [
5
+ { label: 'Use saved password from keychain', value: 'saved' },
6
+ { label: 'Enter a new password', value: 'new' },
7
+ ];
8
+ export function KeychainPasswordChoice({ label = 'A signing password is saved in your OS keychain.', onUseSaved, onEnterNew, onCancel, }) {
9
+ const [selectedIndex, setSelectedIndex] = useState(0);
10
+ useInput((_input, key) => {
11
+ if (key.upArrow) {
12
+ setSelectedIndex(prev => (prev === 0 ? OPTIONS.length - 1 : prev - 1));
13
+ }
14
+ else if (key.downArrow) {
15
+ setSelectedIndex(prev => (prev === OPTIONS.length - 1 ? 0 : prev + 1));
16
+ }
17
+ else if (key.return) {
18
+ if (selectedIndex === 0) {
19
+ onUseSaved();
20
+ }
21
+ else {
22
+ onEnterNew();
23
+ }
24
+ }
25
+ else if (key.escape) {
26
+ onCancel();
27
+ }
28
+ });
29
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: label }) }), OPTIONS.map((option, index) => (_jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', option.label] }, option.value))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move, Enter to select, Esc to cancel" }) })] }));
30
+ }
@@ -1,8 +1,10 @@
1
1
  interface Props {
2
2
  label?: string;
3
3
  showRememberOption?: boolean;
4
+ defaultRemember?: boolean;
5
+ rememberLabel?: string;
4
6
  onSubmit: (password: string, remember: boolean) => void;
5
7
  onCancel: () => void;
6
8
  }
7
- export declare function PasswordInput({ label, showRememberOption, onSubmit, onCancel, }: Props): import("react").JSX.Element;
9
+ export declare function PasswordInput({ label, showRememberOption, defaultRemember, rememberLabel, onSubmit, onCancel, }: Props): import("react").JSX.Element;
8
10
  export {};
@@ -1,9 +1,9 @@
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
- export function PasswordInput({ label = 'Enter Signing Password', showRememberOption = false, onSubmit, onCancel, }) {
4
+ export function PasswordInput({ label = 'Enter Signing Password', showRememberOption = false, defaultRemember = false, rememberLabel = 'Save to OS keychain: ', onSubmit, onCancel, }) {
5
5
  const [password, setPassword] = useState('');
6
- const [remember, setRemember] = useState(false);
6
+ const [remember, setRemember] = useState(defaultRemember);
7
7
  useInput((input, key) => {
8
8
  if (key.return) {
9
9
  onSubmit(password, remember);
@@ -26,5 +26,5 @@ export function PasswordInput({ label = 'Enter Signing Password', showRememberOp
26
26
  setPassword(prev => prev + input);
27
27
  }
28
28
  });
29
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: label }) }), _jsx(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: '*'.repeat(password.length) }) }), showRememberOption && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Save to OS keychain: " }), _jsxs(Text, { color: remember ? 'green' : 'gray', children: ["[", remember ? 'x' : ' ', "]"] }), _jsx(Text, { dimColor: true, children: " (Tab to toggle)" })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to submit, Esc to cancel" }) })] }));
29
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: label }) }), _jsx(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: '*'.repeat(password.length) }) }), showRememberOption && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: rememberLabel }), _jsxs(Text, { color: remember ? 'green' : 'gray', children: ["[", remember ? 'x' : ' ', "]"] }), _jsx(Text, { dimColor: true, children: " (Tab to toggle)" })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to submit, Esc to cancel" }) })] }));
30
30
  }
@@ -0,0 +1,15 @@
1
+ export type SigningStep = 'proceed' | 'choice' | 'input';
2
+ /**
3
+ * Decide what to do when a signing password is needed.
4
+ *
5
+ * - `proceed`: a password is already available this session — use it.
6
+ * - `choice`: a password is saved in the keychain — ask whether to use it or
7
+ * enter a new one.
8
+ * - `input`: prompt for a password (nothing saved, or a saved one just failed
9
+ * and we're retrying).
10
+ */
11
+ export declare function decideSigningStep(options: {
12
+ hasSessionPassword: boolean;
13
+ hasStoredPassword: boolean;
14
+ isRetry: boolean;
15
+ }): SigningStep;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Decide what to do when a signing password is needed.
3
+ *
4
+ * - `proceed`: a password is already available this session — use it.
5
+ * - `choice`: a password is saved in the keychain — ask whether to use it or
6
+ * enter a new one.
7
+ * - `input`: prompt for a password (nothing saved, or a saved one just failed
8
+ * and we're retrying).
9
+ */
10
+ export function decideSigningStep(options) {
11
+ if (options.hasSessionPassword)
12
+ return 'proceed';
13
+ if (options.hasStoredPassword && !options.isRetry)
14
+ return 'choice';
15
+ return 'input';
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sitevision-cli",
3
- "version": "0.4.0-beta.1",
3
+ "version": "0.4.0-beta.2",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "svc": "dist/cli.js"