sitevision-cli 0.4.0-beta.1 → 0.5.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +53 -15
- package/dist/commands/build.js +52 -4
- package/dist/commands/dev.js +139 -22
- package/dist/components/InfoScreen.js +3 -1
- package/dist/components/KeychainPasswordChoice.d.ts +8 -0
- package/dist/components/KeychainPasswordChoice.js +30 -0
- package/dist/components/PasswordInput.d.ts +3 -1
- package/dist/components/PasswordInput.js +3 -3
- package/dist/utils/process-runner.d.ts +0 -8
- package/dist/utils/process-runner.js +0 -15
- package/dist/utils/signing-step.d.ts +15 -0
- package/dist/utils/signing-step.js +16 -0
- package/dist/utils/sitevision-api.d.ts +32 -0
- package/dist/utils/sitevision-api.js +111 -42
- package/dist/utils/sitevision-scripts-runner.d.ts +83 -0
- package/dist/utils/sitevision-scripts-runner.js +187 -0
- package/dist/utils/webpack-runner.d.ts +9 -0
- package/dist/utils/webpack-runner.js +30 -15
- package/dist/utils/zip.d.ts +10 -3
- package/dist/utils/zip.js +171 -59
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -1,20 +1,48 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
3
|
import { MainMenu } from './components/MainMenu.js';
|
|
4
4
|
import { InfoScreen } from './components/InfoScreen.js';
|
|
5
5
|
import { SetupFlow } from './components/SetupFlow.js';
|
|
6
6
|
import { PasswordInput } from './components/PasswordInput.js';
|
|
7
|
+
import { KeychainPasswordChoice } from './components/KeychainPasswordChoice.js';
|
|
8
|
+
import { decideSigningStep } from './utils/signing-step.js';
|
|
7
9
|
import { DevScreen } from './commands/dev.js';
|
|
8
10
|
import { BuildScreen } from './commands/build.js';
|
|
9
11
|
import { DeployScreen } from './commands/deploy.js';
|
|
10
12
|
import { SignScreen } from './commands/sign.js';
|
|
11
13
|
import { SigningPropertiesForm } from './components/SigningPropertiesForm.js';
|
|
12
|
-
import { setDeployPassword as saveDeployPassword, setSigningPassword as saveSigningPassword, } from './utils/keychain.js';
|
|
14
|
+
import { getSigningPassword, setDeployPassword as saveDeployPassword, setSigningPassword as saveSigningPassword, } from './utils/keychain.js';
|
|
13
15
|
export default function App({ project }) {
|
|
14
16
|
const [state, setState] = useState('setup');
|
|
15
17
|
const [currentCommand, setCurrentCommand] = useState('');
|
|
16
18
|
const [signingPassword, setSigningPassword] = useState('');
|
|
17
19
|
const [devPassword, setDevPassword] = useState('');
|
|
20
|
+
// When true, skip the keychain "use saved / enter new" choice and go straight
|
|
21
|
+
// to manual entry (e.g. the saved password just failed and we're retrying).
|
|
22
|
+
const [signingRetry, setSigningRetry] = useState(false);
|
|
23
|
+
// Read the saved signing password from the keychain once (keychain access is
|
|
24
|
+
// slow and this component re-renders frequently).
|
|
25
|
+
const signingUsername = project.devProperties?.signingUsername;
|
|
26
|
+
const storedSigningPassword = useMemo(() => (signingUsername ? getSigningPassword(signingUsername) : null), [signingUsername]);
|
|
27
|
+
// Decide the next step once a signing password is needed: proceed if we already
|
|
28
|
+
// have one this session, offer the keychain choice if one is saved, otherwise
|
|
29
|
+
// prompt for manual entry.
|
|
30
|
+
const routeToSigningStep = (command = currentCommand) => {
|
|
31
|
+
const step = decideSigningStep({
|
|
32
|
+
hasSessionPassword: Boolean(signingPassword),
|
|
33
|
+
hasStoredPassword: Boolean(storedSigningPassword),
|
|
34
|
+
isRetry: signingRetry,
|
|
35
|
+
});
|
|
36
|
+
if (step === 'proceed') {
|
|
37
|
+
setState(command === 'dev-signed' ? 'dev' : 'sign');
|
|
38
|
+
}
|
|
39
|
+
else if (step === 'choice') {
|
|
40
|
+
setState('signing-password-choice');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
setState('signing-password-input');
|
|
44
|
+
}
|
|
45
|
+
};
|
|
18
46
|
// Check if dev password is available (either from file or session)
|
|
19
47
|
const hasDevPassword = Boolean(project.devProperties?.password || devPassword);
|
|
20
48
|
// Get effective dev properties with session password if needed
|
|
@@ -35,8 +63,8 @@ export default function App({ project }) {
|
|
|
35
63
|
}
|
|
36
64
|
// Continue to the intended command
|
|
37
65
|
if (currentCommand === 'dev' || currentCommand === 'dev-signed') {
|
|
38
|
-
if (currentCommand === 'dev-signed'
|
|
39
|
-
|
|
66
|
+
if (currentCommand === 'dev-signed') {
|
|
67
|
+
routeToSigningStep();
|
|
40
68
|
}
|
|
41
69
|
else {
|
|
42
70
|
setState('dev');
|
|
@@ -46,6 +74,15 @@ export default function App({ project }) {
|
|
|
46
74
|
setState('deploy');
|
|
47
75
|
}
|
|
48
76
|
};
|
|
77
|
+
const handleUseSavedSigning = () => {
|
|
78
|
+
if (storedSigningPassword) {
|
|
79
|
+
setSigningPassword(storedSigningPassword);
|
|
80
|
+
}
|
|
81
|
+
setState(currentCommand === 'dev-signed' ? 'dev' : 'sign');
|
|
82
|
+
};
|
|
83
|
+
const handleEnterNewSigning = () => {
|
|
84
|
+
setState('signing-password-input');
|
|
85
|
+
};
|
|
49
86
|
const handleSigningPasswordSubmit = (password, remember) => {
|
|
50
87
|
setSigningPassword(password);
|
|
51
88
|
if (remember && project.devProperties?.signingUsername && password) {
|
|
@@ -60,6 +97,8 @@ export default function App({ project }) {
|
|
|
60
97
|
};
|
|
61
98
|
const handleCommandSelect = (command) => {
|
|
62
99
|
setCurrentCommand(command);
|
|
100
|
+
// Fresh selection from the menu — re-offer the saved keychain password.
|
|
101
|
+
setSigningRetry(false);
|
|
63
102
|
switch (command) {
|
|
64
103
|
case 'info':
|
|
65
104
|
setState('info');
|
|
@@ -92,11 +131,8 @@ export default function App({ project }) {
|
|
|
92
131
|
if (!hasDevPassword) {
|
|
93
132
|
setState('dev-password-input');
|
|
94
133
|
}
|
|
95
|
-
else if (!signingPassword) {
|
|
96
|
-
setState('signing-password-input');
|
|
97
|
-
}
|
|
98
134
|
else {
|
|
99
|
-
|
|
135
|
+
routeToSigningStep(command);
|
|
100
136
|
}
|
|
101
137
|
break;
|
|
102
138
|
case 'sign':
|
|
@@ -108,12 +144,7 @@ export default function App({ project }) {
|
|
|
108
144
|
console.log('\x1b[31mSigning credentials not configured. Run svc setup-signing first.\x1b[0m');
|
|
109
145
|
return;
|
|
110
146
|
}
|
|
111
|
-
|
|
112
|
-
setState('sign');
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
setState('signing-password-input');
|
|
116
|
-
}
|
|
147
|
+
routeToSigningStep(command);
|
|
117
148
|
break;
|
|
118
149
|
case 'build':
|
|
119
150
|
setState('build');
|
|
@@ -146,8 +177,13 @@ export default function App({ project }) {
|
|
|
146
177
|
if (state === 'dev-password-input') {
|
|
147
178
|
return (_jsx(PasswordInput, { label: "Enter Development Password (usually Sitevision Cloud Password)", showRememberOption: Boolean(project.devProperties?.domain && project.devProperties?.username), onSubmit: handleDevPasswordSubmit, onCancel: () => setState('menu') }, "dev-password"));
|
|
148
179
|
}
|
|
180
|
+
if (state === 'signing-password-choice') {
|
|
181
|
+
return (_jsx(KeychainPasswordChoice, { onUseSaved: handleUseSavedSigning, onEnterNew: handleEnterNewSigning, onCancel: () => setState('menu') }, "signing-password-choice"));
|
|
182
|
+
}
|
|
149
183
|
if (state === 'signing-password-input') {
|
|
150
|
-
return (_jsx(PasswordInput, { label: "Enter Signing Password (developer.sitevision.se)", showRememberOption: Boolean(project.devProperties?.signingUsername),
|
|
184
|
+
return (_jsx(PasswordInput, { label: "Enter Signing Password (developer.sitevision.se)", showRememberOption: Boolean(project.devProperties?.signingUsername), defaultRemember: Boolean(storedSigningPassword), rememberLabel: storedSigningPassword
|
|
185
|
+
? 'Update saved password in OS keychain: '
|
|
186
|
+
: 'Save to OS keychain: ', onSubmit: handleSigningPasswordSubmit, onCancel: () => setState('menu') }, "signing-password"));
|
|
151
187
|
}
|
|
152
188
|
if (state === 'dev') {
|
|
153
189
|
return (_jsx(DevScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: getEffectiveDevProperties(), signed: currentCommand === 'dev-signed', onBack: () => setState('menu'), onRetryCredentials: () => {
|
|
@@ -156,6 +192,8 @@ export default function App({ project }) {
|
|
|
156
192
|
project.devProperties.password = undefined;
|
|
157
193
|
if (currentCommand === 'dev-signed') {
|
|
158
194
|
setSigningPassword('');
|
|
195
|
+
// The saved password may be what failed — don't re-offer it.
|
|
196
|
+
setSigningRetry(true);
|
|
159
197
|
}
|
|
160
198
|
setState('dev-password-input');
|
|
161
199
|
}, signingCredentials: currentCommand === 'dev-signed' &&
|
package/dist/commands/build.js
CHANGED
|
@@ -2,8 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render, Box, Text, useInput } from 'ink';
|
|
4
4
|
import { StatusIndicator } from '../components/StatusIndicator.js';
|
|
5
|
-
import { WebpackRunner } from '../utils/webpack-runner.js';
|
|
6
|
-
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';
|
|
7
8
|
import { isBundledApp, getAppType, getFullAppId, } from '../utils/project-detection.js';
|
|
8
9
|
export function BuildScreen({ projectRoot, manifest, createZip = true, onBack, }) {
|
|
9
10
|
const [state, setState] = React.useState({
|
|
@@ -25,8 +26,55 @@ export function BuildScreen({ projectRoot, manifest, createZip = true, onBack, }
|
|
|
25
26
|
setState({ status: 'cleaning', message: 'Cleaning build directory...' });
|
|
26
27
|
cleanBuild(projectRoot);
|
|
27
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
|
+
}
|
|
28
76
|
if (isBundled) {
|
|
29
|
-
//
|
|
77
|
+
// Project ships its own webpack config: build it in-process.
|
|
30
78
|
if (!WebpackRunner.isWebpackAvailable(projectRoot)) {
|
|
31
79
|
setState({
|
|
32
80
|
status: 'error',
|
|
@@ -122,7 +170,7 @@ export function BuildScreen({ projectRoot, manifest, createZip = true, onBack, }
|
|
|
122
170
|
return 'Build failed';
|
|
123
171
|
}
|
|
124
172
|
};
|
|
125
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(StatusIndicator, { status: getStatusIndicator(), label: getStatusLabel(), message: state.message }) }), 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 &&
|
|
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 &&
|
|
126
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" }) }))] }));
|
|
127
175
|
}
|
|
128
176
|
export const buildCommand = {
|
package/dist/commands/dev.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
import React from 'react';
|
|
3
5
|
import { render, Box, Text, useApp, useInput } from 'ink';
|
|
4
6
|
import { StatusIndicator } from '../components/StatusIndicator.js';
|
|
5
|
-
import { WebpackRunner } from '../utils/webpack-runner.js';
|
|
7
|
+
import { WebpackRunner, hasLocalWebpackConfig } from '../utils/webpack-runner.js';
|
|
8
|
+
import { hasSitevisionScripts, runSitevisionScriptsBuild, getDelegatedZipPath, checkSitevisionScriptsCompatibility, } from '../utils/sitevision-scripts-runner.js';
|
|
6
9
|
import { promptPassword, promptYesNo } from '../utils/password-prompt.js';
|
|
7
10
|
import { signApp, deployApp } from '../utils/sitevision-api.js';
|
|
8
11
|
import { setDeployPassword } from '../utils/keychain.js';
|
|
@@ -26,23 +29,13 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, signin
|
|
|
26
29
|
}
|
|
27
30
|
});
|
|
28
31
|
const webpackRunnerRef = React.useRef(null);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
error: result.errors?.join('\n'),
|
|
36
|
-
}));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
32
|
+
const watchersRef = React.useRef([]);
|
|
33
|
+
const debounceTimerRef = React.useRef(null);
|
|
34
|
+
const isBuildingRef = React.useRef(false);
|
|
35
|
+
const pendingRebuildRef = React.useRef(false);
|
|
36
|
+
// Sign (if needed) and deploy an already-built zip, updating UI state.
|
|
37
|
+
const signAndDeploy = React.useCallback(async (zipPath, buildTime) => {
|
|
39
38
|
try {
|
|
40
|
-
// Copy static files
|
|
41
|
-
copyStaticToBuild(projectRoot);
|
|
42
|
-
// Create zip
|
|
43
|
-
const appId = getFullAppId(manifest.id);
|
|
44
|
-
await createBuildZip(projectRoot, appId);
|
|
45
|
-
const zipPath = getZipPath(projectRoot, manifest);
|
|
46
39
|
let deployZipPath = zipPath;
|
|
47
40
|
// Sign if needed
|
|
48
41
|
if (signed && signingCredentials) {
|
|
@@ -94,7 +87,7 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, signin
|
|
|
94
87
|
status: 'ready',
|
|
95
88
|
message: 'Deployed. Watching for changes...',
|
|
96
89
|
buildCount: prev.buildCount + 1,
|
|
97
|
-
lastBuildTime:
|
|
90
|
+
lastBuildTime: buildTime,
|
|
98
91
|
error: undefined,
|
|
99
92
|
}));
|
|
100
93
|
}
|
|
@@ -107,14 +100,122 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, signin
|
|
|
107
100
|
}));
|
|
108
101
|
}
|
|
109
102
|
}, [projectRoot, manifest, devProperties, signed, signingCredentials]);
|
|
103
|
+
// In-house webpack path: copy static, zip, then sign + deploy.
|
|
104
|
+
const handleBuildComplete = React.useCallback(async (result) => {
|
|
105
|
+
if (!result.success) {
|
|
106
|
+
setState(prev => ({
|
|
107
|
+
...prev,
|
|
108
|
+
status: 'error',
|
|
109
|
+
message: result.errors?.join('\n') || 'Build failed',
|
|
110
|
+
error: result.errors?.join('\n'),
|
|
111
|
+
}));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
copyStaticToBuild(projectRoot);
|
|
116
|
+
const appId = getFullAppId(manifest.id);
|
|
117
|
+
await createBuildZip(projectRoot, appId);
|
|
118
|
+
await signAndDeploy(getZipPath(projectRoot, manifest), result.stats?.time);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
setState(prev => ({
|
|
122
|
+
...prev,
|
|
123
|
+
status: 'error',
|
|
124
|
+
message: error instanceof Error ? error.message : String(error),
|
|
125
|
+
error: error instanceof Error ? error.message : String(error),
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
}, [projectRoot, manifest, signAndDeploy]);
|
|
129
|
+
// Delegated path: full `sitevision-scripts build` then sign + deploy.
|
|
130
|
+
// Coalesces overlapping triggers so a save mid-build queues one rebuild.
|
|
131
|
+
const runDelegatedBuild = React.useCallback(async () => {
|
|
132
|
+
if (isBuildingRef.current) {
|
|
133
|
+
pendingRebuildRef.current = true;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
isBuildingRef.current = true;
|
|
137
|
+
const buildOnce = async () => {
|
|
138
|
+
pendingRebuildRef.current = false;
|
|
139
|
+
setState(prev => ({
|
|
140
|
+
...prev,
|
|
141
|
+
status: 'building',
|
|
142
|
+
message: 'Building via sitevision-scripts...',
|
|
143
|
+
}));
|
|
144
|
+
const result = await runSitevisionScriptsBuild(projectRoot);
|
|
145
|
+
if (result.success) {
|
|
146
|
+
await signAndDeploy(getDelegatedZipPath(projectRoot, manifest.id));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
setState(prev => ({
|
|
150
|
+
...prev,
|
|
151
|
+
status: 'error',
|
|
152
|
+
message: result.error ?? 'Build failed',
|
|
153
|
+
error: `${result.error}\n${result.output.slice(-1000)}`,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
if (pendingRebuildRef.current) {
|
|
157
|
+
await buildOnce();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
try {
|
|
161
|
+
await buildOnce();
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
isBuildingRef.current = false;
|
|
165
|
+
}
|
|
166
|
+
}, [projectRoot, manifest, signAndDeploy]);
|
|
167
|
+
// Watch source files and trigger a delegated rebuild (debounced).
|
|
168
|
+
const startFileWatcher = React.useCallback(() => {
|
|
169
|
+
const targets = [
|
|
170
|
+
'src',
|
|
171
|
+
'static',
|
|
172
|
+
'i18n',
|
|
173
|
+
'resource',
|
|
174
|
+
'config',
|
|
175
|
+
'manifest.json',
|
|
176
|
+
]
|
|
177
|
+
.map(name => path.join(projectRoot, name))
|
|
178
|
+
.filter(target => fs.existsSync(target));
|
|
179
|
+
for (const target of targets) {
|
|
180
|
+
const isDir = fs.statSync(target).isDirectory();
|
|
181
|
+
const watcher = fs.watch(target, { recursive: isDir }, () => {
|
|
182
|
+
if (debounceTimerRef.current) {
|
|
183
|
+
clearTimeout(debounceTimerRef.current);
|
|
184
|
+
}
|
|
185
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
186
|
+
void runDelegatedBuild();
|
|
187
|
+
}, 300);
|
|
188
|
+
});
|
|
189
|
+
watchersRef.current.push(watcher);
|
|
190
|
+
}
|
|
191
|
+
}, [projectRoot, runDelegatedBuild]);
|
|
110
192
|
React.useEffect(() => {
|
|
111
193
|
const isBundled = isBundledApp(manifest);
|
|
112
194
|
async function startWatch() {
|
|
113
195
|
try {
|
|
114
196
|
// Clean build directory
|
|
115
197
|
cleanBuild(projectRoot);
|
|
116
|
-
if (isBundled) {
|
|
117
|
-
//
|
|
198
|
+
if (isBundled && !hasLocalWebpackConfig(projectRoot)) {
|
|
199
|
+
// No local webpack config: delegate each build to
|
|
200
|
+
// sitevision-scripts (full rebuild) and watch source files
|
|
201
|
+
// ourselves, keeping the CLI's own sign + deploy flow.
|
|
202
|
+
if (!hasSitevisionScripts(projectRoot)) {
|
|
203
|
+
setState({
|
|
204
|
+
status: 'error',
|
|
205
|
+
message: 'No webpack.config.js found and @sitevision/sitevision-scripts is not installed. Run npm install.',
|
|
206
|
+
buildCount: 0,
|
|
207
|
+
webpackReady: false,
|
|
208
|
+
error: 'No build pipeline available',
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const warning = checkSitevisionScriptsCompatibility(projectRoot).warning;
|
|
213
|
+
setState(prev => ({ ...prev, webpackReady: true, warning }));
|
|
214
|
+
startFileWatcher();
|
|
215
|
+
await runDelegatedBuild();
|
|
216
|
+
}
|
|
217
|
+
else if (isBundled) {
|
|
218
|
+
// Project ships its own webpack config: incremental in-process watch.
|
|
118
219
|
if (!WebpackRunner.isWebpackAvailable(projectRoot)) {
|
|
119
220
|
setState({
|
|
120
221
|
status: 'error',
|
|
@@ -175,14 +276,30 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, signin
|
|
|
175
276
|
if (webpackRunnerRef.current) {
|
|
176
277
|
webpackRunnerRef.current.close().catch(() => { });
|
|
177
278
|
}
|
|
279
|
+
if (debounceTimerRef.current) {
|
|
280
|
+
clearTimeout(debounceTimerRef.current);
|
|
281
|
+
}
|
|
282
|
+
for (const watcher of watchersRef.current) {
|
|
283
|
+
watcher.close();
|
|
284
|
+
}
|
|
285
|
+
watchersRef.current = [];
|
|
178
286
|
};
|
|
179
|
-
}, [
|
|
287
|
+
}, [
|
|
288
|
+
projectRoot,
|
|
289
|
+
manifest,
|
|
290
|
+
handleBuildComplete,
|
|
291
|
+
runDelegatedBuild,
|
|
292
|
+
startFileWatcher,
|
|
293
|
+
]);
|
|
180
294
|
// Handle Ctrl+C
|
|
181
295
|
React.useEffect(() => {
|
|
182
296
|
const handleExit = () => {
|
|
183
297
|
if (webpackRunnerRef.current) {
|
|
184
298
|
webpackRunnerRef.current.close().catch(() => { });
|
|
185
299
|
}
|
|
300
|
+
for (const watcher of watchersRef.current) {
|
|
301
|
+
watcher.close();
|
|
302
|
+
}
|
|
186
303
|
exit();
|
|
187
304
|
};
|
|
188
305
|
process.on('SIGINT', handleExit);
|
|
@@ -220,7 +337,7 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, signin
|
|
|
220
337
|
return 'Error';
|
|
221
338
|
}
|
|
222
339
|
};
|
|
223
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(StatusIndicator, { status: getStatusType(), label: getStatusLabel(), message: state.message }) }), state.buildCount > 0 && (_jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Builds: ", state.buildCount, state.lastBuildTime
|
|
340
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(StatusIndicator, { status: getStatusType(), label: getStatusLabel(), message: state.message }) }), state.warning && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", state.warning] }) })), state.buildCount > 0 && (_jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Builds: ", state.buildCount, state.lastBuildTime
|
|
224
341
|
? ` | Last build: ${state.lastBuildTime}ms`
|
|
225
342
|
: '', signed ? ' | Signed mode' : ''] }) })), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [manifest.name, " v", manifest.version] }) }), devProperties && (_jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Target: ", devProperties.domain, "/", devProperties.siteName, "/", devProperties.addonName] }) })), state.status === 'error' && state.error && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Text, { color: "red", children: state.error }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [state.status === 'error' && onRetryCredentials && (_jsx(Text, { dimColor: true, children: "Press r to retry with new credentials" })), onBack ? (_jsx(Text, { dimColor: true, children: "Press q or Esc to return to menu (Ctrl+C to stop process)" })) : (_jsx(Text, { dimColor: true, children: "Press Ctrl+C to stop" }))] })] }));
|
|
226
343
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
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
3
|
import { getAppType } from '../utils/project-detection.js';
|
|
4
|
+
import { checkSitevisionScriptsCompatibility } from '../utils/sitevision-scripts-runner.js';
|
|
4
5
|
export function InfoScreen({ project, onBack }) {
|
|
5
6
|
const appType = getAppType(project.manifest);
|
|
7
|
+
const scriptsCompat = checkSitevisionScriptsCompatibility(project.root);
|
|
6
8
|
useInput((input, key) => {
|
|
7
9
|
if (key.escape || input === 'q' || key.return) {
|
|
8
10
|
onBack();
|
|
9
11
|
}
|
|
10
12
|
});
|
|
11
|
-
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 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: 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" }) })] }));
|
|
12
14
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
const OPTIONS = [
|
|
5
|
+
{ label: 'Use saved password from keychain', value: 'saved' },
|
|
6
|
+
{ label: 'Enter a new password', value: 'new' },
|
|
7
|
+
];
|
|
8
|
+
export function KeychainPasswordChoice({ label = 'A signing password is saved in your OS keychain.', onUseSaved, onEnterNew, onCancel, }) {
|
|
9
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
10
|
+
useInput((_input, key) => {
|
|
11
|
+
if (key.upArrow) {
|
|
12
|
+
setSelectedIndex(prev => (prev === 0 ? OPTIONS.length - 1 : prev - 1));
|
|
13
|
+
}
|
|
14
|
+
else if (key.downArrow) {
|
|
15
|
+
setSelectedIndex(prev => (prev === OPTIONS.length - 1 ? 0 : prev + 1));
|
|
16
|
+
}
|
|
17
|
+
else if (key.return) {
|
|
18
|
+
if (selectedIndex === 0) {
|
|
19
|
+
onUseSaved();
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
onEnterNew();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else if (key.escape) {
|
|
26
|
+
onCancel();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: label }) }), OPTIONS.map((option, index) => (_jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', option.label] }, option.value))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move, Enter to select, Esc to cancel" }) })] }));
|
|
30
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
interface Props {
|
|
2
2
|
label?: string;
|
|
3
3
|
showRememberOption?: boolean;
|
|
4
|
+
defaultRemember?: boolean;
|
|
5
|
+
rememberLabel?: string;
|
|
4
6
|
onSubmit: (password: string, remember: boolean) => void;
|
|
5
7
|
onCancel: () => void;
|
|
6
8
|
}
|
|
7
|
-
export declare function PasswordInput({ label, showRememberOption, onSubmit, onCancel, }: Props): import("react").JSX.Element;
|
|
9
|
+
export declare function PasswordInput({ label, showRememberOption, defaultRemember, rememberLabel, onSubmit, onCancel, }: Props): import("react").JSX.Element;
|
|
8
10
|
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
export function PasswordInput({ label = 'Enter Signing Password', showRememberOption = false, onSubmit, onCancel, }) {
|
|
4
|
+
export function PasswordInput({ label = 'Enter Signing Password', showRememberOption = false, defaultRemember = false, rememberLabel = 'Save to OS keychain: ', onSubmit, onCancel, }) {
|
|
5
5
|
const [password, setPassword] = useState('');
|
|
6
|
-
const [remember, setRemember] = useState(
|
|
6
|
+
const [remember, setRemember] = useState(defaultRemember);
|
|
7
7
|
useInput((input, key) => {
|
|
8
8
|
if (key.return) {
|
|
9
9
|
onSubmit(password, remember);
|
|
@@ -26,5 +26,5 @@ export function PasswordInput({ label = 'Enter Signing Password', showRememberOp
|
|
|
26
26
|
setPassword(prev => prev + input);
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: label }) }), _jsx(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: '*'.repeat(password.length) }) }), showRememberOption && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children:
|
|
29
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: label }) }), _jsx(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: '*'.repeat(password.length) }) }), showRememberOption && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: rememberLabel }), _jsxs(Text, { color: remember ? 'green' : 'gray', children: ["[", remember ? 'x' : ' ', "]"] }), _jsx(Text, { dimColor: true, children: " (Tab to toggle)" })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to submit, Esc to cancel" }) })] }));
|
|
30
30
|
}
|
|
@@ -20,11 +20,3 @@ export declare class ProcessRunner extends EventEmitter {
|
|
|
20
20
|
kill(): void;
|
|
21
21
|
getOutput(): ProcessOutput[];
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Run a sitevision-scripts command in the project directory
|
|
25
|
-
*/
|
|
26
|
-
export declare function runSitevisionScript(scriptName: string, args?: string[], projectRoot?: string): ProcessRunner;
|
|
27
|
-
/**
|
|
28
|
-
* Run an NPM script
|
|
29
|
-
*/
|
|
30
|
-
export declare function runNpmScript(scriptName: string, args?: string[], projectRoot?: string, customEnv?: Record<string, string>): ProcessRunner;
|
|
@@ -72,18 +72,3 @@ export class ProcessRunner extends EventEmitter {
|
|
|
72
72
|
return this.output;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Run a sitevision-scripts command in the project directory
|
|
77
|
-
*/
|
|
78
|
-
export function runSitevisionScript(scriptName, args = [], projectRoot) {
|
|
79
|
-
// Check if sitevision-scripts is available locally
|
|
80
|
-
const runner = new ProcessRunner('npm', ['run', scriptName, '--', ...args], projectRoot);
|
|
81
|
-
return runner;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Run an NPM script
|
|
85
|
-
*/
|
|
86
|
-
export function runNpmScript(scriptName, args = [], projectRoot, customEnv) {
|
|
87
|
-
const runner = new ProcessRunner('npm', ['run', scriptName, ...args], projectRoot, false, customEnv);
|
|
88
|
-
return runner;
|
|
89
|
-
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type SigningStep = 'proceed' | 'choice' | 'input';
|
|
2
|
+
/**
|
|
3
|
+
* Decide what to do when a signing password is needed.
|
|
4
|
+
*
|
|
5
|
+
* - `proceed`: a password is already available this session — use it.
|
|
6
|
+
* - `choice`: a password is saved in the keychain — ask whether to use it or
|
|
7
|
+
* enter a new one.
|
|
8
|
+
* - `input`: prompt for a password (nothing saved, or a saved one just failed
|
|
9
|
+
* and we're retrying).
|
|
10
|
+
*/
|
|
11
|
+
export declare function decideSigningStep(options: {
|
|
12
|
+
hasSessionPassword: boolean;
|
|
13
|
+
hasStoredPassword: boolean;
|
|
14
|
+
isRetry: boolean;
|
|
15
|
+
}): SigningStep;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide what to do when a signing password is needed.
|
|
3
|
+
*
|
|
4
|
+
* - `proceed`: a password is already available this session — use it.
|
|
5
|
+
* - `choice`: a password is saved in the keychain — ask whether to use it or
|
|
6
|
+
* enter a new one.
|
|
7
|
+
* - `input`: prompt for a password (nothing saved, or a saved one just failed
|
|
8
|
+
* and we're retrying).
|
|
9
|
+
*/
|
|
10
|
+
export function decideSigningStep(options) {
|
|
11
|
+
if (options.hasSessionPassword)
|
|
12
|
+
return 'proceed';
|
|
13
|
+
if (options.hasStoredPassword && !options.isRetry)
|
|
14
|
+
return 'choice';
|
|
15
|
+
return 'input';
|
|
16
|
+
}
|
|
@@ -13,6 +13,38 @@ import type { SigningCredentials, DeployConfig, ProductionDeployConfig, SigningR
|
|
|
13
13
|
* Create Basic Auth header value
|
|
14
14
|
*/
|
|
15
15
|
declare function createBasicAuth(username: string, password: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Make an HTTP/HTTPS request
|
|
18
|
+
*/
|
|
19
|
+
export declare function makeRequest(url: string, options: {
|
|
20
|
+
method: string;
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
body?: Buffer;
|
|
23
|
+
auth?: {
|
|
24
|
+
username: string;
|
|
25
|
+
password: string;
|
|
26
|
+
};
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
statusCode: number;
|
|
30
|
+
body: Buffer;
|
|
31
|
+
headers: Record<string, string>;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Whether an HTTP status is worth retrying (transient server-side failures).
|
|
35
|
+
*/
|
|
36
|
+
export declare function isRetryableStatus(statusCode: number): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Summarize a non-success response body for error messages.
|
|
39
|
+
* Avoids dumping raw bytes (e.g. an HTML error page or a binary blob) by
|
|
40
|
+
* trimming text bodies and labelling binary ones by their content type.
|
|
41
|
+
*/
|
|
42
|
+
export declare function summarizeErrorBody(body: Buffer, headers: Record<string, string>): string;
|
|
43
|
+
/**
|
|
44
|
+
* Check that a buffer begins with the ZIP magic bytes. Used to fail fast when
|
|
45
|
+
* the signing endpoint returns an error page with HTTP 200.
|
|
46
|
+
*/
|
|
47
|
+
export declare function looksLikeZip(body: Buffer): boolean;
|
|
16
48
|
/**
|
|
17
49
|
* Sign an app via developer.sitevision.se
|
|
18
50
|
*
|