sitevision-cli 1.0.0-beta.2 → 1.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/app.d.ts +1 -1
  2. package/dist/app.js +59 -8
  3. package/dist/cli.js +96 -39
  4. package/dist/commands/build.js +1 -1
  5. package/dist/commands/deploy.d.ts +2 -2
  6. package/dist/commands/deploy.js +135 -25
  7. package/dist/commands/dev.d.ts +8 -10
  8. package/dist/commands/dev.js +77 -366
  9. package/dist/commands/info.js +2 -2
  10. package/dist/commands/watch.js +5 -23
  11. package/dist/components/AnimatedLogo.js +8 -2
  12. package/dist/components/AuthLoginScreen.d.ts +21 -0
  13. package/dist/components/AuthLoginScreen.js +90 -0
  14. package/dist/components/DevPropertiesForm.d.ts +2 -1
  15. package/dist/components/DevPropertiesForm.js +198 -33
  16. package/dist/components/InfoScreen.js +2 -2
  17. package/dist/components/MainMenu.js +7 -2
  18. package/dist/components/PasswordInput.js +2 -1
  19. package/dist/components/SetupFlow.d.ts +2 -1
  20. package/dist/components/SetupFlow.js +100 -11
  21. package/dist/shell/AddonPicker.d.ts +14 -0
  22. package/dist/shell/AddonPicker.js +54 -0
  23. package/dist/shell/CommandPalette.d.ts +8 -0
  24. package/dist/shell/CommandPalette.js +63 -0
  25. package/dist/shell/ConfigForm.d.ts +36 -0
  26. package/dist/shell/ConfigForm.js +558 -0
  27. package/dist/shell/Frame.d.ts +59 -0
  28. package/dist/shell/Frame.js +134 -0
  29. package/dist/shell/Settings.d.ts +6 -0
  30. package/dist/shell/Settings.js +96 -0
  31. package/dist/shell/Shell.d.ts +9 -0
  32. package/dist/shell/Shell.js +586 -0
  33. package/dist/shell/Tabs.d.ts +36 -0
  34. package/dist/shell/Tabs.js +90 -0
  35. package/dist/shell/actions.d.ts +45 -0
  36. package/dist/shell/actions.js +0 -0
  37. package/dist/types/index.d.ts +44 -5
  38. package/dist/utils/config.d.ts +10 -0
  39. package/dist/utils/config.js +14 -0
  40. package/dist/utils/environments.d.ts +20 -0
  41. package/dist/utils/environments.js +74 -0
  42. package/dist/utils/i18n.d.ts +12 -0
  43. package/dist/utils/i18n.js +279 -0
  44. package/dist/utils/jsonc.d.ts +19 -0
  45. package/dist/utils/jsonc.js +74 -0
  46. package/dist/utils/keychain.d.ts +9 -0
  47. package/dist/utils/keychain.js +54 -0
  48. package/dist/utils/oauth2-auth.d.ts +64 -0
  49. package/dist/utils/oauth2-auth.js +242 -0
  50. package/dist/utils/password-prompt.d.ts +5 -0
  51. package/dist/utils/password-prompt.js +28 -0
  52. package/dist/utils/project-detection.d.ts +105 -6
  53. package/dist/utils/project-detection.js +411 -54
  54. package/dist/utils/session-cookie-auth.d.ts +35 -0
  55. package/dist/utils/session-cookie-auth.js +99 -0
  56. package/dist/utils/sitevision-api.d.ts +64 -5
  57. package/dist/utils/sitevision-api.js +195 -33
  58. package/dist/utils/tasks.d.ts +48 -0
  59. package/dist/utils/tasks.js +371 -0
  60. package/dist/utils/workspace.d.ts +17 -0
  61. package/dist/utils/workspace.js +67 -0
  62. package/package.json +3 -1
  63. package/readme.md +102 -121
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import Spinner from 'ink-spinner';
5
+ import { t } from '../utils/i18n.js';
6
+ import { beginOAuth2Login, openBrowser } from '../utils/oauth2-auth.js';
7
+ import { beginCookieLogin, } from '../utils/session-cookie-auth.js';
8
+ /**
9
+ * Ink-native interactive login for OAuth2 and session-cookie auth. Drives the
10
+ * browser and (for cookie) the "press Enter to capture" handoff through Ink's
11
+ * own input, so it works inside the TUI as well as the standalone command —
12
+ * neither needs to own raw stdin the way the old console prompt did.
13
+ */
14
+ export function AuthLoginScreen({ method, devProperties, onComplete, onError, onCancel, }) {
15
+ const [phase, setPhase] = React.useState('starting');
16
+ const [authUrl, setAuthUrl] = React.useState('');
17
+ const [note, setNote] = React.useState('');
18
+ const [loginUrl, setLoginUrl] = React.useState('');
19
+ const cookieRef = React.useRef(null);
20
+ const cancelOAuthRef = React.useRef(null);
21
+ React.useEffect(() => {
22
+ void (async () => {
23
+ if (method === 'oauth2') {
24
+ const session = beginOAuth2Login(devProperties);
25
+ if (!session) {
26
+ onError('OAuth2 is not fully configured (authorization/token endpoint or client ID missing).');
27
+ return;
28
+ }
29
+ cancelOAuthRef.current = session.cancel;
30
+ setAuthUrl(session.authUrl);
31
+ openBrowser(session.authUrl);
32
+ setPhase('awaiting');
33
+ const { token, error } = await session.complete();
34
+ cancelOAuthRef.current = null;
35
+ if (token) {
36
+ onComplete({ accessToken: token });
37
+ }
38
+ else {
39
+ onError(error ?? 'OAuth2 login failed.');
40
+ }
41
+ }
42
+ else {
43
+ const session = await beginCookieLogin(devProperties);
44
+ if (!session) {
45
+ onError('Could not open a login browser (is Chrome installed?). Set SITEVISION_SESSION_COOKIE or pass --cookie with a cookie copied from your browser.');
46
+ return;
47
+ }
48
+ cookieRef.current = session;
49
+ setLoginUrl(session.loginUrl);
50
+ setPhase('awaiting');
51
+ }
52
+ })();
53
+ // Release resources if the screen unmounts before completing: close the
54
+ // browser (cookie) and the loopback server (oauth2, frees the port).
55
+ return () => {
56
+ void cookieRef.current?.close();
57
+ cookieRef.current = null;
58
+ cancelOAuthRef.current?.();
59
+ cancelOAuthRef.current = null;
60
+ };
61
+ // Run once: the parent mounts this fresh when a login is needed.
62
+ // eslint-disable-next-line react-hooks/exhaustive-deps
63
+ }, []);
64
+ useInput((_input, key) => {
65
+ if (key.escape) {
66
+ onCancel();
67
+ return;
68
+ }
69
+ if (method === 'cookie' && phase === 'awaiting' && key.return) {
70
+ const session = cookieRef.current;
71
+ if (!session)
72
+ return;
73
+ setPhase('capturing');
74
+ void (async () => {
75
+ const result = await session.capture();
76
+ if (result.cookie) {
77
+ cookieRef.current = null;
78
+ await session.close();
79
+ onComplete({ sessionCookie: result.cookie });
80
+ }
81
+ else {
82
+ // Keep the browser open so the user can navigate and retry.
83
+ setNote(result.error ?? 'No session cookie found.');
84
+ setPhase('awaiting');
85
+ }
86
+ })();
87
+ }
88
+ });
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.') }) })] }));
90
+ }
@@ -3,8 +3,9 @@ interface Props {
3
3
  projectRoot: string;
4
4
  initialProperties?: DevProperties;
5
5
  packageJson: PackageJson;
6
+ authOnly?: boolean;
6
7
  onComplete: () => void;
7
8
  onCancel: () => void;
8
9
  }
9
- export declare function DevPropertiesForm({ projectRoot, initialProperties, packageJson, onComplete, onCancel, }: Props): import("react").JSX.Element;
10
+ export declare function DevPropertiesForm({ projectRoot, initialProperties, packageJson, authOnly, onComplete, onCancel, }: Props): import("react").JSX.Element;
10
11
  export {};
@@ -1,67 +1,232 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useState, useEffect } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
4
  import { TextInput } from './TextInput.js';
5
5
  import { writeDevProperties } from '../utils/project-detection.js';
6
- import { setDeployPassword, deleteDeployPassword } from '../utils/keychain.js';
7
- const STEPS = [
8
- { id: 'domain', label: 'Domain' },
9
- { id: 'siteName', label: 'Site Name' },
10
- { id: 'addonName', label: 'Addon Name' },
11
- { id: 'username', label: 'Username' },
12
- { id: 'password', label: 'Password' },
13
- { id: 'useHTTP', label: 'Use HTTP' },
6
+ import { setDeployPassword, deleteDeployPassword, setOAuth2ClientSecret, } from '../utils/keychain.js';
7
+ import { DEFAULT_REDIRECT_PORT, discoverOAuth2Config, } from '../utils/oauth2-auth.js';
8
+ const LABELS = {
9
+ domain: 'Domain',
10
+ siteName: 'Site Name',
11
+ addonName: 'Addon Name',
12
+ username: 'Username',
13
+ authMethod: 'Auth Method',
14
+ password: 'Password',
15
+ oauthClientId: 'Client ID',
16
+ oauthAuthEndpoint: 'Authorization Endpoint',
17
+ oauthTokenEndpoint: 'Token Endpoint',
18
+ oauthScopes: 'Scopes',
19
+ oauthClientSecret: 'Client Secret',
20
+ sessionLoginUrl: 'Login URL',
21
+ useHTTP: 'Use HTTP',
22
+ };
23
+ const AUTH_METHODS = [
24
+ { value: 'basic', label: 'Basic auth (username + password)' },
25
+ { value: 'oauth2', label: 'OAuth2 bearer token (PKCE)' },
26
+ { value: 'cookie', label: 'Session cookie (SAML / SSO login)' },
14
27
  ];
15
- export function DevPropertiesForm({ projectRoot, initialProperties, packageJson, onComplete, onCancel, }) {
28
+ function parseScopes(raw) {
29
+ const scopes = raw
30
+ .split(/[\s,]+/)
31
+ .map(s => s.trim())
32
+ .filter(Boolean);
33
+ return scopes.length > 0 ? scopes : undefined;
34
+ }
35
+ export function DevPropertiesForm({ projectRoot, initialProperties, packageJson, authOnly, onComplete, onCancel, }) {
16
36
  const [stepIndex, setStepIndex] = useState(0);
17
37
  const [properties, setProperties] = useState(() => {
18
38
  const defaults = {
19
39
  domain: packageJson.developmentDomain || '',
20
40
  addonName: packageJson.addonName || '',
21
41
  siteName: packageJson.siteName || '',
42
+ authMethod: 'basic',
22
43
  };
23
44
  return { ...defaults, ...initialProperties };
24
45
  });
25
- const currentStep = STEPS[stepIndex];
26
- const handleNext = (key, value) => {
27
- const newProperties = { ...properties, [key]: value };
28
- setProperties(newProperties);
29
- if (stepIndex < STEPS.length - 1) {
46
+ const [oauth, setOauth] = useState(() => ({
47
+ authorizationEndpoint: initialProperties?.oauth2?.authorizationEndpoint ?? '',
48
+ tokenEndpoint: initialProperties?.oauth2?.tokenEndpoint ?? '',
49
+ clientId: initialProperties?.oauth2?.clientId ?? '',
50
+ scopes: initialProperties?.oauth2?.scopes?.join(' ') ?? '',
51
+ clientSecret: '',
52
+ }));
53
+ const [discoveryNote, setDiscoveryNote] = useState('');
54
+ const method = properties.authMethod ?? 'basic';
55
+ const isOAuth = method === 'oauth2';
56
+ // When OAuth2 is chosen, auto-fill the endpoints from the site's OpenID
57
+ // configuration (unauthenticated) so the user doesn't type them in.
58
+ useEffect(() => {
59
+ if (method !== 'oauth2' || !properties.domain)
60
+ return;
61
+ if (oauth.authorizationEndpoint && oauth.tokenEndpoint)
62
+ return;
63
+ let cancelled = false;
64
+ setDiscoveryNote('Looking up OAuth2 endpoints…');
65
+ void discoverOAuth2Config(properties.domain, properties.useHTTPForDevDeploy).then(discovered => {
66
+ if (cancelled)
67
+ return;
68
+ if (discovered) {
69
+ // Endpoints only — do NOT pre-fill scopes. `scopes_supported` is the
70
+ // provider's list, not what this client is granted (and casing may
71
+ // differ, e.g. advertised `all` vs client `ALL`), so requesting them
72
+ // causes `invalid_scope`. Empty scopes → the client's default scopes.
73
+ setOauth(previous => ({
74
+ ...previous,
75
+ authorizationEndpoint: previous.authorizationEndpoint || discovered.authorizationEndpoint,
76
+ tokenEndpoint: previous.tokenEndpoint || discovered.tokenEndpoint,
77
+ }));
78
+ setDiscoveryNote('Endpoints auto-filled from the site OpenID config.');
79
+ }
80
+ else {
81
+ setDiscoveryNote('Could not auto-discover endpoints — enter them below.');
82
+ }
83
+ });
84
+ return () => {
85
+ cancelled = true;
86
+ };
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ }, [method, properties.domain]);
89
+ const methodSteps = method === 'oauth2'
90
+ ? [
91
+ 'oauthClientId',
92
+ 'oauthAuthEndpoint',
93
+ 'oauthTokenEndpoint',
94
+ 'oauthScopes',
95
+ 'oauthClientSecret',
96
+ ]
97
+ : method === 'cookie'
98
+ ? ['sessionLoginUrl']
99
+ : ['password'];
100
+ const steps = authOnly
101
+ ? ['authMethod', ...methodSteps]
102
+ : [
103
+ 'domain',
104
+ 'siteName',
105
+ 'addonName',
106
+ 'username',
107
+ 'authMethod',
108
+ ...methodSteps,
109
+ 'useHTTP',
110
+ ];
111
+ const currentStep = steps[stepIndex];
112
+ const redirectPort = initialProperties?.oauth2?.redirectPort ?? DEFAULT_REDIRECT_PORT;
113
+ const advance = () => {
114
+ if (stepIndex < steps.length - 1) {
30
115
  setStepIndex(stepIndex + 1);
31
116
  }
32
- else {
33
- const finalProperties = newProperties;
34
- const { password, domain, username } = finalProperties;
35
- if (password && domain && username) {
36
- setDeployPassword(domain, username, password);
117
+ };
118
+ const finalize = (props, fields) => {
119
+ const authMethod = props.authMethod ?? 'basic';
120
+ const domain = props.domain ?? '';
121
+ const username = props.username ?? '';
122
+ const finalProps = {
123
+ domain,
124
+ siteName: props.siteName ?? '',
125
+ addonName: props.addonName ?? '',
126
+ username,
127
+ authMethod,
128
+ useHTTPForDevDeploy: props.useHTTPForDevDeploy ?? false,
129
+ };
130
+ if (authMethod === 'oauth2') {
131
+ finalProps.oauth2 = {
132
+ authorizationEndpoint: fields.authorizationEndpoint,
133
+ tokenEndpoint: fields.tokenEndpoint,
134
+ clientId: fields.clientId,
135
+ scopes: parseScopes(fields.scopes),
136
+ ...(initialProperties?.oauth2?.redirectPort && {
137
+ redirectPort: initialProperties.oauth2.redirectPort,
138
+ }),
139
+ };
140
+ if (fields.clientSecret && domain && fields.clientId) {
141
+ setOAuth2ClientSecret(domain, fields.clientId, fields.clientSecret);
37
142
  }
38
- else if (domain && username) {
39
- // Empty password clear any stale keychain entry so deploy falls through to prompt
40
- deleteDeployPassword(domain, username);
143
+ }
144
+ else if (authMethod === 'cookie') {
145
+ if (props.sessionLoginUrl) {
146
+ finalProps.sessionLoginUrl = props.sessionLoginUrl;
41
147
  }
42
- writeDevProperties(projectRoot, finalProperties);
43
- onComplete();
148
+ }
149
+ else if (props.password && domain && username) {
150
+ setDeployPassword(domain, username, props.password);
151
+ }
152
+ else if (domain && username) {
153
+ // Empty password — clear any stale keychain entry so deploy prompts.
154
+ deleteDeployPassword(domain, username);
155
+ }
156
+ writeDevProperties(projectRoot, finalProps);
157
+ onComplete();
158
+ };
159
+ // Update a top-level DevProperties field, then advance or finalize.
160
+ const submitProperty = (key, value) => {
161
+ const next = { ...properties, [key]: value };
162
+ setProperties(next);
163
+ if (steps[stepIndex] === steps[steps.length - 1]) {
164
+ finalize(next, oauth);
165
+ }
166
+ else {
167
+ advance();
168
+ }
169
+ };
170
+ // Update an OAuth field, then advance or finalize.
171
+ const submitOAuth = (key, value) => {
172
+ const next = { ...oauth, [key]: value };
173
+ setOauth(next);
174
+ if (steps[stepIndex] === steps[steps.length - 1]) {
175
+ finalize(properties, next);
176
+ }
177
+ else {
178
+ advance();
44
179
  }
45
180
  };
46
181
  const renderInput = () => {
47
- switch (currentStep?.id) {
182
+ switch (currentStep) {
48
183
  case 'domain':
49
- return (_jsx(TextInput, { label: "Development Domain (e.g. www.sitevision.se)", defaultValue: properties.domain, placeholder: "sitevision.se", onSubmit: (value) => handleNext('domain', value), onCancel: onCancel }, "domain"));
184
+ return (_jsx(TextInput, { label: "Development Domain (e.g. www.sitevision.se)", defaultValue: properties.domain, placeholder: "sitevision.se", onSubmit: value => submitProperty('domain', value), onCancel: onCancel }, "domain"));
50
185
  case 'siteName':
51
- return (_jsx(TextInput, { label: "Site Name (Root node name)", defaultValue: properties.siteName, onSubmit: (value) => handleNext('siteName', value), onCancel: onCancel }, "siteName"));
186
+ return (_jsx(TextInput, { label: "Site Name (Root node name)", defaultValue: properties.siteName, onSubmit: value => submitProperty('siteName', value), onCancel: onCancel }, "siteName"));
52
187
  case 'addonName':
53
- return (_jsx(TextInput, { label: "Addon Name", defaultValue: properties.addonName, onSubmit: (value) => handleNext('addonName', value), onCancel: onCancel }, "addonName"));
188
+ return (_jsx(TextInput, { label: "Addon Name", defaultValue: properties.addonName, onSubmit: value => submitProperty('addonName', value), onCancel: onCancel }, "addonName"));
54
189
  case 'username':
55
- return (_jsx(TextInput, { label: "Username (usually your Sitevision Cloud email)", defaultValue: properties.username, onSubmit: (value) => handleNext('username', value), onCancel: onCancel }, "username"));
190
+ return (_jsx(TextInput, { label: "Username (usually your Sitevision Cloud email)", defaultValue: properties.username, onSubmit: value => submitProperty('username', value), onCancel: onCancel }, "username"));
191
+ case 'authMethod':
192
+ return (_jsx(MethodSelect, { defaultValue: method, onSubmit: value => submitProperty('authMethod', value) }, "authMethod"));
56
193
  case 'password':
57
- return (_jsx(TextInput, { label: "Password (saved in OS keychain \u2014 leave empty to prompt on each run)", type: "password", defaultValue: properties.password, onSubmit: (value) => handleNext('password', value), onCancel: onCancel }, "password"));
194
+ return (_jsx(TextInput, { label: "Password (saved in OS keychain \u2014 leave empty to prompt on each run)", type: "password", defaultValue: properties.password, onSubmit: value => submitProperty('password', value), onCancel: onCancel }, "password"));
195
+ case 'oauthClientId':
196
+ return (_jsx(TextInput, { label: "OAuth2 Client ID", defaultValue: oauth.clientId, onSubmit: value => submitOAuth('clientId', value), onCancel: onCancel }, "oauthClientId"));
197
+ case 'oauthAuthEndpoint':
198
+ return (_jsx(TextInput, { label: "Authorization Endpoint URL", defaultValue: oauth.authorizationEndpoint, onSubmit: value => submitOAuth('authorizationEndpoint', value), onCancel: onCancel }, "oauthAuthEndpoint"));
199
+ case 'oauthTokenEndpoint':
200
+ return (_jsx(TextInput, { label: "Token Endpoint URL", defaultValue: oauth.tokenEndpoint, onSubmit: value => submitOAuth('tokenEndpoint', value), onCancel: onCancel }, "oauthTokenEndpoint"));
201
+ case 'oauthScopes':
202
+ return (_jsx(TextInput, { label: "Scopes (space-separated \u2014 leave empty to use the client's default scopes; add offline_access, matching your client's casing, for a refresh token)", defaultValue: oauth.scopes, onSubmit: value => submitOAuth('scopes', value), onCancel: onCancel }, "oauthScopes"));
203
+ case 'oauthClientSecret':
204
+ return (_jsx(TextInput, { label: "Client Secret (OS keychain \u2014 required if your Sitevision client has a secret; leave empty only for a public client)", type: "password", defaultValue: oauth.clientSecret, onSubmit: value => submitOAuth('clientSecret', value), onCancel: onCancel }, "oauthClientSecret"));
205
+ case 'sessionLoginUrl':
206
+ return (_jsx(TextInput, { label: "Login URL (opened in a browser; blank = site root)", defaultValue: properties.sessionLoginUrl ??
207
+ (properties.domain ? `https://${properties.domain}/` : ''), onSubmit: value => submitProperty('sessionLoginUrl', value), onCancel: onCancel }, "sessionLoginUrl"));
58
208
  case 'useHTTP':
59
- return (_jsx(BooleanInput, { label: "Use HTTP for deployment? (y/n)", defaultValue: properties.useHTTPForDevDeploy ?? false, onSubmit: (value) => handleNext('useHTTPForDevDeploy', value) }, "useHTTP"));
209
+ return (_jsx(BooleanInput, { label: "Use HTTP for deployment? (y/n)", defaultValue: properties.useHTTPForDevDeploy ?? false, onSubmit: value => submitProperty('useHTTPForDevDeploy', value) }, "useHTTP"));
60
210
  default:
61
211
  return null;
62
212
  }
63
213
  };
64
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Setup Development Properties" }), _jsxs(Text, { children: [' ', "Step ", stepIndex + 1, " of ", STEPS.length, ": ", currentStep?.label] })] }), _jsx(Box, { marginBottom: 1, children: STEPS.map((s, i) => (_jsx(Box, { marginRight: 1, children: _jsx(Text, { color: i === stepIndex ? 'green' : i < stepIndex ? 'green' : 'gray', children: i < stepIndex ? '✓' : i === stepIndex ? '●' : '○' }) }, s.id))) }), _jsx(Box, { borderStyle: "single", borderColor: "gray", padding: 1, children: renderInput() })] }));
214
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Setup Development Properties" }), _jsxs(Text, { children: [' ', "Step ", stepIndex + 1, " of ", steps.length, ":", ' ', currentStep ? LABELS[currentStep] : ''] })] }), isOAuth && (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Whitelist this redirect URI on the OAuth2 client: http://127.0.0.1:", redirectPort, "/callback"] }), discoveryNote && _jsx(Text, { color: "yellow", children: discoveryNote })] })), _jsx(Box, { marginBottom: 1, children: steps.map((s, i) => (_jsx(Box, { marginRight: 1, children: _jsx(Text, { color: i <= stepIndex ? 'green' : 'gray', children: i < stepIndex ? '✓' : i === stepIndex ? '●' : '○' }) }, s))) }), _jsx(Box, { borderStyle: "single", borderColor: "gray", padding: 1, children: renderInput() })] }));
215
+ }
216
+ function MethodSelect({ defaultValue, onSubmit, }) {
217
+ const [index, setIndex] = useState(() => Math.max(0, AUTH_METHODS.findIndex(m => m.value === defaultValue)));
218
+ useInput((_input, key) => {
219
+ if (key.upArrow) {
220
+ setIndex(p => (p === 0 ? AUTH_METHODS.length - 1 : p - 1));
221
+ }
222
+ else if (key.downArrow) {
223
+ setIndex(p => (p === AUTH_METHODS.length - 1 ? 0 : p + 1));
224
+ }
225
+ else if (key.return) {
226
+ onSubmit(AUTH_METHODS[index].value);
227
+ }
228
+ });
229
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Authentication method" }) }), AUTH_METHODS.map((m, i) => (_jsxs(Text, { color: i === index ? 'green' : undefined, children: [i === index ? '❯ ' : ' ', m.label] }, m.value))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move, Enter to select" }) })] }));
65
230
  }
66
231
  function BooleanInput({ label, defaultValue, onSubmit, }) {
67
232
  useInput(input => {
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Box, Text, useInput } from 'ink';
3
- import { getAppType } from '../utils/project-detection.js';
3
+ import { getAppType, localizedText, } from '../utils/project-detection.js';
4
4
  import { checkSitevisionScriptsCompatibility } from '../utils/sitevision-scripts-runner.js';
5
5
  export function InfoScreen({ project, onBack }) {
6
6
  const appType = getAppType(project.manifest);
@@ -10,5 +10,5 @@ export function InfoScreen({ project, onBack }) {
10
10
  onBack();
11
11
  }
12
12
  });
13
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: project.manifest.name })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Build Tooling" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "sitevision-scripts: " }), _jsx(Text, { children: scriptsCompat.installed ?? 'not installed' }), _jsxs(Text, { dimColor: true, children: [" (supported ", scriptsCompat.supportedRange, ")"] })] }), scriptsCompat.warning && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", scriptsCompat.warning] }) }))] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties found. Run setup-dev-properties to configure." }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { dimColor: true, children: "Press Enter or ESC to return to menu" }) })] }));
13
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: localizedText(project.manifest.name) })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Build Tooling" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "sitevision-scripts: " }), _jsx(Text, { children: scriptsCompat.installed ?? 'not installed' }), _jsxs(Text, { dimColor: true, children: [" (supported ", scriptsCompat.supportedRange, ")"] })] }), scriptsCompat.warning && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", scriptsCompat.warning] }) }))] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties found. Run setup-dev-properties to configure." }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { dimColor: true, children: "Press Enter or ESC to return to menu" }) })] }));
14
14
  }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
- import { getAppType } from '../utils/project-detection.js';
4
+ import { getAppType, localizedText } from '../utils/project-detection.js';
5
5
  export function MainMenu({ project, onSelect }) {
6
6
  const appType = getAppType(project.manifest);
7
7
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -51,6 +51,11 @@ export function MainMenu({ project, onSelect }) {
51
51
  value: 'deploy-production',
52
52
  description: 'Deploy to production server',
53
53
  },
54
+ {
55
+ label: '🔑 Auth Method',
56
+ value: 'change-auth',
57
+ description: 'Change the deploy authentication method',
58
+ },
54
59
  {
55
60
  label: 'ℹ️ Info',
56
61
  value: 'info',
@@ -79,5 +84,5 @@ export function MainMenu({ project, onSelect }) {
79
84
  onSelect(selectedItem.value);
80
85
  }
81
86
  });
82
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: project.manifest.name })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), project.hasSigningProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Signing Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Signing User: " }), _jsx(Text, { children: project.devProperties.signingUsername })] }), project.devProperties.certificateName && (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Certificate: " }), _jsx(Text, { children: project.devProperties.certificateName })] }))] })] })), !project.hasDevProperties && (_jsx(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "yellow", children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties found. Some commands may not work." }) })), project.hasDevProperties && !project.hasSigningProperties && (_jsx(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "yellow", children: _jsx(Text, { color: "yellow", children: "\u26A0 No signing credentials configured. Run svc setup-signing to configure." }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select a command:" }) }), _jsx(Box, { flexDirection: "column", children: items.map((item, index) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: index === selectedIndex ? 'cyan' : undefined, bold: index === selectedIndex, children: [index === selectedIndex ? '▶ ' : ' ', item.label, item.description && _jsxs(Text, { dimColor: true, children: [" - ", item.description] })] }) }, item.value))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 arrows to navigate, Enter to select" }) })] }));
87
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: localizedText(project.manifest.name) })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), project.hasSigningProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Signing Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Signing User: " }), _jsx(Text, { children: project.devProperties.signingUsername })] }), project.devProperties.certificateName && (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Certificate: " }), _jsx(Text, { children: project.devProperties.certificateName })] }))] })] })), !project.hasDevProperties && (_jsx(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "yellow", children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties found. Some commands may not work." }) })), project.hasDevProperties && !project.hasSigningProperties && (_jsx(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "yellow", children: _jsx(Text, { color: "yellow", children: "\u26A0 No signing credentials configured. Run svc setup-signing to configure." }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select a command:" }) }), _jsx(Box, { flexDirection: "column", children: items.map((item, index) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: index === selectedIndex ? 'cyan' : undefined, bold: index === selectedIndex, children: [index === selectedIndex ? '▶ ' : ' ', item.label, item.description && _jsxs(Text, { dimColor: true, children: [" - ", item.description] })] }) }, item.value))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 arrows to navigate, Enter to select" }) })] }));
83
88
  }
@@ -1,6 +1,7 @@
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 { t } from '../utils/i18n.js';
4
5
  export function PasswordInput({ label = 'Enter Signing Password', showRememberOption = false, defaultRemember = false, rememberLabel = 'Save to OS keychain: ', onSubmit, onCancel, }) {
5
6
  const [password, setPassword] = useState('');
6
7
  const [remember, setRemember] = useState(defaultRemember);
@@ -26,5 +27,5 @@ export function PasswordInput({ label = 'Enter Signing Password', showRememberOp
26
27
  setPassword(prev => prev + input);
27
28
  }
28
29
  });
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
+ 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' : ' ', "]"] }), _jsxs(Text, { dimColor: true, children: [" ", t('(Tab to toggle)')] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('Press Enter to submit, Esc to cancel') }) })] }));
30
31
  }
@@ -1,7 +1,8 @@
1
1
  import { type ProjectInfo } from '../utils/project-detection.js';
2
2
  interface Props {
3
3
  project: ProjectInfo;
4
+ onReload: () => void;
4
5
  onComplete: () => void;
5
6
  }
6
- export declare function SetupFlow({ project, onComplete }: Props): import("react").JSX.Element | null;
7
+ export declare function SetupFlow({ project, onReload, onComplete }: Props): import("react").JSX.Element | null;
7
8
  export {};