sitevision-cli 1.0.0-beta.3 → 1.0.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/dist/app.d.ts +1 -1
- package/dist/app.js +59 -8
- package/dist/cli.js +125 -40
- package/dist/commands/build.js +1 -1
- package/dist/commands/deploy.d.ts +2 -2
- package/dist/commands/deploy.js +137 -28
- package/dist/commands/dev.d.ts +8 -10
- package/dist/commands/dev.js +70 -371
- package/dist/commands/watch.js +5 -23
- package/dist/components/AnimatedLogo.js +8 -2
- package/dist/components/AuthLoginScreen.d.ts +21 -0
- package/dist/components/AuthLoginScreen.js +90 -0
- package/dist/components/DevPropertiesForm.d.ts +2 -1
- package/dist/components/DevPropertiesForm.js +198 -33
- package/dist/components/MainMenu.js +5 -0
- package/dist/components/PasswordInput.js +2 -1
- package/dist/components/SetupFlow.d.ts +2 -1
- package/dist/components/SetupFlow.js +99 -10
- package/dist/shell/AddonPicker.d.ts +14 -0
- package/dist/shell/AddonPicker.js +54 -0
- package/dist/shell/Changelog.d.ts +10 -0
- package/dist/shell/Changelog.js +44 -0
- package/dist/shell/CommandPalette.d.ts +12 -0
- package/dist/shell/CommandPalette.js +63 -0
- package/dist/shell/ConfigForm.d.ts +36 -0
- package/dist/shell/ConfigForm.js +558 -0
- package/dist/shell/Frame.d.ts +59 -0
- package/dist/shell/Frame.js +135 -0
- package/dist/shell/Help.d.ts +9 -0
- package/dist/shell/Help.js +95 -0
- package/dist/shell/Popover.d.ts +12 -0
- package/dist/shell/Popover.js +15 -0
- package/dist/shell/Settings.d.ts +6 -0
- package/dist/shell/Settings.js +96 -0
- package/dist/shell/Shell.d.ts +10 -0
- package/dist/shell/Shell.js +622 -0
- package/dist/shell/Tabs.d.ts +36 -0
- package/dist/shell/Tabs.js +90 -0
- package/dist/shell/actions.d.ts +48 -0
- package/dist/shell/actions.js +0 -0
- package/dist/types/index.d.ts +37 -3
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.js +14 -0
- package/dist/utils/environments.d.ts +20 -0
- package/dist/utils/environments.js +74 -0
- package/dist/utils/i18n.d.ts +12 -0
- package/dist/utils/i18n.js +314 -0
- package/dist/utils/jsonc.d.ts +19 -0
- package/dist/utils/jsonc.js +74 -0
- package/dist/utils/keychain.d.ts +9 -0
- package/dist/utils/keychain.js +54 -0
- package/dist/utils/oauth2-auth.d.ts +64 -0
- package/dist/utils/oauth2-auth.js +242 -0
- package/dist/utils/password-prompt.d.ts +5 -0
- package/dist/utils/password-prompt.js +28 -0
- package/dist/utils/process-runner.d.ts +13 -4
- package/dist/utils/process-runner.js +63 -37
- package/dist/utils/project-detection.d.ts +90 -4
- package/dist/utils/project-detection.js +389 -63
- package/dist/utils/session-cookie-auth.d.ts +35 -0
- package/dist/utils/session-cookie-auth.js +99 -0
- package/dist/utils/sitevision-api.d.ts +72 -5
- package/dist/utils/sitevision-api.js +220 -38
- package/dist/utils/sitevision-scripts-runner.d.ts +2 -1
- package/dist/utils/sitevision-scripts-runner.js +18 -7
- package/dist/utils/tasks.d.ts +50 -0
- package/dist/utils/tasks.js +439 -0
- package/dist/utils/workspace.d.ts +17 -0
- package/dist/utils/workspace.js +67 -0
- package/package.json +5 -2
- package/readme.md +103 -121
package/dist/commands/deploy.js
CHANGED
|
@@ -3,29 +3,127 @@ import React from 'react';
|
|
|
3
3
|
import { render, Box, Text, useInput } from 'ink';
|
|
4
4
|
import { StatusIndicator } from '../components/StatusIndicator.js';
|
|
5
5
|
import { deployApp, deployProduction } from '../utils/sitevision-api.js';
|
|
6
|
-
import {
|
|
6
|
+
import { getDeployZipPath, getSignedZipPath, getAppType, } from '../utils/project-detection.js';
|
|
7
7
|
import { zipExists } from '../utils/zip.js';
|
|
8
8
|
import { promptPassword, promptYesNo } from '../utils/password-prompt.js';
|
|
9
|
-
import { setDeployPassword } from '../utils/keychain.js';
|
|
10
|
-
|
|
9
|
+
import { setDeployPassword, deleteSessionCookie, deleteOAuth2RefreshToken, } from '../utils/keychain.js';
|
|
10
|
+
import { resolveOAuth2AccessToken } from '../utils/oauth2-auth.js';
|
|
11
|
+
import { AuthLoginScreen } from '../components/AuthLoginScreen.js';
|
|
12
|
+
export function DeployScreen({ projectRoot, manifest, devProperties, force, production, activate, onBack, onRetryCredentials, onChangeAuthMethod, }) {
|
|
11
13
|
const [state, setState] = React.useState({
|
|
12
14
|
status: 'deploying',
|
|
13
15
|
message: production ? 'Deploying to production...' : 'Deploying to dev...',
|
|
14
16
|
});
|
|
17
|
+
// 'init' resolves cached credentials, 'login' shows the Ink login screen,
|
|
18
|
+
// 'deploy' runs the upload. Token/cookie login now happens here, so both the
|
|
19
|
+
// TUI and the standalone command reach it.
|
|
20
|
+
const [phase, setPhase] = React.useState('init');
|
|
21
|
+
const [credential, setCredential] = React.useState({
|
|
22
|
+
accessToken: devProperties.accessToken,
|
|
23
|
+
sessionCookie: devProperties.sessionCookie,
|
|
24
|
+
});
|
|
25
|
+
const deployStartedRef = React.useRef(false);
|
|
26
|
+
const authMethod = devProperties.authMethod ?? 'basic';
|
|
27
|
+
// OAuth2 and cookie can re-authenticate in-place; basic re-prompts via the
|
|
28
|
+
// parent (TUI password entry).
|
|
29
|
+
const canRelogin = authMethod === 'oauth2' || authMethod === 'cookie';
|
|
30
|
+
// Discard the stored credential and force a fresh login. This is the
|
|
31
|
+
// "retry with new credentials" action for token/cookie auth — the usual fix
|
|
32
|
+
// when a session/token has expired (Sitevision reports that as a 400, not a
|
|
33
|
+
// 401, so it isn't auto-cleared).
|
|
34
|
+
const retryWithFreshLogin = () => {
|
|
35
|
+
const { domain, username } = devProperties;
|
|
36
|
+
if (authMethod === 'cookie' && domain && username) {
|
|
37
|
+
deleteSessionCookie(domain, username);
|
|
38
|
+
devProperties.sessionCookie = undefined;
|
|
39
|
+
}
|
|
40
|
+
else if (authMethod === 'oauth2' &&
|
|
41
|
+
domain &&
|
|
42
|
+
devProperties.oauth2?.clientId) {
|
|
43
|
+
deleteOAuth2RefreshToken(domain, devProperties.oauth2.clientId);
|
|
44
|
+
devProperties.accessToken = undefined;
|
|
45
|
+
}
|
|
46
|
+
setCredential({});
|
|
47
|
+
deployStartedRef.current = false;
|
|
48
|
+
setState({
|
|
49
|
+
status: 'deploying',
|
|
50
|
+
message: production
|
|
51
|
+
? 'Deploying to production...'
|
|
52
|
+
: 'Deploying to dev...',
|
|
53
|
+
});
|
|
54
|
+
setPhase('login');
|
|
55
|
+
};
|
|
15
56
|
useInput((input, key) => {
|
|
16
57
|
if (state.status !== 'deploying') {
|
|
17
58
|
if (onBack && (key.escape || input === 'q')) {
|
|
18
59
|
onBack();
|
|
19
60
|
}
|
|
20
|
-
if (
|
|
21
|
-
|
|
61
|
+
if (state.status === 'error' && input === 'r') {
|
|
62
|
+
if (canRelogin) {
|
|
63
|
+
retryWithFreshLogin();
|
|
64
|
+
}
|
|
65
|
+
else if (onRetryCredentials) {
|
|
66
|
+
onRetryCredentials();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (state.status === 'error' && input === 'm' && onChangeAuthMethod) {
|
|
70
|
+
onChangeAuthMethod();
|
|
22
71
|
}
|
|
23
72
|
}
|
|
24
73
|
});
|
|
74
|
+
// Decide once whether we can deploy straight away or must log in first.
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
if (authMethod === 'basic' || devProperties.sessionCookie) {
|
|
77
|
+
setPhase('deploy');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (authMethod === 'cookie') {
|
|
81
|
+
// env/keychain cookie is already loaded in devProperties; none here.
|
|
82
|
+
setPhase('login');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (authMethod === 'oauth2') {
|
|
86
|
+
if (devProperties.accessToken) {
|
|
87
|
+
setPhase('deploy');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
void (async () => {
|
|
91
|
+
const token = await resolveOAuth2AccessToken(devProperties);
|
|
92
|
+
if (token) {
|
|
93
|
+
setCredential({ accessToken: token });
|
|
94
|
+
setPhase('deploy');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
setPhase('login');
|
|
98
|
+
}
|
|
99
|
+
})();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
setPhase('deploy');
|
|
103
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
+
}, []);
|
|
25
105
|
React.useEffect(() => {
|
|
106
|
+
if (phase !== 'deploy' || deployStartedRef.current)
|
|
107
|
+
return;
|
|
108
|
+
deployStartedRef.current = true;
|
|
26
109
|
async function runDeploy() {
|
|
27
110
|
try {
|
|
28
111
|
const appType = getAppType(manifest);
|
|
112
|
+
// Credential resolved in the init effect / login screen.
|
|
113
|
+
const { accessToken, sessionCookie } = credential;
|
|
114
|
+
// A stale session fails without a clean 401 — clear the stored cookie
|
|
115
|
+
// so the next run re-authenticates. Skip when SITEVISION_SESSION_COOKIE
|
|
116
|
+
// is set: detection re-reads it first, so clearing would just replay
|
|
117
|
+
// the same dead cookie in a loop.
|
|
118
|
+
const clearStaleCookie = (result) => {
|
|
119
|
+
if (result.authExpired &&
|
|
120
|
+
authMethod === 'cookie' &&
|
|
121
|
+
!process.env['SITEVISION_SESSION_COOKIE'] &&
|
|
122
|
+
devProperties.domain &&
|
|
123
|
+
devProperties.username) {
|
|
124
|
+
deleteSessionCookie(devProperties.domain, devProperties.username);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
29
127
|
if (production) {
|
|
30
128
|
// Production deployment requires a signed zip
|
|
31
129
|
const signedZipPath = getSignedZipPath(projectRoot, manifest);
|
|
@@ -42,11 +140,14 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
42
140
|
addonName: devProperties.addonName,
|
|
43
141
|
username: devProperties.username,
|
|
44
142
|
password: devProperties.password,
|
|
143
|
+
accessToken,
|
|
144
|
+
sessionCookie,
|
|
45
145
|
useHTTP: devProperties.useHTTPForDevDeploy,
|
|
46
146
|
activate,
|
|
47
147
|
};
|
|
48
148
|
const result = await deployProduction(signedZipPath, config, appType);
|
|
49
149
|
if (!result.success) {
|
|
150
|
+
clearStaleCookie(result);
|
|
50
151
|
setState({
|
|
51
152
|
status: 'error',
|
|
52
153
|
error: result.error || 'Deployment failed',
|
|
@@ -60,8 +161,7 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
60
161
|
});
|
|
61
162
|
}
|
|
62
163
|
else {
|
|
63
|
-
|
|
64
|
-
const zipPath = getZipPath(projectRoot, manifest);
|
|
164
|
+
const zipPath = getDeployZipPath(projectRoot, manifest);
|
|
65
165
|
if (!zipExists(zipPath)) {
|
|
66
166
|
setState({
|
|
67
167
|
status: 'error',
|
|
@@ -75,10 +175,13 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
75
175
|
addonName: devProperties.addonName,
|
|
76
176
|
username: devProperties.username,
|
|
77
177
|
password: devProperties.password,
|
|
178
|
+
accessToken,
|
|
179
|
+
sessionCookie,
|
|
78
180
|
useHTTP: devProperties.useHTTPForDevDeploy,
|
|
79
181
|
};
|
|
80
182
|
const result = await deployApp(zipPath, config, appType, force);
|
|
81
183
|
if (!result.success) {
|
|
184
|
+
clearStaleCookie(result);
|
|
82
185
|
setState({
|
|
83
186
|
status: 'error',
|
|
84
187
|
error: result.error || 'Deployment failed',
|
|
@@ -100,20 +203,30 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
100
203
|
}
|
|
101
204
|
}
|
|
102
205
|
runDeploy();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
devProperties
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
206
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
207
|
+
}, [phase]);
|
|
208
|
+
if (phase === 'login' && state.status !== 'error') {
|
|
209
|
+
return (_jsx(AuthLoginScreen, { method: (devProperties.authMethod ?? 'basic') === 'cookie'
|
|
210
|
+
? 'cookie'
|
|
211
|
+
: 'oauth2', devProperties: devProperties, onComplete: cred => {
|
|
212
|
+
setCredential(cred);
|
|
213
|
+
setPhase('deploy');
|
|
214
|
+
}, onError: message => {
|
|
215
|
+
setState({ status: 'error', error: message });
|
|
216
|
+
}, onCancel: () => {
|
|
217
|
+
if (onBack) {
|
|
218
|
+
onBack();
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
setState({ status: 'error', error: 'Login cancelled.' });
|
|
222
|
+
}
|
|
223
|
+
} }));
|
|
224
|
+
}
|
|
112
225
|
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(StatusIndicator, { status: state.status === 'deploying' ? 'running' : state.status, label: state.status === 'deploying'
|
|
113
226
|
? 'Deploying'
|
|
114
227
|
: state.status === 'success'
|
|
115
228
|
? 'Deployed'
|
|
116
|
-
: 'Failed', message: state.message }) }), state.status === 'success' && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { color: "green", children: [production ? 'Production deployment' : 'Dev deployment', " complete"] }), state.executableId && (_jsxs(Text, { dimColor: true, children: ["Executable ID: ", state.executableId] })), force && _jsx(Text, { dimColor: true, children: "(Force mode - overwrote existing)" }), activate && production && _jsx(Text, { dimColor: true, children: "(Activated)" })] })), state.status === 'error' && state.error && (
|
|
229
|
+
: 'Failed', message: state.message }) }), state.status === 'success' && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { color: "green", children: [production ? 'Production deployment' : 'Dev deployment', " complete"] }), state.executableId && (_jsxs(Text, { dimColor: true, children: ["Executable ID: ", state.executableId] })), force && _jsx(Text, { dimColor: true, children: "(Force mode - overwrote existing)" }), activate && production && _jsx(Text, { dimColor: true, children: "(Activated)" })] })), state.status === 'error' && state.error && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "red", children: state.error }), canRelogin && (_jsx(Text, { color: "yellow", children: "This can happen when your session or token has expired \u2014 log in again to get fresh credentials." }))] })), state.status !== 'deploying' && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [state.status === 'error' && canRelogin && (_jsx(Text, { dimColor: true, children: "Press r to log in again with fresh credentials" })), state.status === 'error' && !canRelogin && onRetryCredentials && (_jsx(Text, { dimColor: true, children: "Press r to retry with new credentials" })), state.status === 'error' && onChangeAuthMethod && (_jsx(Text, { dimColor: true, children: "Press m to change auth method" })), onBack && _jsx(Text, { dimColor: true, children: "Press q or Esc to return to menu" })] }))] }));
|
|
117
230
|
}
|
|
118
231
|
export const deployCommand = {
|
|
119
232
|
name: 'deploy',
|
|
@@ -146,8 +259,11 @@ export const deployCommand = {
|
|
|
146
259
|
console.log('Create a .dev_properties.json file with domain, siteName, addonName, and username, then run setup.\n');
|
|
147
260
|
return;
|
|
148
261
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
262
|
+
// Basic auth prompts for a password here; OAuth2 and cookie resolve or log
|
|
263
|
+
// in inside DeployScreen (Ink-native), so both the TUI and this command
|
|
264
|
+
// share one login path. env/--flag token/cookie are already loaded.
|
|
265
|
+
const authMethod = project.devProperties.authMethod ?? 'basic';
|
|
266
|
+
if (authMethod === 'basic' && !project.devProperties.password) {
|
|
151
267
|
const { domain, username } = project.devProperties;
|
|
152
268
|
console.log('');
|
|
153
269
|
const password = await promptPassword(`Deploy password for ${username}@${domain}: `);
|
|
@@ -164,15 +280,8 @@ export const deployCommand = {
|
|
|
164
280
|
const production = Boolean(flags['production']);
|
|
165
281
|
const force = Boolean(flags['force']);
|
|
166
282
|
const activate = Boolean(flags['activate']);
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
if (production &&
|
|
170
|
-
project.hasSigningProperties &&
|
|
171
|
-
project.devProperties.signingUsername) {
|
|
172
|
-
// We already have a signed zip, no need to prompt for password here
|
|
173
|
-
// The sign command should have been run separately
|
|
174
|
-
}
|
|
175
|
-
const { waitUntilExit } = render(_jsx(DeployScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: project.devProperties, force: force, production: production, activate: activate, signingPassword: signingPassword }));
|
|
283
|
+
// Production deploys use the already-signed zip; `sign` is run separately.
|
|
284
|
+
const { waitUntilExit } = render(_jsx(DeployScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: project.devProperties, force: force, production: production, activate: activate }));
|
|
176
285
|
await waitUntilExit();
|
|
177
286
|
},
|
|
178
287
|
};
|
package/dist/commands/dev.d.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type Command } from './types.js';
|
|
3
|
-
import
|
|
3
|
+
import { type DevOptions } from '../utils/tasks.js';
|
|
4
|
+
import type { ProjectInfo, SigningCredentials } from '../types/index.js';
|
|
4
5
|
interface DevScreenProps {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
devProperties?: DevProperties;
|
|
8
|
-
signed: boolean;
|
|
9
|
-
deploy?: boolean;
|
|
10
|
-
signingCredentials?: SigningCredentials;
|
|
11
|
-
onBack?: () => void;
|
|
12
|
-
onRetryCredentials?: () => void;
|
|
6
|
+
project: ProjectInfo;
|
|
7
|
+
options: DevOptions;
|
|
13
8
|
}
|
|
14
|
-
|
|
9
|
+
/** Standalone `svc dev` / `svc watch`: one task, its log, Ctrl+C or q to stop. */
|
|
10
|
+
export declare function DevScreen({ project, options }: DevScreenProps): React.JSX.Element;
|
|
11
|
+
/** Resolve signing credentials for signed dev/watch, prompting if needed. */
|
|
12
|
+
export declare function resolveSigningForCli(project: ProjectInfo): Promise<SigningCredentials | undefined>;
|
|
15
13
|
export declare const devCommand: Command;
|
|
16
14
|
export {};
|