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.
- package/dist/app.d.ts +1 -1
- package/dist/app.js +44 -7
- package/dist/cli.js +22 -0
- package/dist/commands/deploy.d.ts +1 -2
- package/dist/commands/deploy.js +96 -22
- package/dist/commands/dev.js +29 -3
- package/dist/commands/info.js +2 -2
- package/dist/components/AuthLoginScreen.d.ts +21 -0
- package/dist/components/AuthLoginScreen.js +87 -0
- package/dist/components/DevPropertiesForm.js +160 -31
- package/dist/components/InfoScreen.js +2 -2
- package/dist/components/MainMenu.js +2 -2
- package/dist/components/SetupFlow.d.ts +2 -1
- package/dist/components/SetupFlow.js +100 -11
- package/dist/types/index.d.ts +32 -3
- 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 +27 -0
- package/dist/utils/oauth2-auth.js +172 -0
- package/dist/utils/password-prompt.d.ts +5 -0
- package/dist/utils/password-prompt.js +28 -0
- package/dist/utils/project-detection.d.ts +56 -3
- package/dist/utils/project-detection.js +179 -21
- package/dist/utils/session-cookie-auth.d.ts +34 -0
- package/dist/utils/session-cookie-auth.js +99 -0
- package/dist/utils/sitevision-api.d.ts +29 -5
- package/dist/utils/sitevision-api.js +121 -32
- package/package.json +2 -1
- package/readme.md +23 -0
package/dist/app.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import { type ProjectInfo } from './utils/project-detection.js';
|
|
|
2
2
|
type Props = {
|
|
3
3
|
project: ProjectInfo;
|
|
4
4
|
};
|
|
5
|
-
export default function App({ project }: Props): import("react").JSX.Element | null;
|
|
5
|
+
export default function App({ project: initialProject }: Props): import("react").JSX.Element | null;
|
|
6
6
|
export {};
|
package/dist/app.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState } from 'react';
|
|
2
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
3
|
+
import { detectProject } from './utils/project-detection.js';
|
|
3
4
|
import { MainMenu } from './components/MainMenu.js';
|
|
4
5
|
import { InfoScreen } from './components/InfoScreen.js';
|
|
5
6
|
import { SetupFlow } from './components/SetupFlow.js';
|
|
@@ -12,7 +13,23 @@ import { DeployScreen } from './commands/deploy.js';
|
|
|
12
13
|
import { SignScreen } from './commands/sign.js';
|
|
13
14
|
import { SigningPropertiesForm } from './components/SigningPropertiesForm.js';
|
|
14
15
|
import { getSigningPassword, setDeployPassword as saveDeployPassword, setSigningPassword as saveSigningPassword, } from './utils/keychain.js';
|
|
15
|
-
|
|
16
|
+
const NONBASIC_DEV_UNSUPPORTED = '\x1b[33mdev/watch support only basic auth. Use `svc deploy` for OAuth2/cookie.\x1b[0m';
|
|
17
|
+
export default function App({ project: initialProject }) {
|
|
18
|
+
// The project is loaded once at startup, but setup flows write new values to
|
|
19
|
+
// disk and the OS keychain. Hold it in state so we can re-detect after setup
|
|
20
|
+
// and pick up those changes (e.g. saved passwords) without restarting the CLI.
|
|
21
|
+
const [project, setProject] = useState(initialProject);
|
|
22
|
+
const reloadProject = useCallback(() => {
|
|
23
|
+
try {
|
|
24
|
+
const refreshed = detectProject(initialProject.root);
|
|
25
|
+
if (refreshed)
|
|
26
|
+
setProject(refreshed);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Re-detection failed (e.g. manifest became unparseable mid-session) —
|
|
30
|
+
// keep the existing in-memory project rather than crashing.
|
|
31
|
+
}
|
|
32
|
+
}, [initialProject.root]);
|
|
16
33
|
const [state, setState] = useState('setup');
|
|
17
34
|
const [currentCommand, setCurrentCommand] = useState('');
|
|
18
35
|
const [signingPassword, setSigningPassword] = useState('');
|
|
@@ -54,10 +71,18 @@ export default function App({ project }) {
|
|
|
54
71
|
};
|
|
55
72
|
// Check if dev password is available (either from file or session)
|
|
56
73
|
const hasDevPassword = Boolean(project.devProperties?.password || devPassword);
|
|
74
|
+
// OAuth2 / cookie configs authenticate with a token or session resolved at
|
|
75
|
+
// deploy time, so they need no basic password. `svc dev`/`watch` stay basic.
|
|
76
|
+
const authMethod = project.devProperties?.authMethod ?? 'basic';
|
|
77
|
+
const isTokenAuth = authMethod === 'oauth2' || authMethod === 'cookie';
|
|
78
|
+
const deployAuthReady = isTokenAuth || hasDevPassword;
|
|
57
79
|
// Get effective dev properties with session password if needed
|
|
58
80
|
const getEffectiveDevProperties = () => {
|
|
59
81
|
if (!project.devProperties)
|
|
60
82
|
return undefined;
|
|
83
|
+
// Non-basic configs authenticate by token/cookie — never graft a password.
|
|
84
|
+
if (isTokenAuth)
|
|
85
|
+
return project.devProperties;
|
|
61
86
|
if (project.devProperties.password)
|
|
62
87
|
return project.devProperties;
|
|
63
88
|
return { ...project.devProperties, password: devPassword };
|
|
@@ -115,6 +140,10 @@ export default function App({ project }) {
|
|
|
115
140
|
console.log('\x1b[31mDevelopment properties not configured. Create a .dev_properties.json file first.\x1b[0m');
|
|
116
141
|
return;
|
|
117
142
|
}
|
|
143
|
+
if (isTokenAuth) {
|
|
144
|
+
console.log(NONBASIC_DEV_UNSUPPORTED);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
118
147
|
if (!hasDevPassword) {
|
|
119
148
|
setState('dev-password-input');
|
|
120
149
|
}
|
|
@@ -131,6 +160,10 @@ export default function App({ project }) {
|
|
|
131
160
|
console.log('\x1b[31mSigning credentials not configured. Run svc setup-signing first.\x1b[0m');
|
|
132
161
|
return;
|
|
133
162
|
}
|
|
163
|
+
if (isTokenAuth) {
|
|
164
|
+
console.log(NONBASIC_DEV_UNSUPPORTED);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
134
167
|
// Need both dev password and signing password
|
|
135
168
|
if (!hasDevPassword) {
|
|
136
169
|
setState('dev-password-input');
|
|
@@ -152,6 +185,10 @@ export default function App({ project }) {
|
|
|
152
185
|
console.log('\x1b[31mSigning credentials not configured. Run svc setup-signing first.\x1b[0m');
|
|
153
186
|
return;
|
|
154
187
|
}
|
|
188
|
+
if (isTokenAuth) {
|
|
189
|
+
console.log(NONBASIC_DEV_UNSUPPORTED);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
155
192
|
routeToSigningStep(command);
|
|
156
193
|
break;
|
|
157
194
|
case 'sign':
|
|
@@ -175,7 +212,7 @@ export default function App({ project }) {
|
|
|
175
212
|
console.log('\x1b[31mDevelopment properties not configured. Create a .dev_properties.json file first.\x1b[0m');
|
|
176
213
|
return;
|
|
177
214
|
}
|
|
178
|
-
if (!
|
|
215
|
+
if (!deployAuthReady) {
|
|
179
216
|
setState('dev-password-input');
|
|
180
217
|
}
|
|
181
218
|
else {
|
|
@@ -185,7 +222,7 @@ export default function App({ project }) {
|
|
|
185
222
|
}
|
|
186
223
|
};
|
|
187
224
|
if (state === 'setup') {
|
|
188
|
-
return _jsx(SetupFlow, { project: project, onComplete: () => setState('menu') });
|
|
225
|
+
return (_jsx(SetupFlow, { project: project, onReload: reloadProject, onComplete: () => setState('menu') }));
|
|
189
226
|
}
|
|
190
227
|
if (state === 'menu') {
|
|
191
228
|
return _jsx(MainMenu, { project: project, onSelect: handleCommandSelect });
|
|
@@ -260,9 +297,9 @@ export default function App({ project }) {
|
|
|
260
297
|
}
|
|
261
298
|
if (state === 'setup-signing') {
|
|
262
299
|
return (_jsx(SigningPropertiesForm, { projectRoot: project.root, onComplete: () => {
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
|
|
300
|
+
// Re-detect so the newly written signing credentials are reflected
|
|
301
|
+
// in memory (hasSigningProperties, keychain password) without a restart.
|
|
302
|
+
reloadProject();
|
|
266
303
|
setState('menu');
|
|
267
304
|
}, onCancel: () => setState('menu') }));
|
|
268
305
|
}
|
package/dist/cli.js
CHANGED
|
@@ -58,6 +58,12 @@ const cli = meow(`
|
|
|
58
58
|
shortFlag: 'p',
|
|
59
59
|
default: false,
|
|
60
60
|
},
|
|
61
|
+
token: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
},
|
|
64
|
+
cookie: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
},
|
|
61
67
|
},
|
|
62
68
|
});
|
|
63
69
|
const [commandName, ...args] = cli.input;
|
|
@@ -149,6 +155,22 @@ async function main() {
|
|
|
149
155
|
process.exit(1);
|
|
150
156
|
}
|
|
151
157
|
})();
|
|
158
|
+
// --token / --cookie override the resolved bearer token / session cookie for
|
|
159
|
+
// this run (manual / CI path, alongside SITEVISION_ACCESS_TOKEN and
|
|
160
|
+
// SITEVISION_SESSION_COOKIE).
|
|
161
|
+
if (cli.flags.token || cli.flags.cookie) {
|
|
162
|
+
if (project.devProperties) {
|
|
163
|
+
if (cli.flags.token) {
|
|
164
|
+
project.devProperties.accessToken = cli.flags.token;
|
|
165
|
+
}
|
|
166
|
+
if (cli.flags.cookie) {
|
|
167
|
+
project.devProperties.sessionCookie = cli.flags.cookie;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.log('\x1b[33m--token/--cookie needs a .dev_properties.json (domain, site, addon) to deploy against.\x1b[0m');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
152
174
|
// First run: show the welcome (branding + optional signing-password save),
|
|
153
175
|
// then continue to the normal flow once the user dismisses it.
|
|
154
176
|
if (firstRun) {
|
|
@@ -8,10 +8,9 @@ interface DeployScreenProps {
|
|
|
8
8
|
force: boolean;
|
|
9
9
|
production: boolean;
|
|
10
10
|
activate: boolean;
|
|
11
|
-
signingPassword?: string;
|
|
12
11
|
onBack?: () => void;
|
|
13
12
|
onRetryCredentials?: () => void;
|
|
14
13
|
}
|
|
15
|
-
export declare function DeployScreen({ projectRoot, manifest, devProperties, force, production, activate,
|
|
14
|
+
export declare function DeployScreen({ projectRoot, manifest, devProperties, force, production, activate, onBack, onRetryCredentials, }: DeployScreenProps): React.JSX.Element;
|
|
16
15
|
export declare const deployCommand: Command;
|
|
17
16
|
export {};
|
package/dist/commands/deploy.js
CHANGED
|
@@ -6,12 +6,23 @@ import { deployApp, deployProduction } from '../utils/sitevision-api.js';
|
|
|
6
6
|
import { getZipPath, 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 } 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, }) {
|
|
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);
|
|
15
26
|
useInput((input, key) => {
|
|
16
27
|
if (state.status !== 'deploying') {
|
|
17
28
|
if (onBack && (key.escape || input === 'q')) {
|
|
@@ -22,10 +33,61 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
35
|
});
|
|
36
|
+
// Decide once whether we can deploy straight away or must log in first.
|
|
25
37
|
React.useEffect(() => {
|
|
38
|
+
const authMethod = devProperties.authMethod ?? 'basic';
|
|
39
|
+
if (authMethod === 'basic' || devProperties.sessionCookie) {
|
|
40
|
+
setPhase('deploy');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (authMethod === 'cookie') {
|
|
44
|
+
// env/keychain cookie is already loaded in devProperties; none here.
|
|
45
|
+
setPhase('login');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (authMethod === 'oauth2') {
|
|
49
|
+
if (devProperties.accessToken) {
|
|
50
|
+
setPhase('deploy');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
void (async () => {
|
|
54
|
+
const token = await resolveOAuth2AccessToken(devProperties);
|
|
55
|
+
if (token) {
|
|
56
|
+
setCredential({ accessToken: token });
|
|
57
|
+
setPhase('deploy');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
setPhase('login');
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
setPhase('deploy');
|
|
66
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
67
|
+
}, []);
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
if (phase !== 'deploy' || deployStartedRef.current)
|
|
70
|
+
return;
|
|
71
|
+
deployStartedRef.current = true;
|
|
26
72
|
async function runDeploy() {
|
|
27
73
|
try {
|
|
28
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
|
+
};
|
|
29
91
|
if (production) {
|
|
30
92
|
// Production deployment requires a signed zip
|
|
31
93
|
const signedZipPath = getSignedZipPath(projectRoot, manifest);
|
|
@@ -42,11 +104,14 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
42
104
|
addonName: devProperties.addonName,
|
|
43
105
|
username: devProperties.username,
|
|
44
106
|
password: devProperties.password,
|
|
107
|
+
accessToken,
|
|
108
|
+
sessionCookie,
|
|
45
109
|
useHTTP: devProperties.useHTTPForDevDeploy,
|
|
46
110
|
activate,
|
|
47
111
|
};
|
|
48
112
|
const result = await deployProduction(signedZipPath, config, appType);
|
|
49
113
|
if (!result.success) {
|
|
114
|
+
clearStaleCookie(result);
|
|
50
115
|
setState({
|
|
51
116
|
status: 'error',
|
|
52
117
|
error: result.error || 'Deployment failed',
|
|
@@ -75,10 +140,13 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
75
140
|
addonName: devProperties.addonName,
|
|
76
141
|
username: devProperties.username,
|
|
77
142
|
password: devProperties.password,
|
|
143
|
+
accessToken,
|
|
144
|
+
sessionCookie,
|
|
78
145
|
useHTTP: devProperties.useHTTPForDevDeploy,
|
|
79
146
|
};
|
|
80
147
|
const result = await deployApp(zipPath, config, appType, force);
|
|
81
148
|
if (!result.success) {
|
|
149
|
+
clearStaleCookie(result);
|
|
82
150
|
setState({
|
|
83
151
|
status: 'error',
|
|
84
152
|
error: result.error || 'Deployment failed',
|
|
@@ -100,15 +168,25 @@ export function DeployScreen({ projectRoot, manifest, devProperties, force, prod
|
|
|
100
168
|
}
|
|
101
169
|
}
|
|
102
170
|
runDeploy();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
devProperties
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
171
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
172
|
+
}, [phase]);
|
|
173
|
+
if (phase === 'login' && state.status !== 'error') {
|
|
174
|
+
return (_jsx(AuthLoginScreen, { method: (devProperties.authMethod ?? 'basic') === 'cookie'
|
|
175
|
+
? 'cookie'
|
|
176
|
+
: 'oauth2', devProperties: devProperties, onComplete: cred => {
|
|
177
|
+
setCredential(cred);
|
|
178
|
+
setPhase('deploy');
|
|
179
|
+
}, onError: message => {
|
|
180
|
+
setState({ status: 'error', error: message });
|
|
181
|
+
}, onCancel: () => {
|
|
182
|
+
if (onBack) {
|
|
183
|
+
onBack();
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
setState({ status: 'error', error: 'Login cancelled.' });
|
|
187
|
+
}
|
|
188
|
+
} }));
|
|
189
|
+
}
|
|
112
190
|
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
191
|
? 'Deploying'
|
|
114
192
|
: state.status === 'success'
|
|
@@ -146,8 +224,11 @@ export const deployCommand = {
|
|
|
146
224
|
console.log('Create a .dev_properties.json file with domain, siteName, addonName, and username, then run setup.\n');
|
|
147
225
|
return;
|
|
148
226
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
227
|
+
// Basic auth prompts for a password here; OAuth2 and cookie resolve or log
|
|
228
|
+
// in inside DeployScreen (Ink-native), so both the TUI and this command
|
|
229
|
+
// share one login path. env/--flag token/cookie are already loaded.
|
|
230
|
+
const authMethod = project.devProperties.authMethod ?? 'basic';
|
|
231
|
+
if (authMethod === 'basic' && !project.devProperties.password) {
|
|
151
232
|
const { domain, username } = project.devProperties;
|
|
152
233
|
console.log('');
|
|
153
234
|
const password = await promptPassword(`Deploy password for ${username}@${domain}: `);
|
|
@@ -164,15 +245,8 @@ export const deployCommand = {
|
|
|
164
245
|
const production = Boolean(flags['production']);
|
|
165
246
|
const force = Boolean(flags['force']);
|
|
166
247
|
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 }));
|
|
248
|
+
// Production deploys use the already-signed zip; `sign` is run separately.
|
|
249
|
+
const { waitUntilExit } = render(_jsx(DeployScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: project.devProperties, force: force, production: production, activate: activate }));
|
|
176
250
|
await waitUntilExit();
|
|
177
251
|
},
|
|
178
252
|
};
|
package/dist/commands/dev.js
CHANGED
|
@@ -11,9 +11,10 @@ import { signApp, deployApp } from '../utils/sitevision-api.js';
|
|
|
11
11
|
import { setDeployPassword } from '../utils/keychain.js';
|
|
12
12
|
import { resolveSigningPassword } from '../utils/signing-password.js';
|
|
13
13
|
import { copyStaticToBuild, createBuildZip, cleanBuild } from '../utils/zip.js';
|
|
14
|
-
import { isBundledApp, getAppType, getFullAppId, getZipPath, getSignedZipPath, } from '../utils/project-detection.js';
|
|
14
|
+
import { isBundledApp, getAppType, getFullAppId, getZipPath, getSignedZipPath, localizedText, readManifest, } from '../utils/project-detection.js';
|
|
15
15
|
export function DevScreen({ projectRoot, manifest, devProperties, signed, deploy = true, signingCredentials, onBack, onRetryCredentials, }) {
|
|
16
16
|
const { exit } = useApp();
|
|
17
|
+
const [version, setVersion] = React.useState(manifest.version);
|
|
17
18
|
const [state, setState] = React.useState({
|
|
18
19
|
status: 'initializing',
|
|
19
20
|
message: 'Starting webpack watch...',
|
|
@@ -34,6 +35,16 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, deploy
|
|
|
34
35
|
const isBuildingRef = React.useRef(false);
|
|
35
36
|
const pendingRebuildRef = React.useRef(false);
|
|
36
37
|
// Sign (if needed) and deploy an already-built zip, updating UI state.
|
|
38
|
+
const refreshVersion = React.useCallback(() => {
|
|
39
|
+
try {
|
|
40
|
+
const fresh = readManifest(projectRoot)?.manifest.version;
|
|
41
|
+
if (fresh)
|
|
42
|
+
setVersion(fresh);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// keep last known version
|
|
46
|
+
}
|
|
47
|
+
}, [projectRoot]);
|
|
37
48
|
const signAndDeploy = React.useCallback(async (zipPath, buildTime) => {
|
|
38
49
|
try {
|
|
39
50
|
let deployZipPath = zipPath;
|
|
@@ -57,6 +68,7 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, deploy
|
|
|
57
68
|
}
|
|
58
69
|
deployZipPath = signedZipPath;
|
|
59
70
|
}
|
|
71
|
+
refreshVersion();
|
|
60
72
|
// Watch/build-only mode: stop after building (and signing).
|
|
61
73
|
if (!deploy || !devProperties) {
|
|
62
74
|
setState(prev => ({
|
|
@@ -113,7 +125,15 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, deploy
|
|
|
113
125
|
error: error instanceof Error ? error.message : String(error),
|
|
114
126
|
}));
|
|
115
127
|
}
|
|
116
|
-
}, [
|
|
128
|
+
}, [
|
|
129
|
+
projectRoot,
|
|
130
|
+
manifest,
|
|
131
|
+
devProperties,
|
|
132
|
+
signed,
|
|
133
|
+
deploy,
|
|
134
|
+
signingCredentials,
|
|
135
|
+
refreshVersion,
|
|
136
|
+
]);
|
|
117
137
|
// In-house webpack path: copy static, zip, then sign + deploy.
|
|
118
138
|
const handleBuildComplete = React.useCallback(async (result) => {
|
|
119
139
|
if (!result.success) {
|
|
@@ -353,7 +373,7 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, deploy
|
|
|
353
373
|
};
|
|
354
374
|
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
|
|
355
375
|
? ` | Last build: ${state.lastBuildTime}ms`
|
|
356
|
-
: '', signed ? ' | Signed mode' : ''] }) })), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [manifest.name, " v",
|
|
376
|
+
: '', signed ? ' | Signed mode' : ''] }) })), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [localizedText(manifest.name), " v", version] }) }), deploy && 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" }))] })] }));
|
|
357
377
|
}
|
|
358
378
|
export const devCommand = {
|
|
359
379
|
name: 'dev',
|
|
@@ -374,6 +394,12 @@ export const devCommand = {
|
|
|
374
394
|
console.log('Create a .dev_properties.json file with domain, siteName, addonName, and username, then run setup.\n');
|
|
375
395
|
return;
|
|
376
396
|
}
|
|
397
|
+
// dev/watch continuously redeploy and only support basic auth. Fail clearly
|
|
398
|
+
// on an OAuth2/cookie config instead of prompting for an unusable password.
|
|
399
|
+
if ((project.devProperties.authMethod ?? 'basic') !== 'basic') {
|
|
400
|
+
console.log('\n\x1b[33mdev/watch support only basic auth. Use `svc deploy` for OAuth2/cookie.\x1b[0m\n');
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
377
403
|
// Resolve deploy password (already loaded from keychain/env in detectProject — prompt if missing)
|
|
378
404
|
if (!project.devProperties.password) {
|
|
379
405
|
const { domain, username } = project.devProperties;
|
package/dist/commands/info.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { render } from 'ink';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
-
import { getAppType } from '../utils/project-detection.js';
|
|
4
|
+
import { getAppType, localizedText } from '../utils/project-detection.js';
|
|
5
5
|
function InfoScreen({ project }) {
|
|
6
6
|
const appType = getAppType(project.manifest);
|
|
7
|
-
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: _jsxs(Text, { color: "yellow", children: ["\u26A0 No dev properties found. Run", ' ', _jsx(Text, { bold: true, children: "setup-dev-properties" }), " to configure."] }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
|
|
7
|
+
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: _jsxs(Text, { color: "yellow", children: ["\u26A0 No dev properties found. Run", ' ', _jsx(Text, { bold: true, children: "setup-dev-properties" }), " to configure."] }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
|
|
8
8
|
}
|
|
9
9
|
export const infoCommand = {
|
|
10
10
|
name: 'info',
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { DevProperties } from '../types/index.js';
|
|
3
|
+
interface Credential {
|
|
4
|
+
accessToken?: string;
|
|
5
|
+
sessionCookie?: string;
|
|
6
|
+
}
|
|
7
|
+
interface Props {
|
|
8
|
+
method: 'oauth2' | 'cookie';
|
|
9
|
+
devProperties: DevProperties;
|
|
10
|
+
onComplete: (credential: Credential) => void;
|
|
11
|
+
onError: (message: string) => void;
|
|
12
|
+
onCancel: () => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Ink-native interactive login for OAuth2 and session-cookie auth. Drives the
|
|
16
|
+
* browser and (for cookie) the "press Enter to capture" handoff through Ink's
|
|
17
|
+
* own input, so it works inside the TUI as well as the standalone command —
|
|
18
|
+
* neither needs to own raw stdin the way the old console prompt did.
|
|
19
|
+
*/
|
|
20
|
+
export declare function AuthLoginScreen({ method, devProperties, onComplete, onError, onCancel, }: Props): React.JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import { beginOAuth2Login, openBrowser } from '../utils/oauth2-auth.js';
|
|
6
|
+
import { beginCookieLogin, } from '../utils/session-cookie-auth.js';
|
|
7
|
+
/**
|
|
8
|
+
* Ink-native interactive login for OAuth2 and session-cookie auth. Drives the
|
|
9
|
+
* browser and (for cookie) the "press Enter to capture" handoff through Ink's
|
|
10
|
+
* own input, so it works inside the TUI as well as the standalone command —
|
|
11
|
+
* neither needs to own raw stdin the way the old console prompt did.
|
|
12
|
+
*/
|
|
13
|
+
export function AuthLoginScreen({ method, devProperties, onComplete, onError, onCancel, }) {
|
|
14
|
+
const [phase, setPhase] = React.useState('starting');
|
|
15
|
+
const [authUrl, setAuthUrl] = React.useState('');
|
|
16
|
+
const [note, setNote] = React.useState('');
|
|
17
|
+
const cookieRef = React.useRef(null);
|
|
18
|
+
const cancelOAuthRef = React.useRef(null);
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
void (async () => {
|
|
21
|
+
if (method === 'oauth2') {
|
|
22
|
+
const session = beginOAuth2Login(devProperties);
|
|
23
|
+
if (!session) {
|
|
24
|
+
onError('OAuth2 is not fully configured (authorization/token endpoint or client ID missing).');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
cancelOAuthRef.current = session.cancel;
|
|
28
|
+
setAuthUrl(session.authUrl);
|
|
29
|
+
openBrowser(session.authUrl);
|
|
30
|
+
setPhase('awaiting');
|
|
31
|
+
const token = await session.complete();
|
|
32
|
+
cancelOAuthRef.current = null;
|
|
33
|
+
if (token) {
|
|
34
|
+
onComplete({ accessToken: token });
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
onError('OAuth2 login failed, timed out, or was rejected.');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const session = await beginCookieLogin(devProperties);
|
|
42
|
+
if (!session) {
|
|
43
|
+
onError('Could not open a login browser (is Chrome installed?). Set SITEVISION_SESSION_COOKIE or pass --cookie with a cookie copied from your browser.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
cookieRef.current = session;
|
|
47
|
+
setPhase('awaiting');
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
// Release resources if the screen unmounts before completing: close the
|
|
51
|
+
// browser (cookie) and the loopback server (oauth2, frees the port).
|
|
52
|
+
return () => {
|
|
53
|
+
void cookieRef.current?.close();
|
|
54
|
+
cookieRef.current = null;
|
|
55
|
+
cancelOAuthRef.current?.();
|
|
56
|
+
cancelOAuthRef.current = null;
|
|
57
|
+
};
|
|
58
|
+
// Run once: the parent mounts this fresh when a login is needed.
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, []);
|
|
61
|
+
useInput((_input, key) => {
|
|
62
|
+
if (key.escape) {
|
|
63
|
+
onCancel();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (method === 'cookie' && phase === 'awaiting' && key.return) {
|
|
67
|
+
const session = cookieRef.current;
|
|
68
|
+
if (!session)
|
|
69
|
+
return;
|
|
70
|
+
setPhase('capturing');
|
|
71
|
+
void (async () => {
|
|
72
|
+
const result = await session.capture();
|
|
73
|
+
if (result.cookie) {
|
|
74
|
+
cookieRef.current = null;
|
|
75
|
+
await session.close();
|
|
76
|
+
onComplete({ sessionCookie: result.cookie });
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Keep the browser open so the user can navigate and retry.
|
|
80
|
+
setNote(result.error ?? 'No session cookie found.');
|
|
81
|
+
setPhase('awaiting');
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: method === 'oauth2' ? 'OAuth2 login' : 'Session cookie login' }) }), method === 'oauth2' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Waiting for you to finish login in the browser\u2026" })] }), authUrl && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "If the browser didn't open, visit:" }), _jsx(Text, { children: authUrl })] }))] })) : (_jsxs(Box, { flexDirection: "column", children: [phase === 'starting' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Opening browser\u2026" })] })), phase === 'awaiting' && (_jsx(Text, { children: "Log in in the opened browser, then press Enter to capture the session." })), phase === 'capturing' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Capturing session\u2026" })] })), note && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: note }) }))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Esc to cancel." }) })] }));
|
|
87
|
+
}
|