sitevision-cli 1.0.0-beta.9 → 1.0.1
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 +143 -0
- package/LICENSE +21 -0
- package/dist/cli.js +194 -70
- package/dist/commands/TaskScreen.js +32 -0
- package/dist/commands/build.js +6 -185
- package/dist/commands/deploy.js +95 -212
- package/dist/commands/dev.js +87 -416
- package/dist/commands/info.js +1 -1
- package/dist/commands/setup-signing.js +50 -33
- package/dist/commands/sign.js +9 -86
- package/dist/commands/use-finish.js +17 -0
- package/dist/commands/watch.js +5 -31
- package/dist/components/AnimatedLogo.js +8 -2
- package/dist/components/AuthLoginScreen.js +33 -15
- package/dist/components/PasswordInput.js +2 -1
- package/dist/scaffold/inquirer-stub.js +119 -0
- package/dist/scaffold/register.js +15 -0
- package/dist/shell/AddonPicker.js +54 -0
- package/dist/shell/Changelog.js +50 -0
- package/dist/shell/CommandPalette.js +63 -0
- package/dist/shell/ConfigForm.js +703 -0
- package/dist/shell/Frame.js +138 -0
- package/dist/shell/Help.js +96 -0
- package/dist/shell/Popover.js +15 -0
- package/dist/shell/Settings.js +149 -0
- package/dist/shell/Shell.js +953 -0
- package/dist/shell/Tabs.js +96 -0
- package/dist/shell/actions.js +493 -0
- package/dist/utils/config.js +62 -2
- package/dist/utils/create-app.js +231 -0
- package/dist/utils/debug.js +32 -0
- package/dist/utils/environments.js +74 -0
- package/dist/utils/i18n.js +369 -0
- package/dist/utils/jsonc.js +2 -1
- package/dist/utils/keychain.js +54 -6
- package/dist/utils/oauth2-auth.js +146 -43
- package/dist/utils/password-prompt.js +37 -3
- package/dist/utils/process-runner.js +77 -38
- package/dist/utils/project-detection.js +457 -112
- package/dist/utils/session-cookie-auth.js +10 -6
- package/dist/utils/sitevision-api.js +134 -25
- package/dist/utils/sitevision-scripts-runner.js +50 -22
- package/dist/utils/tasks.js +510 -0
- package/dist/utils/version-check.js +3 -3
- package/dist/utils/webpack-runner.js +9 -5
- package/dist/utils/workspace.js +105 -0
- package/dist/utils/zip.js +18 -19
- package/package.json +29 -5
- package/readme.md +117 -141
- package/dist/app.d.ts +0 -6
- package/dist/app.js +0 -307
- package/dist/cli.d.ts +0 -2
- package/dist/commands/build.d.ts +0 -12
- package/dist/commands/deploy.d.ts +0 -16
- package/dist/commands/dev.d.ts +0 -16
- package/dist/commands/index.d.ts +0 -4
- package/dist/commands/info.d.ts +0 -2
- package/dist/commands/setup-signing.d.ts +0 -2
- package/dist/commands/sign.d.ts +0 -14
- package/dist/commands/types.d.ts +0 -18
- package/dist/commands/watch.d.ts +0 -2
- package/dist/components/AnimatedLogo.d.ts +0 -10
- package/dist/components/AuthLoginScreen.d.ts +0 -21
- package/dist/components/DevPropertiesForm.d.ts +0 -10
- package/dist/components/DevPropertiesForm.js +0 -210
- package/dist/components/InfoScreen.d.ts +0 -7
- package/dist/components/InfoScreen.js +0 -14
- package/dist/components/KeychainPasswordChoice.d.ts +0 -8
- package/dist/components/KeychainPasswordChoice.js +0 -30
- package/dist/components/MainMenu.d.ts +0 -7
- package/dist/components/MainMenu.js +0 -83
- package/dist/components/PasswordInput.d.ts +0 -10
- package/dist/components/ProcessOutput.d.ts +0 -6
- package/dist/components/ProcessOutput.js +0 -30
- package/dist/components/SetupFlow.d.ts +0 -8
- package/dist/components/SetupFlow.js +0 -233
- package/dist/components/SigningPropertiesForm.d.ts +0 -7
- package/dist/components/SigningPropertiesForm.js +0 -38
- package/dist/components/StatusIndicator.d.ts +0 -8
- package/dist/components/TextInput.d.ts +0 -10
- package/dist/components/TextInput.js +0 -29
- package/dist/components/WelcomeScreen.d.ts +0 -14
- package/dist/types/index.d.ts +0 -284
- package/dist/utils/branding.d.ts +0 -28
- package/dist/utils/config.d.ts +0 -17
- package/dist/utils/jsonc.d.ts +0 -19
- package/dist/utils/keychain.d.ts +0 -15
- package/dist/utils/oauth2-auth.d.ts +0 -27
- package/dist/utils/password-prompt.d.ts +0 -14
- package/dist/utils/process-runner.d.ts +0 -22
- package/dist/utils/project-detection.d.ts +0 -151
- package/dist/utils/session-cookie-auth.d.ts +0 -34
- package/dist/utils/signing-password.d.ts +0 -12
- package/dist/utils/signing-step.d.ts +0 -15
- package/dist/utils/signing-step.js +0 -16
- package/dist/utils/sitevision-api.d.ts +0 -112
- package/dist/utils/sitevision-scripts-runner.d.ts +0 -83
- package/dist/utils/version-check.d.ts +0 -9
- package/dist/utils/webpack-runner.d.ts +0 -76
- package/dist/utils/zip.d.ts +0 -71
package/dist/commands/build.js
CHANGED
|
@@ -1,192 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { WebpackRunner, hasLocalWebpackConfig } from '../utils/webpack-runner.js';
|
|
6
|
-
import { hasSitevisionScripts, runSitevisionScriptsBuild, getDelegatedZipPath, checkSitevisionScriptsCompatibility, } from '../utils/sitevision-scripts-runner.js';
|
|
7
|
-
import { copyStaticToBuild, copySrcToBuild, cleanBuild, formatFileSize, createBuildZip, getZipSize, zipExists, } from '../utils/zip.js';
|
|
8
|
-
import { isBundledApp, getAppType, getFullAppId, } from '../utils/project-detection.js';
|
|
9
|
-
export function BuildScreen({ projectRoot, manifest, createZip = true, onBack, }) {
|
|
10
|
-
const [state, setState] = React.useState({
|
|
11
|
-
status: 'cleaning',
|
|
12
|
-
message: 'Cleaning build directory...',
|
|
13
|
-
});
|
|
14
|
-
useInput((input, key) => {
|
|
15
|
-
if (onBack &&
|
|
16
|
-
(key.escape || input === 'q') &&
|
|
17
|
-
(state.status === 'success' || state.status === 'error')) {
|
|
18
|
-
onBack();
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
const isBundled = isBundledApp(manifest);
|
|
22
|
-
React.useEffect(() => {
|
|
23
|
-
async function runBuild() {
|
|
24
|
-
try {
|
|
25
|
-
// Step 1: Clean build directory
|
|
26
|
-
setState({ status: 'cleaning', message: 'Cleaning build directory...' });
|
|
27
|
-
cleanBuild(projectRoot);
|
|
28
|
-
// Step 2: Build or copy files
|
|
29
|
-
if (isBundled && !hasLocalWebpackConfig(projectRoot)) {
|
|
30
|
-
// No project-local webpack config: delegate the whole build to
|
|
31
|
-
// @sitevision/sitevision-scripts, which compiles + zips to
|
|
32
|
-
// dist/<appId>.zip (the path the CLI's sign/deploy already use).
|
|
33
|
-
if (!hasSitevisionScripts(projectRoot)) {
|
|
34
|
-
setState({
|
|
35
|
-
status: 'error',
|
|
36
|
-
error: 'No webpack.config.js found and @sitevision/sitevision-scripts is not installed. Run npm install.',
|
|
37
|
-
});
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
// Warn if the installed sitevision-scripts is outside the range
|
|
41
|
-
// the delegated build was validated against.
|
|
42
|
-
const warning = checkSitevisionScriptsCompatibility(projectRoot).warning;
|
|
43
|
-
setState({
|
|
44
|
-
status: 'building',
|
|
45
|
-
message: 'Building via sitevision-scripts...',
|
|
46
|
-
warning,
|
|
47
|
-
});
|
|
48
|
-
const sitevisionResult = await runSitevisionScriptsBuild(projectRoot);
|
|
49
|
-
if (!sitevisionResult.success) {
|
|
50
|
-
setState({
|
|
51
|
-
status: 'error',
|
|
52
|
-
error: `${sitevisionResult.error}\n${sitevisionResult.output.slice(-1000)}`,
|
|
53
|
-
warning,
|
|
54
|
-
});
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
// sitevision-scripts already produced the zip; report it directly.
|
|
58
|
-
const zipPath = getDelegatedZipPath(projectRoot, manifest.id);
|
|
59
|
-
if (!zipExists(zipPath)) {
|
|
60
|
-
setState({
|
|
61
|
-
status: 'error',
|
|
62
|
-
error: `Build reported success but no zip was found at ${zipPath}.`,
|
|
63
|
-
warning,
|
|
64
|
-
});
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
setState({
|
|
68
|
-
status: 'success',
|
|
69
|
-
message: 'Build complete',
|
|
70
|
-
zipPath,
|
|
71
|
-
zipSize: getZipSize(zipPath),
|
|
72
|
-
warning,
|
|
73
|
-
});
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (isBundled) {
|
|
77
|
-
// Project ships its own webpack config: build it in-process.
|
|
78
|
-
if (!WebpackRunner.isWebpackAvailable(projectRoot)) {
|
|
79
|
-
setState({
|
|
80
|
-
status: 'error',
|
|
81
|
-
error: 'webpack not found. Run npm install to install dependencies.',
|
|
82
|
-
});
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
setState({ status: 'building', message: 'Compiling with webpack...' });
|
|
86
|
-
const appType = getAppType(manifest);
|
|
87
|
-
const runner = new WebpackRunner(projectRoot, {
|
|
88
|
-
mode: 'production',
|
|
89
|
-
cssPrefix: manifest.id,
|
|
90
|
-
restApp: appType === 'rest',
|
|
91
|
-
});
|
|
92
|
-
const result = await runner.run();
|
|
93
|
-
await runner.close();
|
|
94
|
-
if (!result.success) {
|
|
95
|
-
setState({
|
|
96
|
-
status: 'error',
|
|
97
|
-
error: result.errors?.join('\n') || 'Build failed',
|
|
98
|
-
result,
|
|
99
|
-
});
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
// Copy static files after webpack build
|
|
103
|
-
setState({ status: 'copying', message: 'Copying static files...' });
|
|
104
|
-
copyStaticToBuild(projectRoot);
|
|
105
|
-
setState(prev => ({ ...prev, result }));
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// Non-bundled app: just copy files
|
|
109
|
-
setState({ status: 'copying', message: 'Copying source files...' });
|
|
110
|
-
copySrcToBuild(projectRoot);
|
|
111
|
-
copyStaticToBuild(projectRoot);
|
|
112
|
-
}
|
|
113
|
-
// Step 3: Create zip if requested
|
|
114
|
-
if (createZip) {
|
|
115
|
-
setState({ status: 'zipping', message: 'Creating zip archive...' });
|
|
116
|
-
const appId = getFullAppId(manifest.id);
|
|
117
|
-
const zipPath = await createBuildZip(projectRoot, appId);
|
|
118
|
-
const zipSize = getZipSize(zipPath);
|
|
119
|
-
setState(prev => ({
|
|
120
|
-
...prev,
|
|
121
|
-
status: 'success',
|
|
122
|
-
message: 'Build complete',
|
|
123
|
-
zipPath,
|
|
124
|
-
zipSize,
|
|
125
|
-
}));
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
setState(prev => ({
|
|
129
|
-
...prev,
|
|
130
|
-
status: 'success',
|
|
131
|
-
message: 'Build complete',
|
|
132
|
-
}));
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
setState({
|
|
137
|
-
status: 'error',
|
|
138
|
-
error: error instanceof Error ? error.message : String(error),
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
runBuild();
|
|
143
|
-
}, [projectRoot, manifest, isBundled, createZip]);
|
|
144
|
-
const getStatusIndicator = () => {
|
|
145
|
-
switch (state.status) {
|
|
146
|
-
case 'cleaning':
|
|
147
|
-
case 'building':
|
|
148
|
-
case 'copying':
|
|
149
|
-
case 'zipping':
|
|
150
|
-
return 'running';
|
|
151
|
-
case 'success':
|
|
152
|
-
return 'success';
|
|
153
|
-
case 'error':
|
|
154
|
-
return 'error';
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
const getStatusLabel = () => {
|
|
158
|
-
switch (state.status) {
|
|
159
|
-
case 'cleaning':
|
|
160
|
-
return 'Cleaning';
|
|
161
|
-
case 'building':
|
|
162
|
-
return 'Building';
|
|
163
|
-
case 'copying':
|
|
164
|
-
return 'Copying files';
|
|
165
|
-
case 'zipping':
|
|
166
|
-
return 'Creating zip';
|
|
167
|
-
case 'success':
|
|
168
|
-
return 'Build complete';
|
|
169
|
-
case 'error':
|
|
170
|
-
return 'Build failed';
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(StatusIndicator, { status: getStatusIndicator(), label: getStatusLabel(), message: state.message }) }), state.warning && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", state.warning] }) })), state.status === 'success' && state.result?.stats && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { dimColor: true, children: ["Compiled in ", state.result.stats.time, "ms"] }), state.result.stats.assets &&
|
|
174
|
-
state.result.stats.assets.length > 0 && (_jsxs(Text, { dimColor: true, children: ["Assets: ", state.result.stats.assets.join(', ')] }))] })), state.status === 'success' && state.zipPath && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [_jsxs(Text, { color: "green", children: ["\u2713 Created: ", state.zipPath] }), state.zipSize !== undefined && (_jsxs(Text, { dimColor: true, children: [" Size: ", formatFileSize(state.zipSize)] }))] })), state.result?.warnings && state.result.warnings.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", children: "Warnings:" }), state.result.warnings.slice(0, 5).map((warning, i) => (_jsx(Text, { color: "yellow", dimColor: true, children: warning.substring(0, 200) }, i))), state.result.warnings.length > 5 && (_jsxs(Text, { color: "yellow", dimColor: true, children: ["...and ", state.result.warnings.length - 5, " more"] }))] })), state.status === 'error' && state.error && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Text, { color: "red", children: state.error }) })), onBack && (state.status === 'success' || state.status === 'error') && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press q or Esc to return to menu" }) }))] }));
|
|
175
|
-
}
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from 'ink';
|
|
3
|
+
import { TaskScreen } from './TaskScreen.js';
|
|
4
|
+
import { startBuild } from '../utils/tasks.js';
|
|
176
5
|
export const buildCommand = {
|
|
177
6
|
name: 'build',
|
|
178
7
|
description: 'Build the application for production',
|
|
179
8
|
requiresProject: true,
|
|
180
|
-
flags: {
|
|
181
|
-
'no-zip': {
|
|
182
|
-
type: 'boolean',
|
|
183
|
-
description: 'Skip creating the zip archive',
|
|
184
|
-
default: false,
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
9
|
async execute({ project, flags }) {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
await waitUntilExit();
|
|
10
|
+
const task = startBuild(project, { zip: flags['zip'] !== false });
|
|
11
|
+
await render(_jsx(TaskScreen, { task: task })).waitUntilExit();
|
|
191
12
|
},
|
|
192
13
|
};
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,55 +1,82 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import { render, Box, Text, useInput } from 'ink';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { render, Box, Text, useApp, useInput, useStdin } from 'ink';
|
|
4
|
+
import { TaskScreen } from './TaskScreen.js';
|
|
5
|
+
import { useFinish } from './use-finish.js';
|
|
6
|
+
import { resolveDeployPasswordForCli } from './dev.js';
|
|
4
7
|
import { StatusIndicator } from '../components/StatusIndicator.js';
|
|
5
|
-
import { deployApp, deployProduction } from '../utils/sitevision-api.js';
|
|
6
|
-
import { getZipPath, getSignedZipPath, getAppType, } from '../utils/project-detection.js';
|
|
7
|
-
import { zipExists } from '../utils/zip.js';
|
|
8
|
-
import { promptPassword, promptYesNo } from '../utils/password-prompt.js';
|
|
9
|
-
import { setDeployPassword, deleteSessionCookie } from '../utils/keychain.js';
|
|
10
|
-
import { resolveOAuth2AccessToken } from '../utils/oauth2-auth.js';
|
|
11
8
|
import { AuthLoginScreen } from '../components/AuthLoginScreen.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
9
|
+
import { deleteSessionCookie, deleteOAuth2RefreshToken, } from '../utils/keychain.js';
|
|
10
|
+
import { resolveOAuth2AccessToken } from '../utils/oauth2-auth.js';
|
|
11
|
+
import { startDeploy, useTasks } from '../utils/tasks.js';
|
|
12
|
+
import { toDeployConfig } from '../utils/workspace.js';
|
|
13
|
+
import { say } from '../utils/debug.js';
|
|
14
|
+
/** Standalone `svc deploy`: log in if needed, then run the shell's deploy task. */
|
|
15
|
+
export function DeployScreen({ project, force, production, activate, }) {
|
|
16
|
+
const devProperties = project.devProperties;
|
|
17
|
+
const { exit } = useApp();
|
|
18
|
+
// Undefined, not false, on a pipe; and useInput reads undefined as active.
|
|
19
|
+
const interactive = Boolean(useStdin().isRawModeSupported);
|
|
20
|
+
// 'init' resolves cached credentials, 'login' shows the login screen,
|
|
21
|
+
// 'deploy' starts the task.
|
|
22
|
+
const [phase, setPhase] = useState('init');
|
|
23
|
+
const [credential, setCredential] = useState({
|
|
22
24
|
accessToken: devProperties.accessToken,
|
|
23
25
|
sessionCookie: devProperties.sessionCookie,
|
|
24
26
|
});
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
const [task, setTask] = useState();
|
|
28
|
+
// Anything that goes wrong before there is a task to show it.
|
|
29
|
+
const [failure, setFailure] = useState();
|
|
30
|
+
const authMethod = devProperties.authMethod ?? 'basic';
|
|
31
|
+
// OAuth2 and cookie can re-authenticate in place, given a terminal to do it in.
|
|
32
|
+
const canRelogin = interactive && (authMethod === 'oauth2' || authMethod === 'cookie');
|
|
33
|
+
const live = useTasks().find(t => t.id === task?.id) ?? task;
|
|
34
|
+
const failed = Boolean(failure) || live?.status === 'error';
|
|
35
|
+
useFinish(failure && !task ? 'error' : undefined, canRelogin);
|
|
36
|
+
// A browser login needs a terminal; without one only env credentials work.
|
|
37
|
+
const startLogin = () => {
|
|
38
|
+
if (interactive) {
|
|
39
|
+
setPhase('login');
|
|
34
40
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
React.useEffect(() => {
|
|
38
|
-
const authMethod = devProperties.authMethod ?? 'basic';
|
|
39
|
-
if (authMethod === 'basic' || devProperties.sessionCookie) {
|
|
40
|
-
setPhase('deploy');
|
|
41
|
-
return;
|
|
41
|
+
else {
|
|
42
|
+
setFailure('No stored login and no terminal to log in from. Set SITEVISION_ACCESS_TOKEN or SITEVISION_SESSION_COOKIE.');
|
|
42
43
|
}
|
|
44
|
+
};
|
|
45
|
+
// Drop the stored credential and log in again: the usual fix for an expired
|
|
46
|
+
// session or token, which Sitevision reports as a 400 rather than a 401.
|
|
47
|
+
const retryWithFreshLogin = () => {
|
|
48
|
+
const { domain, username } = devProperties;
|
|
43
49
|
if (authMethod === 'cookie') {
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
deleteSessionCookie(domain, username);
|
|
51
|
+
}
|
|
52
|
+
else if (devProperties.oauth2?.clientId) {
|
|
53
|
+
deleteOAuth2RefreshToken(domain, devProperties.oauth2.clientId);
|
|
54
|
+
}
|
|
55
|
+
setCredential({});
|
|
56
|
+
setFailure(undefined);
|
|
57
|
+
setTask(undefined);
|
|
58
|
+
setPhase('login');
|
|
59
|
+
};
|
|
60
|
+
useInput((input, key) => {
|
|
61
|
+
if (!failed)
|
|
46
62
|
return;
|
|
63
|
+
if (input === 'r')
|
|
64
|
+
retryWithFreshLogin();
|
|
65
|
+
if (key.escape || input === 'q')
|
|
66
|
+
exit();
|
|
67
|
+
}, { isActive: canRelogin });
|
|
68
|
+
// Decide once whether to deploy straight away or log in first.
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (authMethod === 'basic' || credential.sessionCookie) {
|
|
71
|
+
setPhase('deploy');
|
|
47
72
|
}
|
|
48
|
-
if (authMethod === '
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
else if (authMethod === 'cookie') {
|
|
74
|
+
startLogin();
|
|
75
|
+
}
|
|
76
|
+
else if (credential.accessToken) {
|
|
77
|
+
setPhase('deploy');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
53
80
|
void (async () => {
|
|
54
81
|
const token = await resolveOAuth2AccessToken(devProperties);
|
|
55
82
|
if (token) {
|
|
@@ -57,196 +84,52 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
57
84
|
setPhase('deploy');
|
|
58
85
|
}
|
|
59
86
|
else {
|
|
60
|
-
|
|
87
|
+
startLogin();
|
|
61
88
|
}
|
|
62
89
|
})();
|
|
63
|
-
return;
|
|
64
90
|
}
|
|
65
|
-
setPhase('deploy');
|
|
66
91
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
67
92
|
}, []);
|
|
68
|
-
|
|
69
|
-
if (phase !== 'deploy' ||
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (phase !== 'deploy' || task)
|
|
95
|
+
return;
|
|
96
|
+
const complete = toDeployConfig({ ...devProperties, ...credential });
|
|
97
|
+
if ('error' in complete) {
|
|
98
|
+
setFailure(complete.error);
|
|
70
99
|
return;
|
|
71
|
-
deployStartedRef.current = true;
|
|
72
|
-
async function runDeploy() {
|
|
73
|
-
try {
|
|
74
|
-
const appType = getAppType(manifest);
|
|
75
|
-
const authMethod = devProperties.authMethod ?? 'basic';
|
|
76
|
-
// Credential resolved in the init effect / login screen.
|
|
77
|
-
const { accessToken, sessionCookie } = credential;
|
|
78
|
-
// A stale session fails without a clean 401 — clear the stored cookie
|
|
79
|
-
// so the next run re-authenticates. Skip when SITEVISION_SESSION_COOKIE
|
|
80
|
-
// is set: detection re-reads it first, so clearing would just replay
|
|
81
|
-
// the same dead cookie in a loop.
|
|
82
|
-
const clearStaleCookie = (result) => {
|
|
83
|
-
if (result.authExpired &&
|
|
84
|
-
authMethod === 'cookie' &&
|
|
85
|
-
!process.env['SITEVISION_SESSION_COOKIE'] &&
|
|
86
|
-
devProperties.domain &&
|
|
87
|
-
devProperties.username) {
|
|
88
|
-
deleteSessionCookie(devProperties.domain, devProperties.username);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
if (production) {
|
|
92
|
-
// Production deployment requires a signed zip
|
|
93
|
-
const signedZipPath = getSignedZipPath(projectRoot, manifest);
|
|
94
|
-
if (!zipExists(signedZipPath)) {
|
|
95
|
-
setState({
|
|
96
|
-
status: 'error',
|
|
97
|
-
error: `Signed zip not found: ${signedZipPath}\nRun 'sign' first to create the signed zip.`,
|
|
98
|
-
});
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const config = {
|
|
102
|
-
domain: devProperties.domain,
|
|
103
|
-
siteName: devProperties.siteName,
|
|
104
|
-
addonName: devProperties.addonName,
|
|
105
|
-
username: devProperties.username,
|
|
106
|
-
password: devProperties.password,
|
|
107
|
-
accessToken,
|
|
108
|
-
sessionCookie,
|
|
109
|
-
useHTTP: devProperties.useHTTPForDevDeploy,
|
|
110
|
-
activate,
|
|
111
|
-
};
|
|
112
|
-
const result = await deployProduction(signedZipPath, config, appType);
|
|
113
|
-
if (!result.success) {
|
|
114
|
-
clearStaleCookie(result);
|
|
115
|
-
setState({
|
|
116
|
-
status: 'error',
|
|
117
|
-
error: result.error || 'Deployment failed',
|
|
118
|
-
});
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
setState({
|
|
122
|
-
status: 'success',
|
|
123
|
-
message: result.message || 'Deployed to production successfully',
|
|
124
|
-
executableId: result.executableId,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
// Dev deployment can use unsigned zip
|
|
129
|
-
const zipPath = getZipPath(projectRoot, manifest);
|
|
130
|
-
if (!zipExists(zipPath)) {
|
|
131
|
-
setState({
|
|
132
|
-
status: 'error',
|
|
133
|
-
error: `Zip not found: ${zipPath}\nRun 'build' first to create the zip.`,
|
|
134
|
-
});
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const config = {
|
|
138
|
-
domain: devProperties.domain,
|
|
139
|
-
siteName: devProperties.siteName,
|
|
140
|
-
addonName: devProperties.addonName,
|
|
141
|
-
username: devProperties.username,
|
|
142
|
-
password: devProperties.password,
|
|
143
|
-
accessToken,
|
|
144
|
-
sessionCookie,
|
|
145
|
-
useHTTP: devProperties.useHTTPForDevDeploy,
|
|
146
|
-
};
|
|
147
|
-
const result = await deployApp(zipPath, config, appType, force);
|
|
148
|
-
if (!result.success) {
|
|
149
|
-
clearStaleCookie(result);
|
|
150
|
-
setState({
|
|
151
|
-
status: 'error',
|
|
152
|
-
error: result.error || 'Deployment failed',
|
|
153
|
-
});
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
setState({
|
|
157
|
-
status: 'success',
|
|
158
|
-
message: 'Deployed to dev successfully',
|
|
159
|
-
executableId: result.executableId,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
setState({
|
|
165
|
-
status: 'error',
|
|
166
|
-
error: error instanceof Error ? error.message : String(error),
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
100
|
}
|
|
170
|
-
|
|
101
|
+
setTask(startDeploy(project, complete.config, { force, production, activate }));
|
|
171
102
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
172
|
-
}, [phase]);
|
|
173
|
-
if (phase === 'login' &&
|
|
174
|
-
return (_jsx(AuthLoginScreen, { method:
|
|
175
|
-
|
|
176
|
-
: 'oauth2', devProperties: devProperties, onComplete: cred => {
|
|
177
|
-
setCredential(cred);
|
|
103
|
+
}, [phase, task]);
|
|
104
|
+
if (phase === 'login' && !failure) {
|
|
105
|
+
return (_jsx(AuthLoginScreen, { method: authMethod === 'cookie' ? 'cookie' : 'oauth2', devProperties: devProperties, onComplete: next => {
|
|
106
|
+
setCredential(next);
|
|
178
107
|
setPhase('deploy');
|
|
179
|
-
}, onError:
|
|
180
|
-
|
|
181
|
-
}, onCancel: () => {
|
|
182
|
-
if (onBack) {
|
|
183
|
-
onBack();
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
setState({ status: 'error', error: 'Login cancelled.' });
|
|
187
|
-
}
|
|
108
|
+
}, onError: setFailure, onCancel: () => {
|
|
109
|
+
setFailure('Login cancelled.');
|
|
188
110
|
} }));
|
|
189
111
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
112
|
+
const retryHint = failed && canRelogin && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "This can happen when your session or token has expired." }), _jsx(Text, { dimColor: true, children: "Press r to log in again with fresh credentials, q to quit" })] }));
|
|
113
|
+
if (task) {
|
|
114
|
+
return (_jsx(TaskScreen, { task: task, stay: canRelogin, children: retryHint }));
|
|
115
|
+
}
|
|
116
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(StatusIndicator, { status: failure ? 'error' : 'running', label: failure ? 'Failed' : 'Preparing', message: failure }), retryHint] }));
|
|
195
117
|
}
|
|
196
118
|
export const deployCommand = {
|
|
197
119
|
name: 'deploy',
|
|
198
120
|
description: 'Deploy the application',
|
|
199
121
|
requiresProject: true,
|
|
200
|
-
flags: {
|
|
201
|
-
force: {
|
|
202
|
-
type: 'boolean',
|
|
203
|
-
description: 'Force deployment (overwrite existing)',
|
|
204
|
-
alias: 'f',
|
|
205
|
-
default: false,
|
|
206
|
-
},
|
|
207
|
-
production: {
|
|
208
|
-
type: 'boolean',
|
|
209
|
-
description: 'Deploy to production (requires signed app)',
|
|
210
|
-
alias: 'p',
|
|
211
|
-
default: false,
|
|
212
|
-
},
|
|
213
|
-
activate: {
|
|
214
|
-
type: 'boolean',
|
|
215
|
-
description: 'Activate the app after production deployment',
|
|
216
|
-
alias: 'a',
|
|
217
|
-
default: false,
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
122
|
async execute({ project, flags }) {
|
|
221
|
-
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
123
|
+
const complete = toDeployConfig(project.devProperties);
|
|
124
|
+
if ('error' in complete) {
|
|
125
|
+
say(`\n\x1b[33m${complete.error}\x1b[0m\n`);
|
|
126
|
+
process.exitCode = 1;
|
|
225
127
|
return;
|
|
226
128
|
}
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const authMethod = project.devProperties.authMethod ?? 'basic';
|
|
231
|
-
if (authMethod === 'basic' && !project.devProperties.password) {
|
|
232
|
-
const { domain, username } = project.devProperties;
|
|
233
|
-
console.log('');
|
|
234
|
-
const password = await promptPassword(`Deploy password for ${username}@${domain}: `);
|
|
235
|
-
if (!password) {
|
|
236
|
-
console.log('\x1b[31mError: Password is required\x1b[0m');
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const remember = await promptYesNo('Save password to OS keychain? (y/N): ');
|
|
240
|
-
if (remember && domain && username) {
|
|
241
|
-
setDeployPassword(domain, username, password);
|
|
242
|
-
}
|
|
243
|
-
project.devProperties.password = password;
|
|
244
|
-
}
|
|
245
|
-
const production = Boolean(flags['production']);
|
|
246
|
-
const force = Boolean(flags['force']);
|
|
247
|
-
const activate = Boolean(flags['activate']);
|
|
129
|
+
// OAuth2 and cookie log in inside DeployScreen; basic asks here.
|
|
130
|
+
if (!(await resolveDeployPasswordForCli(project.devProperties)))
|
|
131
|
+
return;
|
|
248
132
|
// Production deploys use the already-signed zip; `sign` is run separately.
|
|
249
|
-
|
|
250
|
-
await waitUntilExit();
|
|
133
|
+
await render(_jsx(DeployScreen, { project: project, force: Boolean(flags['force']), production: Boolean(flags['production']), activate: Boolean(flags['activate']) })).waitUntilExit();
|
|
251
134
|
},
|
|
252
135
|
};
|