sitevision-cli 1.0.0-beta.8 → 1.0.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/LICENSE +21 -0
  3. package/dist/cli.js +194 -70
  4. package/dist/commands/TaskScreen.js +32 -0
  5. package/dist/commands/build.js +6 -185
  6. package/dist/commands/deploy.js +95 -212
  7. package/dist/commands/dev.js +87 -416
  8. package/dist/commands/info.js +1 -1
  9. package/dist/commands/setup-signing.js +50 -33
  10. package/dist/commands/sign.js +9 -86
  11. package/dist/commands/use-finish.js +17 -0
  12. package/dist/commands/watch.js +5 -31
  13. package/dist/components/AnimatedLogo.js +8 -2
  14. package/dist/components/AuthLoginScreen.js +33 -15
  15. package/dist/components/PasswordInput.js +2 -1
  16. package/dist/scaffold/inquirer-stub.js +119 -0
  17. package/dist/scaffold/register.js +15 -0
  18. package/dist/shell/AddonPicker.js +54 -0
  19. package/dist/shell/Changelog.js +44 -0
  20. package/dist/shell/CommandPalette.js +63 -0
  21. package/dist/shell/ConfigForm.js +703 -0
  22. package/dist/shell/Frame.js +138 -0
  23. package/dist/shell/Help.js +96 -0
  24. package/dist/shell/Popover.js +15 -0
  25. package/dist/shell/Settings.js +149 -0
  26. package/dist/shell/Shell.js +953 -0
  27. package/dist/shell/Tabs.js +96 -0
  28. package/dist/shell/actions.js +493 -0
  29. package/dist/utils/config.js +62 -2
  30. package/dist/utils/create-app.js +231 -0
  31. package/dist/utils/debug.js +32 -0
  32. package/dist/utils/environments.js +74 -0
  33. package/dist/utils/i18n.js +369 -0
  34. package/dist/utils/jsonc.js +2 -1
  35. package/dist/utils/keychain.js +54 -6
  36. package/dist/utils/oauth2-auth.js +146 -43
  37. package/dist/utils/password-prompt.js +37 -3
  38. package/dist/utils/process-runner.js +77 -38
  39. package/dist/utils/project-detection.js +457 -112
  40. package/dist/utils/session-cookie-auth.js +10 -6
  41. package/dist/utils/sitevision-api.js +162 -32
  42. package/dist/utils/sitevision-scripts-runner.js +50 -22
  43. package/dist/utils/tasks.js +510 -0
  44. package/dist/utils/version-check.js +3 -3
  45. package/dist/utils/webpack-runner.js +9 -5
  46. package/dist/utils/workspace.js +105 -0
  47. package/dist/utils/zip.js +18 -19
  48. package/package.json +27 -4
  49. package/readme.md +113 -141
  50. package/dist/app.d.ts +0 -6
  51. package/dist/app.js +0 -307
  52. package/dist/cli.d.ts +0 -2
  53. package/dist/commands/build.d.ts +0 -12
  54. package/dist/commands/deploy.d.ts +0 -16
  55. package/dist/commands/dev.d.ts +0 -16
  56. package/dist/commands/index.d.ts +0 -4
  57. package/dist/commands/info.d.ts +0 -2
  58. package/dist/commands/setup-signing.d.ts +0 -2
  59. package/dist/commands/sign.d.ts +0 -14
  60. package/dist/commands/types.d.ts +0 -18
  61. package/dist/commands/watch.d.ts +0 -2
  62. package/dist/components/AnimatedLogo.d.ts +0 -10
  63. package/dist/components/AuthLoginScreen.d.ts +0 -21
  64. package/dist/components/DevPropertiesForm.d.ts +0 -10
  65. package/dist/components/DevPropertiesForm.js +0 -210
  66. package/dist/components/InfoScreen.d.ts +0 -7
  67. package/dist/components/InfoScreen.js +0 -14
  68. package/dist/components/KeychainPasswordChoice.d.ts +0 -8
  69. package/dist/components/KeychainPasswordChoice.js +0 -30
  70. package/dist/components/MainMenu.d.ts +0 -7
  71. package/dist/components/MainMenu.js +0 -83
  72. package/dist/components/PasswordInput.d.ts +0 -10
  73. package/dist/components/ProcessOutput.d.ts +0 -6
  74. package/dist/components/ProcessOutput.js +0 -30
  75. package/dist/components/SetupFlow.d.ts +0 -8
  76. package/dist/components/SetupFlow.js +0 -233
  77. package/dist/components/SigningPropertiesForm.d.ts +0 -7
  78. package/dist/components/SigningPropertiesForm.js +0 -38
  79. package/dist/components/StatusIndicator.d.ts +0 -8
  80. package/dist/components/TextInput.d.ts +0 -10
  81. package/dist/components/TextInput.js +0 -29
  82. package/dist/components/WelcomeScreen.d.ts +0 -14
  83. package/dist/types/index.d.ts +0 -284
  84. package/dist/utils/branding.d.ts +0 -28
  85. package/dist/utils/config.d.ts +0 -17
  86. package/dist/utils/jsonc.d.ts +0 -19
  87. package/dist/utils/keychain.d.ts +0 -15
  88. package/dist/utils/oauth2-auth.d.ts +0 -27
  89. package/dist/utils/password-prompt.d.ts +0 -14
  90. package/dist/utils/process-runner.d.ts +0 -22
  91. package/dist/utils/project-detection.d.ts +0 -151
  92. package/dist/utils/session-cookie-auth.d.ts +0 -34
  93. package/dist/utils/signing-password.d.ts +0 -12
  94. package/dist/utils/signing-step.d.ts +0 -15
  95. package/dist/utils/signing-step.js +0 -16
  96. package/dist/utils/sitevision-api.d.ts +0 -112
  97. package/dist/utils/sitevision-scripts-runner.d.ts +0 -83
  98. package/dist/utils/version-check.d.ts +0 -9
  99. package/dist/utils/webpack-runner.d.ts +0 -76
  100. package/dist/utils/zip.d.ts +0 -71
@@ -1,443 +1,114 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import fs from 'fs';
3
- import path from 'path';
4
2
  import React from 'react';
5
- import { render, Box, Text, useApp, useInput } from 'ink';
3
+ import { render, Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';
6
4
  import { StatusIndicator } from '../components/StatusIndicator.js';
7
- import { WebpackRunner, hasLocalWebpackConfig } from '../utils/webpack-runner.js';
8
- import { hasSitevisionScripts, runSitevisionScriptsBuild, getDelegatedZipPath, checkSitevisionScriptsCompatibility, } from '../utils/sitevision-scripts-runner.js';
9
5
  import { promptPassword, promptYesNo } from '../utils/password-prompt.js';
10
- import { signApp, deployApp } from '../utils/sitevision-api.js';
11
6
  import { setDeployPassword } from '../utils/keychain.js';
12
7
  import { resolveSigningPassword } from '../utils/signing-password.js';
13
- import { copyStaticToBuild, createBuildZip, cleanBuild } from '../utils/zip.js';
14
- import { isBundledApp, getAppType, getFullAppId, getZipPath, getSignedZipPath, localizedText, readManifest, } from '../utils/project-detection.js';
15
- export function DevScreen({ projectRoot, manifest, devProperties, signed, deploy = true, signingCredentials, onBack, onRetryCredentials, }) {
8
+ import { startDev, useTasks } from '../utils/tasks.js';
9
+ import { toDeployConfig } from '../utils/workspace.js';
10
+ import { Log } from '../shell/Tabs.js';
11
+ import { say } from '../utils/debug.js';
12
+ /** Standalone `svc dev` / `svc watch`: one task, its log, Ctrl+C or q to stop. */
13
+ export function DevScreen({ project, options }) {
16
14
  const { exit } = useApp();
17
- const [version, setVersion] = React.useState(manifest.version);
18
- const [state, setState] = React.useState({
19
- status: 'initializing',
20
- message: 'Starting webpack watch...',
21
- buildCount: 0,
22
- webpackReady: false,
23
- });
15
+ const { stdout } = useStdout();
16
+ const tasks = useTasks();
17
+ // eslint-disable-next-line react-hooks/exhaustive-deps
18
+ const task = React.useMemo(() => startDev(project, options), []);
19
+ const live = tasks.find(t => t.id === task.id) ?? task;
20
+ const { isRawModeSupported } = useStdin();
24
21
  useInput((input, key) => {
25
- if (onBack && (key.escape || input === 'q')) {
26
- onBack();
27
- }
28
- if (onRetryCredentials && state.status === 'error' && input === 'r') {
29
- onRetryCredentials();
30
- }
31
- });
32
- const webpackRunnerRef = React.useRef(null);
33
- const watchersRef = React.useRef([]);
34
- const debounceTimerRef = React.useRef(null);
35
- const isBuildingRef = React.useRef(false);
36
- const pendingRebuildRef = React.useRef(false);
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]);
48
- const signAndDeploy = React.useCallback(async (zipPath, buildTime) => {
49
- try {
50
- let deployZipPath = zipPath;
51
- // Sign if needed
52
- if (signed && signingCredentials) {
53
- setState(prev => ({
54
- ...prev,
55
- status: 'signing',
56
- message: 'Signing app...',
57
- }));
58
- const signedZipPath = getSignedZipPath(projectRoot, manifest);
59
- const signResult = await signApp(zipPath, signingCredentials, signedZipPath);
60
- if (!signResult.success) {
61
- setState(prev => ({
62
- ...prev,
63
- status: 'error',
64
- message: `Signing failed: ${signResult.error}`,
65
- error: signResult.error,
66
- }));
67
- return;
68
- }
69
- deployZipPath = signedZipPath;
70
- }
71
- refreshVersion();
72
- // Watch/build-only mode: stop after building (and signing).
73
- if (!deploy || !devProperties) {
74
- setState(prev => ({
75
- ...prev,
76
- status: 'ready',
77
- message: signed
78
- ? 'Signed. Watching for changes...'
79
- : 'Built. Watching for changes...',
80
- buildCount: prev.buildCount + 1,
81
- lastBuildTime: buildTime,
82
- error: undefined,
83
- }));
84
- return;
85
- }
86
- // Deploy
87
- setState(prev => ({
88
- ...prev,
89
- status: 'deploying',
90
- message: 'Deploying to dev...',
91
- }));
92
- const appType = getAppType(manifest);
93
- const deployResult = await deployApp(deployZipPath, {
94
- domain: devProperties.domain,
95
- siteName: devProperties.siteName,
96
- addonName: devProperties.addonName,
97
- username: devProperties.username,
98
- password: devProperties.password,
99
- useHTTP: devProperties.useHTTPForDevDeploy,
100
- }, appType, true);
101
- if (!deployResult.success) {
102
- setState(prev => ({
103
- ...prev,
104
- status: 'error',
105
- message: `Deploy failed: ${deployResult.error}`,
106
- error: deployResult.error,
107
- }));
108
- return;
109
- }
110
- // Success - back to watching
111
- setState(prev => ({
112
- ...prev,
113
- status: 'ready',
114
- message: 'Deployed. Watching for changes...',
115
- buildCount: prev.buildCount + 1,
116
- lastBuildTime: buildTime,
117
- error: undefined,
118
- }));
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
- }, [
129
- projectRoot,
130
- manifest,
131
- devProperties,
132
- signed,
133
- deploy,
134
- signingCredentials,
135
- refreshVersion,
136
- ]);
137
- // In-house webpack path: copy static, zip, then sign + deploy.
138
- const handleBuildComplete = React.useCallback(async (result) => {
139
- if (!result.success) {
140
- setState(prev => ({
141
- ...prev,
142
- status: 'error',
143
- message: result.errors?.join('\n') || 'Build failed',
144
- error: result.errors?.join('\n'),
145
- }));
146
- return;
147
- }
148
- try {
149
- copyStaticToBuild(projectRoot);
150
- const appId = getFullAppId(manifest.id);
151
- await createBuildZip(projectRoot, appId);
152
- await signAndDeploy(getZipPath(projectRoot, manifest), result.stats?.time);
153
- }
154
- catch (error) {
155
- setState(prev => ({
156
- ...prev,
157
- status: 'error',
158
- message: error instanceof Error ? error.message : String(error),
159
- error: error instanceof Error ? error.message : String(error),
160
- }));
161
- }
162
- }, [projectRoot, manifest, signAndDeploy]);
163
- // Delegated path: full `sitevision-scripts build` then sign + deploy.
164
- // Coalesces overlapping triggers so a save mid-build queues one rebuild.
165
- const runDelegatedBuild = React.useCallback(async () => {
166
- if (isBuildingRef.current) {
167
- pendingRebuildRef.current = true;
168
- return;
169
- }
170
- isBuildingRef.current = true;
171
- const buildOnce = async () => {
172
- pendingRebuildRef.current = false;
173
- setState(prev => ({
174
- ...prev,
175
- status: 'building',
176
- message: 'Building via sitevision-scripts...',
177
- }));
178
- const result = await runSitevisionScriptsBuild(projectRoot);
179
- if (result.success) {
180
- await signAndDeploy(getDelegatedZipPath(projectRoot, manifest.id));
181
- }
182
- else {
183
- setState(prev => ({
184
- ...prev,
185
- status: 'error',
186
- message: result.error ?? 'Build failed',
187
- error: `${result.error}\n${result.output.slice(-1000)}`,
188
- }));
189
- }
190
- if (pendingRebuildRef.current) {
191
- await buildOnce();
192
- }
193
- };
194
- try {
195
- await buildOnce();
196
- }
197
- finally {
198
- isBuildingRef.current = false;
199
- }
200
- }, [projectRoot, manifest, signAndDeploy]);
201
- // Watch source files and trigger a delegated rebuild (debounced).
202
- const startFileWatcher = React.useCallback(() => {
203
- const targets = [
204
- 'src',
205
- 'static',
206
- 'i18n',
207
- 'resource',
208
- 'config',
209
- 'manifest.json',
210
- ]
211
- .map(name => path.join(projectRoot, name))
212
- .filter(target => fs.existsSync(target));
213
- for (const target of targets) {
214
- const isDir = fs.statSync(target).isDirectory();
215
- const watcher = fs.watch(target, { recursive: isDir }, () => {
216
- if (debounceTimerRef.current) {
217
- clearTimeout(debounceTimerRef.current);
218
- }
219
- debounceTimerRef.current = setTimeout(() => {
220
- void runDelegatedBuild();
221
- }, 300);
222
- });
223
- watchersRef.current.push(watcher);
224
- }
225
- }, [projectRoot, runDelegatedBuild]);
226
- React.useEffect(() => {
227
- const isBundled = isBundledApp(manifest);
228
- async function startWatch() {
229
- try {
230
- // Clean build directory
231
- cleanBuild(projectRoot);
232
- if (isBundled && !hasLocalWebpackConfig(projectRoot)) {
233
- // No local webpack config: delegate each build to
234
- // sitevision-scripts (full rebuild) and watch source files
235
- // ourselves, keeping the CLI's own sign + deploy flow.
236
- if (!hasSitevisionScripts(projectRoot)) {
237
- setState({
238
- status: 'error',
239
- message: 'No webpack.config.js found and @sitevision/sitevision-scripts is not installed. Run npm install.',
240
- buildCount: 0,
241
- webpackReady: false,
242
- error: 'No build pipeline available',
243
- });
244
- return;
245
- }
246
- const warning = checkSitevisionScriptsCompatibility(projectRoot).warning;
247
- setState(prev => ({ ...prev, webpackReady: true, warning }));
248
- startFileWatcher();
249
- await runDelegatedBuild();
250
- }
251
- else if (isBundled) {
252
- // Project ships its own webpack config: incremental in-process watch.
253
- if (!WebpackRunner.isWebpackAvailable(projectRoot)) {
254
- setState({
255
- status: 'error',
256
- message: 'webpack not found. Run npm install.',
257
- buildCount: 0,
258
- webpackReady: false,
259
- error: 'webpack not found',
260
- });
261
- return;
262
- }
263
- setState(prev => ({
264
- ...prev,
265
- status: 'building',
266
- message: 'Starting initial build...',
267
- }));
268
- const appType = getAppType(manifest);
269
- const runner = new WebpackRunner(projectRoot, {
270
- mode: 'development',
271
- watch: true,
272
- cssPrefix: manifest.id,
273
- restApp: appType === 'rest',
274
- });
275
- webpackRunnerRef.current = runner;
276
- await runner.watch(handleBuildComplete);
277
- setState(prev => ({
278
- ...prev,
279
- status: 'watching',
280
- message: 'Building...',
281
- webpackReady: true,
282
- }));
283
- }
284
- else {
285
- // Non-bundled app: just copy and deploy
286
- setState(prev => ({
287
- ...prev,
288
- status: 'building',
289
- message: 'Copying files...',
290
- }));
291
- await handleBuildComplete({
292
- success: true,
293
- stats: { time: 0, hash: '', assets: [] },
294
- });
295
- }
296
- }
297
- catch (error) {
298
- setState({
299
- status: 'error',
300
- message: error instanceof Error ? error.message : String(error),
301
- buildCount: 0,
302
- webpackReady: false,
303
- error: error instanceof Error ? error.message : String(error),
304
- });
305
- }
306
- }
307
- startWatch();
308
- // Cleanup
309
- return () => {
310
- if (webpackRunnerRef.current) {
311
- webpackRunnerRef.current.close().catch(() => { });
312
- }
313
- if (debounceTimerRef.current) {
314
- clearTimeout(debounceTimerRef.current);
315
- }
316
- for (const watcher of watchersRef.current) {
317
- watcher.close();
318
- }
319
- watchersRef.current = [];
320
- };
321
- }, [
322
- projectRoot,
323
- manifest,
324
- handleBuildComplete,
325
- runDelegatedBuild,
326
- startFileWatcher,
327
- ]);
328
- // Handle Ctrl+C
329
- React.useEffect(() => {
330
- const handleExit = () => {
331
- if (webpackRunnerRef.current) {
332
- webpackRunnerRef.current.close().catch(() => { });
333
- }
334
- for (const watcher of watchersRef.current) {
335
- watcher.close();
336
- }
22
+ if (key.escape || input === 'q') {
23
+ live.stop();
337
24
  exit();
338
- };
339
- process.on('SIGINT', handleExit);
340
- process.on('SIGTERM', handleExit);
341
- return () => {
342
- process.off('SIGINT', handleExit);
343
- process.off('SIGTERM', handleExit);
344
- };
345
- }, [exit]);
346
- const getStatusType = () => {
347
- switch (state.status) {
348
- case 'error':
349
- return 'error';
350
- case 'ready':
351
- return 'success';
352
- default:
353
- return 'running';
354
- }
355
- };
356
- const getStatusLabel = () => {
357
- switch (state.status) {
358
- case 'initializing':
359
- return 'Initializing';
360
- case 'watching':
361
- return 'Watching';
362
- case 'building':
363
- return 'Building';
364
- case 'signing':
365
- return 'Signing';
366
- case 'deploying':
367
- return 'Deploying';
368
- case 'ready':
369
- return 'Ready';
370
- case 'error':
371
- return 'Error';
372
25
  }
26
+ },
27
+ // Undefined, not false, on a pipe; and useInput reads undefined as active.
28
+ { isActive: Boolean(isRawModeSupported) });
29
+ const status = live.status === 'running'
30
+ ? live.phase === 'error'
31
+ ? 'error'
32
+ : 'running'
33
+ : live.status === 'success'
34
+ ? 'success'
35
+ : 'error';
36
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(StatusIndicator, { status: status, label: live.phase, message: `${live.label} ${live.appName}` }), _jsx(Box, { marginTop: 1, children: _jsx(Log, { task: live, height: (stdout.rows || 24) - 6, scroll: 0, wrap: true }) }), _jsx(Text, { dimColor: true, children: "Press q, Esc or Ctrl+C to stop" })] }));
37
+ }
38
+ /** Resolve signing credentials for signed dev/watch, prompting if needed. */
39
+ export async function resolveSigningForCli(project) {
40
+ if (!project.hasSigningProperties ||
41
+ !project.devProperties?.signingUsername) {
42
+ say('\n\x1b[33mSigning credentials not configured.\x1b[0m');
43
+ say('Run \x1b[36msetup-signing\x1b[0m to configure credentials.\n');
44
+ process.exitCode = 1;
45
+ return undefined;
46
+ }
47
+ const signingUsername = project.devProperties.signingUsername;
48
+ const password = await resolveSigningPassword(signingUsername);
49
+ if (!password) {
50
+ say('\x1b[31mError: Password is required for signed mode\x1b[0m');
51
+ process.exitCode = 1;
52
+ return undefined;
53
+ }
54
+ return {
55
+ username: signingUsername,
56
+ password,
57
+ certificateName: project.devProperties.certificateName,
373
58
  };
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
375
- ? ` | Last build: ${state.lastBuildTime}ms`
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" }))] })] }));
59
+ }
60
+ /** Basic auth without a stored password: ask for it. False when there is none. */
61
+ export async function resolveDeployPasswordForCli(dev) {
62
+ if ((dev.authMethod ?? 'basic') !== 'basic' || dev.password)
63
+ return true;
64
+ const { domain, username } = dev;
65
+ console.log('');
66
+ const password = await promptPassword(`Deploy password for ${username}@${domain}: `);
67
+ if (!password) {
68
+ say('\x1b[31mError: Password is required\x1b[0m');
69
+ process.exitCode = 1;
70
+ return false;
71
+ }
72
+ if (await promptYesNo('Save password to OS keychain? (y/N): ')) {
73
+ setDeployPassword(domain, username, password);
74
+ }
75
+ dev.password = password;
76
+ return true;
377
77
  }
378
78
  export const devCommand = {
379
79
  name: 'dev',
380
80
  description: 'Start development server with watch mode',
381
81
  requiresProject: true,
382
- flags: {
383
- signed: {
384
- type: 'boolean',
385
- description: 'Use signed mode (sign before each deploy)',
386
- alias: 's',
387
- default: false,
388
- },
389
- },
390
82
  async execute({ project, flags }) {
391
- // Check if dev properties are configured
392
- if (!project.hasDevProperties || !project.devProperties) {
393
- console.log('\n\x1b[33mDeployment credentials not configured.\x1b[0m');
394
- console.log('Create a .dev_properties.json file with domain, siteName, addonName, and username, then run setup.\n');
83
+ const dev = project.devProperties;
84
+ const incomplete = toDeployConfig(dev);
85
+ if (!dev || 'error' in incomplete) {
86
+ say(`\n\x1b[33m${'error' in incomplete ? incomplete.error : ''}\x1b[0m\n`);
87
+ process.exitCode = 1;
395
88
  return;
396
89
  }
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');
90
+ // The standalone command only prompts for a basic password; OAuth2/cookie
91
+ // logins are interactive and live in the shell (`svc`) or `svc deploy`.
92
+ if ((dev.authMethod ?? 'basic') !== 'basic' &&
93
+ !dev.accessToken &&
94
+ !dev.sessionCookie) {
95
+ say('\n\x1b[33mNo token/cookie available. Run `svc` and use Dev from the shell, or set SITEVISION_ACCESS_TOKEN / SITEVISION_SESSION_COOKIE.\x1b[0m\n');
96
+ process.exitCode = 1;
401
97
  return;
402
98
  }
403
- // Resolve deploy password (already loaded from keychain/env in detectProject — prompt if missing)
404
- if (!project.devProperties.password) {
405
- const { domain, username } = project.devProperties;
406
- console.log('');
407
- const pw = await promptPassword(`Deploy password for ${username}@${domain}: `);
408
- if (!pw) {
409
- console.log('\x1b[31mError: Password is required\x1b[0m');
410
- return;
411
- }
412
- const remember = await promptYesNo('Save password to OS keychain? (y/N): ');
413
- if (remember && domain && username) {
414
- setDeployPassword(domain, username, pw);
415
- }
416
- project.devProperties.password = pw;
417
- }
418
- const signed = Boolean(flags['signed']);
99
+ if (!(await resolveDeployPasswordForCli(dev)))
100
+ return;
419
101
  let signingCredentials;
420
- // If signed mode, resolve signing password (keychain → env → prompt)
421
- if (signed) {
422
- if (!project.hasSigningProperties ||
423
- !project.devProperties.signingUsername) {
424
- console.log('\n\x1b[33mSigning credentials not configured.\x1b[0m');
425
- console.log('Run \x1b[36msetup-signing\x1b[0m to configure credentials.\n');
426
- return;
427
- }
428
- const signingUsername = project.devProperties.signingUsername;
429
- const password = await resolveSigningPassword(signingUsername);
430
- if (!password) {
431
- console.log('\x1b[31mError: Password is required for signed mode\x1b[0m');
102
+ if (flags['signed']) {
103
+ signingCredentials = await resolveSigningForCli(project);
104
+ if (!signingCredentials)
432
105
  return;
433
- }
434
- signingCredentials = {
435
- username: signingUsername,
436
- password,
437
- certificateName: project.devProperties.certificateName,
438
- };
439
106
  }
440
- const { waitUntilExit } = render(_jsx(DevScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: project.devProperties, signed: signed, signingCredentials: signingCredentials }));
107
+ const { waitUntilExit } = render(_jsx(DevScreen, { project: project, options: {
108
+ deploy: true,
109
+ signingCredentials,
110
+ deployConfig: { ...incomplete.config, password: dev.password },
111
+ } }));
441
112
  await waitUntilExit();
442
113
  },
443
114
  };
@@ -4,7 +4,7 @@ import { Box, Text } from 'ink';
4
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: 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 })] })] }));
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: "svc" }), " and press", ' ', _jsx(Text, { bold: true, children: "e" }), " 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',