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 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
- export default function App({ project }) {
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 (!hasDevPassword) {
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
- // We can't easily update project info here without full reload,
264
- // but since we are just returning to menu, it's fine.
265
- // The user might need to restart CLI or we implement a reload mechanism.
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, signingPassword, onBack, onRetryCredentials, }: DeployScreenProps): React.JSX.Element;
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 {};
@@ -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
- export function DeployScreen({ projectRoot, manifest, devProperties, force, production, activate, signingPassword, onBack, onRetryCredentials, }) {
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
- projectRoot,
105
- manifest,
106
- devProperties,
107
- force,
108
- production,
109
- activate,
110
- signingPassword,
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
- // Resolve deploy password (already loaded from keychain/env in detectProject prompt if missing)
150
- if (!project.devProperties.password) {
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
- // For production, we need the signed zip, which requires signing credentials
168
- let signingPassword;
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
  };
@@ -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
- }, [projectRoot, manifest, devProperties, signed, deploy, signingCredentials]);
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", manifest.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" }))] })] }));
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;
@@ -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
+ }