sitevision-cli 1.0.0-beta.0 → 1.0.0-beta.10

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.
@@ -3,15 +3,35 @@ import { useState } 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 } 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
  ];
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
+ }
15
35
  export function DevPropertiesForm({ projectRoot, initialProperties, packageJson, onComplete, onCancel, }) {
16
36
  const [stepIndex, setStepIndex] = useState(0);
17
37
  const [properties, setProperties] = useState(() => {
@@ -19,49 +39,158 @@ export function DevPropertiesForm({ projectRoot, initialProperties, packageJson,
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 method = properties.authMethod ?? 'basic';
54
+ const isOAuth = method === 'oauth2';
55
+ const methodSteps = method === 'oauth2'
56
+ ? [
57
+ 'oauthClientId',
58
+ 'oauthAuthEndpoint',
59
+ 'oauthTokenEndpoint',
60
+ 'oauthScopes',
61
+ 'oauthClientSecret',
62
+ ]
63
+ : method === 'cookie'
64
+ ? ['sessionLoginUrl']
65
+ : ['password'];
66
+ const steps = [
67
+ 'domain',
68
+ 'siteName',
69
+ 'addonName',
70
+ 'username',
71
+ 'authMethod',
72
+ ...methodSteps,
73
+ 'useHTTP',
74
+ ];
75
+ const currentStep = steps[stepIndex];
76
+ const redirectPort = initialProperties?.oauth2?.redirectPort ?? DEFAULT_REDIRECT_PORT;
77
+ const advance = () => {
78
+ if (stepIndex < steps.length - 1) {
30
79
  setStepIndex(stepIndex + 1);
31
80
  }
32
- else {
33
- const finalProperties = newProperties;
34
- const { password, domain, username } = finalProperties;
35
- if (password && domain && username) {
36
- setDeployPassword(domain, username, password);
81
+ };
82
+ const finalize = (props, fields) => {
83
+ const authMethod = props.authMethod ?? 'basic';
84
+ const domain = props.domain ?? '';
85
+ const username = props.username ?? '';
86
+ const finalProps = {
87
+ domain,
88
+ siteName: props.siteName ?? '',
89
+ addonName: props.addonName ?? '',
90
+ username,
91
+ authMethod,
92
+ useHTTPForDevDeploy: props.useHTTPForDevDeploy ?? false,
93
+ };
94
+ if (authMethod === 'oauth2') {
95
+ finalProps.oauth2 = {
96
+ authorizationEndpoint: fields.authorizationEndpoint,
97
+ tokenEndpoint: fields.tokenEndpoint,
98
+ clientId: fields.clientId,
99
+ scopes: parseScopes(fields.scopes),
100
+ ...(initialProperties?.oauth2?.redirectPort && {
101
+ redirectPort: initialProperties.oauth2.redirectPort,
102
+ }),
103
+ };
104
+ if (fields.clientSecret && domain && fields.clientId) {
105
+ setOAuth2ClientSecret(domain, fields.clientId, fields.clientSecret);
37
106
  }
38
- else if (domain && username) {
39
- // Empty password clear any stale keychain entry so deploy falls through to prompt
40
- deleteDeployPassword(domain, username);
107
+ }
108
+ else if (authMethod === 'cookie') {
109
+ if (props.sessionLoginUrl) {
110
+ finalProps.sessionLoginUrl = props.sessionLoginUrl;
41
111
  }
42
- writeDevProperties(projectRoot, finalProperties);
43
- onComplete();
112
+ }
113
+ else if (props.password && domain && username) {
114
+ setDeployPassword(domain, username, props.password);
115
+ }
116
+ else if (domain && username) {
117
+ // Empty password — clear any stale keychain entry so deploy prompts.
118
+ deleteDeployPassword(domain, username);
119
+ }
120
+ writeDevProperties(projectRoot, finalProps);
121
+ onComplete();
122
+ };
123
+ // Update a top-level DevProperties field, then advance or finalize.
124
+ const submitProperty = (key, value) => {
125
+ const next = { ...properties, [key]: value };
126
+ setProperties(next);
127
+ if (steps[stepIndex] === steps[steps.length - 1]) {
128
+ finalize(next, oauth);
129
+ }
130
+ else {
131
+ advance();
132
+ }
133
+ };
134
+ // Update an OAuth field, then advance or finalize.
135
+ const submitOAuth = (key, value) => {
136
+ const next = { ...oauth, [key]: value };
137
+ setOauth(next);
138
+ if (steps[stepIndex] === steps[steps.length - 1]) {
139
+ finalize(properties, next);
140
+ }
141
+ else {
142
+ advance();
44
143
  }
45
144
  };
46
145
  const renderInput = () => {
47
- switch (currentStep?.id) {
146
+ switch (currentStep) {
48
147
  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"));
148
+ 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
149
  case 'siteName':
51
- return (_jsx(TextInput, { label: "Site Name (Root node name)", defaultValue: properties.siteName, onSubmit: (value) => handleNext('siteName', value), onCancel: onCancel }, "siteName"));
150
+ return (_jsx(TextInput, { label: "Site Name (Root node name)", defaultValue: properties.siteName, onSubmit: value => submitProperty('siteName', value), onCancel: onCancel }, "siteName"));
52
151
  case 'addonName':
53
- return (_jsx(TextInput, { label: "Addon Name", defaultValue: properties.addonName, onSubmit: (value) => handleNext('addonName', value), onCancel: onCancel }, "addonName"));
152
+ return (_jsx(TextInput, { label: "Addon Name", defaultValue: properties.addonName, onSubmit: value => submitProperty('addonName', value), onCancel: onCancel }, "addonName"));
54
153
  case 'username':
55
- return (_jsx(TextInput, { label: "Username (usually your Sitevision Cloud email)", defaultValue: properties.username, onSubmit: (value) => handleNext('username', value), onCancel: onCancel }, "username"));
154
+ return (_jsx(TextInput, { label: "Username (usually your Sitevision Cloud email)", defaultValue: properties.username, onSubmit: value => submitProperty('username', value), onCancel: onCancel }, "username"));
155
+ case 'authMethod':
156
+ return (_jsx(MethodSelect, { defaultValue: method, onSubmit: value => submitProperty('authMethod', value) }, "authMethod"));
56
157
  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"));
158
+ 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"));
159
+ case 'oauthClientId':
160
+ return (_jsx(TextInput, { label: "OAuth2 Client ID", defaultValue: oauth.clientId, onSubmit: value => submitOAuth('clientId', value), onCancel: onCancel }, "oauthClientId"));
161
+ case 'oauthAuthEndpoint':
162
+ return (_jsx(TextInput, { label: "Authorization Endpoint URL", defaultValue: oauth.authorizationEndpoint, onSubmit: value => submitOAuth('authorizationEndpoint', value), onCancel: onCancel }, "oauthAuthEndpoint"));
163
+ case 'oauthTokenEndpoint':
164
+ return (_jsx(TextInput, { label: "Token Endpoint URL", defaultValue: oauth.tokenEndpoint, onSubmit: value => submitOAuth('tokenEndpoint', value), onCancel: onCancel }, "oauthTokenEndpoint"));
165
+ case 'oauthScopes':
166
+ return (_jsx(TextInput, { label: "Scopes (space-separated, optional)", defaultValue: oauth.scopes, onSubmit: value => submitOAuth('scopes', value), onCancel: onCancel }, "oauthScopes"));
167
+ case 'oauthClientSecret':
168
+ return (_jsx(TextInput, { label: "Client Secret (OS keychain \u2014 leave empty for a public/PKCE client)", type: "password", defaultValue: oauth.clientSecret, onSubmit: value => submitOAuth('clientSecret', value), onCancel: onCancel }, "oauthClientSecret"));
169
+ case 'sessionLoginUrl':
170
+ return (_jsx(TextInput, { label: "Login URL (opened in a browser; blank = site root)", defaultValue: properties.sessionLoginUrl ??
171
+ (properties.domain ? `https://${properties.domain}/` : ''), onSubmit: value => submitProperty('sessionLoginUrl', value), onCancel: onCancel }, "sessionLoginUrl"));
58
172
  case 'useHTTP':
59
- return (_jsx(BooleanInput, { label: "Use HTTP for deployment? (y/n)", defaultValue: properties.useHTTPForDevDeploy, onSubmit: (value) => handleNext('useHTTPForDevDeploy', value) }, "useHTTP"));
173
+ return (_jsx(BooleanInput, { label: "Use HTTP for deployment? (y/n)", defaultValue: properties.useHTTPForDevDeploy ?? false, onSubmit: value => submitProperty('useHTTPForDevDeploy', value) }, "useHTTP"));
60
174
  default:
61
175
  return null;
62
176
  }
63
177
  };
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() })] }));
178
+ 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 && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Whitelist this redirect URI on the OAuth2 client: http://127.0.0.1:", redirectPort, "/callback"] }) })), _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() })] }));
179
+ }
180
+ function MethodSelect({ defaultValue, onSubmit, }) {
181
+ const [index, setIndex] = useState(() => Math.max(0, AUTH_METHODS.findIndex(m => m.value === defaultValue)));
182
+ useInput((_input, key) => {
183
+ if (key.upArrow) {
184
+ setIndex(p => (p === 0 ? AUTH_METHODS.length - 1 : p - 1));
185
+ }
186
+ else if (key.downArrow) {
187
+ setIndex(p => (p === AUTH_METHODS.length - 1 ? 0 : p + 1));
188
+ }
189
+ else if (key.return) {
190
+ onSubmit(AUTH_METHODS[index].value);
191
+ }
192
+ });
193
+ 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
194
  }
66
195
  function BooleanInput({ label, defaultValue, onSubmit, }) {
67
196
  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);
@@ -79,5 +79,5 @@ export function MainMenu({ project, onSelect }) {
79
79
  onSelect(selectedItem.value);
80
80
  }
81
81
  });
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" }) })] }));
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: 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
83
  }
@@ -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 {};
@@ -1,16 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
- import { getAppType, migrateLegacyPassword, } from '../utils/project-detection.js';
4
+ import { getAppType, localizedText, migrateLegacyPassword, getPackageJsonSyncChanges, syncDevPropertiesToPackageJson, readSvcConfig, writeSvcConfig, writeDevProperties, } from '../utils/project-detection.js';
5
5
  import { ProcessRunner } from '../utils/process-runner.js';
6
6
  import { ProcessOutputComponent } from './ProcessOutput.js';
7
7
  import { StatusIndicator } from './StatusIndicator.js';
8
8
  import { DevPropertiesForm } from './DevPropertiesForm.js';
9
9
  import { SigningPropertiesForm } from './SigningPropertiesForm.js';
10
- export function SetupFlow({ project, onComplete }) {
10
+ export function SetupFlow({ project, onReload, onComplete }) {
11
11
  const [step, setStep] = useState('check-node-modules');
12
12
  const [runner, setRunner] = useState(null);
13
13
  const [commandStatus, setCommandStatus] = useState('running');
14
+ const [syncChanges, setSyncChanges] = useState([]);
15
+ const [syncDecision, setSyncDecision] = useState(false);
16
+ // Set when the user picks OAuth2 for an existing config, so the form opens in
17
+ // the OAuth2 branch without mutating the shared project object.
18
+ const [pendingAuthMethod, setPendingAuthMethod] = useState(undefined);
14
19
  const appType = getAppType(project.manifest);
15
20
  // Auto-advance through checks
16
21
  useEffect(() => {
@@ -27,14 +32,36 @@ export function SetupFlow({ project, onComplete }) {
27
32
  if (project.hasLegacyPassword) {
28
33
  setStep('confirm-password-migration');
29
34
  }
35
+ else if (project.devProperties?.authMethod === undefined) {
36
+ setStep('confirm-auth-method');
37
+ }
30
38
  else {
31
- setStep('check-signing-properties');
39
+ setStep('check-package-sync');
32
40
  }
33
41
  }
34
42
  else {
35
43
  setStep('confirm-dev-setup');
36
44
  }
37
45
  }
46
+ else if (step === 'check-package-sync') {
47
+ const preference = readSvcConfig(project.root).syncPackageJson;
48
+ const properties = project.devProperties;
49
+ const changes = preference !== false && properties
50
+ ? getPackageJsonSyncChanges(project.root, properties)
51
+ : [];
52
+ if (changes.length === 0 || !properties) {
53
+ setStep('check-signing-properties');
54
+ }
55
+ else if (preference === true) {
56
+ syncDevPropertiesToPackageJson(project.root, properties);
57
+ onReload();
58
+ setStep('check-signing-properties');
59
+ }
60
+ else {
61
+ setSyncChanges(changes);
62
+ setStep('confirm-package-sync');
63
+ }
64
+ }
38
65
  else if (step === 'check-signing-properties') {
39
66
  if (project.hasSigningProperties) {
40
67
  setStep('show-info');
@@ -84,6 +111,29 @@ export function SetupFlow({ project, onComplete }) {
84
111
  else if (step === 'confirm-password-migration') {
85
112
  if (input === 'y' || input === 'Y') {
86
113
  migrateLegacyPassword(project);
114
+ // The file was rewritten (plaintext stripped, password moved to
115
+ // keychain) — re-detect so hasLegacyPassword/password reflect that.
116
+ onReload();
117
+ setStep('check-package-sync');
118
+ }
119
+ else if (input === 'n' || input === 'N') {
120
+ setStep('check-package-sync');
121
+ }
122
+ }
123
+ else if (step === 'confirm-package-sync') {
124
+ if (['y', 'Y', 'n', 'N'].includes(input)) {
125
+ const accepted = input.toLowerCase() === 'y';
126
+ if (accepted && project.devProperties) {
127
+ syncDevPropertiesToPackageJson(project.root, project.devProperties);
128
+ onReload();
129
+ }
130
+ setSyncDecision(accepted);
131
+ setStep('confirm-save-sync-choice');
132
+ }
133
+ }
134
+ else if (step === 'confirm-save-sync-choice') {
135
+ if (input === 'y' || input === 'Y') {
136
+ writeSvcConfig(project.root, { syncPackageJson: syncDecision });
87
137
  setStep('check-signing-properties');
88
138
  }
89
139
  else if (input === 'n' || input === 'N') {
@@ -98,21 +148,46 @@ export function SetupFlow({ project, onComplete }) {
98
148
  setStep('show-info');
99
149
  }
100
150
  }
151
+ else if (step === 'confirm-auth-method') {
152
+ if (input === 'o' || input === 'O') {
153
+ // Choosing OAuth2 needs endpoints — collect them in the full form.
154
+ setPendingAuthMethod('oauth2');
155
+ setStep('setup-dev-properties');
156
+ }
157
+ else if (input === 'c' || input === 'C') {
158
+ setPendingAuthMethod('cookie');
159
+ setStep('setup-dev-properties');
160
+ }
161
+ else if (['b', 'B', '\r'].includes(input)) {
162
+ if (project.devProperties) {
163
+ writeDevProperties(project.root, {
164
+ ...project.devProperties,
165
+ authMethod: 'basic',
166
+ });
167
+ onReload();
168
+ }
169
+ setStep('check-package-sync');
170
+ }
171
+ }
101
172
  });
102
173
  // Setup Dev Properties Form
103
174
  if (step === 'setup-dev-properties') {
104
- return (_jsx(DevPropertiesForm, { projectRoot: project.root, initialProperties: project.devProperties, packageJson: project.packageJson, onComplete: () => {
105
- // Manually update project state locally if possible, or just proceed
106
- // Since we can't easily update 'project' prop from here without reloading,
107
- // we just move to next step. The file is written.
108
- project.hasDevProperties = true; // Optimization/Hack to pass check
109
- setStep('check-signing-properties');
175
+ return (_jsx(DevPropertiesForm, { projectRoot: project.root, initialProperties: pendingAuthMethod && project.devProperties
176
+ ? { ...project.devProperties, authMethod: pendingAuthMethod }
177
+ : project.devProperties, packageJson: project.packageJson, onComplete: () => {
178
+ // Re-detect from disk/keychain so devProperties (incl. the keychain
179
+ // password) populate in memory otherwise the rest of this flow and
180
+ // the menu would see stale state until the CLI is restarted.
181
+ onReload();
182
+ setStep('check-package-sync');
110
183
  }, onCancel: () => setStep('check-signing-properties') }));
111
184
  }
112
185
  // Setup Signing Properties Form
113
186
  if (step === 'setup-signing-properties') {
114
187
  return (_jsx(SigningPropertiesForm, { projectRoot: project.root, onComplete: () => {
115
- project.hasSigningProperties = true; // Optimization/Hack
188
+ // Re-detect so signing credentials are reflected in memory before
189
+ // the info screen / menu render.
190
+ onReload();
116
191
  setStep('show-info');
117
192
  }, onCancel: () => setStep('show-info') }));
118
193
  }
@@ -132,13 +207,27 @@ export function SetupFlow({ project, onComplete }) {
132
207
  if (step === 'confirm-password-migration') {
133
208
  return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 Plaintext password found in .dev_properties.json" }) }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { children: "Move it to the OS keychain and remove it from the file? (y/n)" }), _jsx(Text, { dimColor: true, children: "Recommended \u2014 storing passwords in project files is insecure." })] })] }));
134
209
  }
210
+ // Ask which auth method an existing config should use (setting is missing)
211
+ if (step === 'confirm-auth-method') {
212
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 Deploy authentication method not set" }) }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { children: "Which method should deploys use? [B]asic / [O]Auth2 / [C]ookie" }), _jsx(Text, { dimColor: true, children: "B = Basic (default), O = OAuth2 bearer, C = session cookie (SSO)." })] })] }));
213
+ }
214
+ // Confirm package.json sync
215
+ if (step === 'confirm-package-sync') {
216
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 package.json is out of sync with .dev_properties.json" }) }), _jsx(Box, { marginBottom: 1, flexDirection: "column", marginLeft: 2, children: syncChanges.map(change => (_jsxs(Box, { children: [_jsx(Text, { color: change.from === undefined ? 'green' : 'yellow', children: change.from === undefined ? '+ ' : '~ ' }), _jsxs(Text, { bold: true, children: [change.key, ": "] }), change.from !== undefined && (_jsxs(Text, { dimColor: true, children: [change.from, " \u2192 "] })), _jsx(Text, { children: change.to })] }, change.key))) }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { children: "Update package.json from .dev_properties.json? (y/n)" }), _jsx(Text, { dimColor: true, children: "sitevision-scripts reads these fields from package.json." })] })] }));
217
+ }
218
+ // Offer to persist the sync decision in .svcconfig
219
+ if (step === 'confirm-save-sync-choice') {
220
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { children: "Remember this choice in .svcconfig? (y/n)" }), _jsx(Text, { dimColor: true, children: syncDecision
221
+ ? 'svc will update package.json automatically from now on.'
222
+ : 'svc will stop asking about package.json sync.' })] })] }));
223
+ }
135
224
  // Confirm signing setup
136
225
  if (step === 'confirm-signing-setup') {
137
226
  return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 signing credentials not configured" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: "Signing credentials are required for signing apps on developer.sitevision.se" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: "Would you like to set up signing credentials? (y/n)" }) })] }));
138
227
  }
139
228
  // Show info
140
229
  if (step === 'show-info') {
141
- 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' })] })] }), 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 configured" }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
230
+ 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' })] })] }), 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 configured" }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
142
231
  }
143
232
  return null;
144
233
  }
@@ -11,16 +11,22 @@ export type AppType = 'WebApp' | 'Widget' | 'RESTApp';
11
11
  * Simplified app type for internal use
12
12
  */
13
13
  export type SimpleAppType = 'web' | 'widget' | 'rest';
14
+ /**
15
+ * A manifest text field that may be a plain string or a localized object keyed
16
+ * by language code, e.g. `{sv: 'Namn', en: 'Name'}`. Sitevision allows either
17
+ * form for human-facing fields like `name` and `description`.
18
+ */
19
+ export type LocalizedString = string | Record<string, string>;
14
20
  /**
15
21
  * Sitevision app manifest (manifest.json)
16
22
  */
17
23
  export interface SitevisionManifest {
18
24
  id: string;
19
- name: string;
25
+ name: LocalizedString;
20
26
  version: string;
21
27
  type: AppType;
22
28
  bundled?: boolean;
23
- description?: string;
29
+ description?: LocalizedString;
24
30
  author?: string;
25
31
  helpUrl?: string;
26
32
  license?: string;
@@ -42,6 +48,24 @@ export interface DevProperties {
42
48
  useHTTPForDevDeploy?: boolean;
43
49
  signingUsername?: string;
44
50
  certificateName?: string;
51
+ authMethod?: 'basic' | 'oauth2' | 'cookie';
52
+ oauth2?: OAuth2Config;
53
+ sessionLoginUrl?: string;
54
+ accessToken?: string;
55
+ sessionCookie?: string;
56
+ }
57
+ /**
58
+ * OAuth2 provider config for bearer-token deploys.
59
+ *
60
+ * Deliberately secret-free: the client secret and refresh token live in the OS
61
+ * keychain, so nothing here is unsafe to write to .dev_properties.json.
62
+ */
63
+ export interface OAuth2Config {
64
+ authorizationEndpoint: string;
65
+ tokenEndpoint: string;
66
+ clientId: string;
67
+ scopes?: string[];
68
+ redirectPort?: number;
45
69
  }
46
70
  /**
47
71
  * Signing credentials (password is runtime-only, not persisted)
@@ -59,7 +83,9 @@ export interface DeployConfig {
59
83
  siteName: string;
60
84
  addonName: string;
61
85
  username: string;
62
- password: string;
86
+ password?: string;
87
+ accessToken?: string;
88
+ sessionCookie?: string;
63
89
  useHTTP?: boolean;
64
90
  }
65
91
  /**
@@ -143,6 +169,7 @@ export interface DeployResponse {
143
169
  executableId?: string;
144
170
  message?: string;
145
171
  error?: string;
172
+ authExpired?: boolean;
146
173
  }
147
174
  /**
148
175
  * API response from addon creation
@@ -151,6 +178,7 @@ export interface CreateAddonResponse {
151
178
  success: boolean;
152
179
  addonId?: string;
153
180
  error?: string;
181
+ authExpired?: boolean;
154
182
  }
155
183
  /**
156
184
  * API response from activation
@@ -158,6 +186,7 @@ export interface CreateAddonResponse {
158
186
  export interface ActivationResponse {
159
187
  success: boolean;
160
188
  error?: string;
189
+ authExpired?: boolean;
161
190
  }
162
191
  /**
163
192
  * Build mode
@@ -0,0 +1,19 @@
1
+ /**
2
+ * JSONC helpers.
3
+ *
4
+ * Sitevision's own manifest documentation shows manifest.json with line and
5
+ * block comments (e.g. `"name": { // Multilingual-manifest requires SV 10.1`),
6
+ * so real-world manifests copied from the docs contain them. Strict JSON.parse
7
+ * rejects those, so we strip comments before parsing.
8
+ */
9
+ /**
10
+ * Remove line comments (//...) and block comments from a JSON string, leaving
11
+ * everything inside string literals untouched (so values like
12
+ * `"https://example.com"` survive).
13
+ */
14
+ export declare function stripJsonComments(input: string): string;
15
+ /**
16
+ * Parse a JSON string that may contain comments (JSONC). Throws the underlying
17
+ * SyntaxError if the content is invalid even after comments are removed.
18
+ */
19
+ export declare function parseJsonc<T>(input: string): T;