sitevision-cli 1.0.0-beta.13 → 1.0.0-beta.15

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 (43) hide show
  1. package/dist/cli.js +70 -41
  2. package/dist/commands/build.js +1 -1
  3. package/dist/commands/dev.d.ts +8 -10
  4. package/dist/commands/dev.js +75 -390
  5. package/dist/commands/watch.js +5 -23
  6. package/dist/components/AuthLoginScreen.js +2 -1
  7. package/dist/components/DevPropertiesForm.js +6 -3
  8. package/dist/components/PasswordInput.js +2 -1
  9. package/dist/shell/AddonPicker.d.ts +14 -0
  10. package/dist/shell/AddonPicker.js +54 -0
  11. package/dist/shell/CommandPalette.d.ts +8 -0
  12. package/dist/shell/CommandPalette.js +63 -0
  13. package/dist/shell/ConfigForm.d.ts +35 -0
  14. package/dist/shell/ConfigForm.js +472 -0
  15. package/dist/shell/Frame.d.ts +52 -0
  16. package/dist/shell/Frame.js +98 -0
  17. package/dist/shell/Settings.d.ts +6 -0
  18. package/dist/shell/Settings.js +96 -0
  19. package/dist/shell/Shell.d.ts +8 -0
  20. package/dist/shell/Shell.js +520 -0
  21. package/dist/shell/Tabs.d.ts +36 -0
  22. package/dist/shell/Tabs.js +85 -0
  23. package/dist/shell/actions.d.ts +45 -0
  24. package/dist/shell/actions.js +0 -0
  25. package/dist/types/index.d.ts +12 -2
  26. package/dist/utils/config.d.ts +10 -0
  27. package/dist/utils/config.js +14 -0
  28. package/dist/utils/environments.d.ts +20 -0
  29. package/dist/utils/environments.js +74 -0
  30. package/dist/utils/i18n.d.ts +12 -0
  31. package/dist/utils/i18n.js +263 -0
  32. package/dist/utils/oauth2-auth.d.ts +1 -0
  33. package/dist/utils/oauth2-auth.js +9 -12
  34. package/dist/utils/project-detection.d.ts +23 -1
  35. package/dist/utils/project-detection.js +124 -51
  36. package/dist/utils/sitevision-api.d.ts +35 -0
  37. package/dist/utils/sitevision-api.js +74 -1
  38. package/dist/utils/tasks.d.ts +48 -0
  39. package/dist/utils/tasks.js +371 -0
  40. package/dist/utils/workspace.d.ts +8 -0
  41. package/dist/utils/workspace.js +48 -0
  42. package/package.json +2 -1
  43. package/readme.md +76 -24
@@ -1,379 +1,66 @@
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, 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 { Log } from '../shell/Tabs.js';
10
+ /** Standalone `svc dev` / `svc watch`: one task, its log, Ctrl+C or q to stop. */
11
+ export function DevScreen({ project, options }) {
16
12
  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
- });
13
+ const { stdout } = useStdout();
14
+ const tasks = useTasks();
15
+ // eslint-disable-next-line react-hooks/exhaustive-deps
16
+ const task = React.useMemo(() => startDev(project, options), []);
17
+ const live = tasks.find(t => t.id === task.id) ?? task;
24
18
  useInput((input, key) => {
25
- if (onBack && (key.escape || input === 'q')) {
26
- onBack();
27
- }
28
- if (onRetryCredentials && state.status === 'error' && input === 'r') {
29
- onRetryCredentials();
19
+ if (key.escape || input === 'q') {
20
+ live.stop();
21
+ exit();
30
22
  }
31
23
  });
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
24
  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
- }
25
+ const stop = () => {
26
+ live.stop();
337
27
  exit();
338
28
  };
339
- process.on('SIGINT', handleExit);
340
- process.on('SIGTERM', handleExit);
29
+ process.on('SIGINT', stop);
30
+ process.on('SIGTERM', stop);
341
31
  return () => {
342
- process.off('SIGINT', handleExit);
343
- process.off('SIGTERM', handleExit);
32
+ process.off('SIGINT', stop);
33
+ process.off('SIGTERM', stop);
344
34
  };
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
- }
35
+ }, [live, exit]);
36
+ const status = live.status === 'running'
37
+ ? live.phase === 'error'
38
+ ? 'error'
39
+ : 'running'
40
+ : live.status === 'success'
41
+ ? 'success'
42
+ : 'error';
43
+ 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" })] }));
44
+ }
45
+ /** Resolve signing credentials for signed dev/watch, prompting if needed. */
46
+ export async function resolveSigningForCli(project) {
47
+ if (!project.hasSigningProperties ||
48
+ !project.devProperties?.signingUsername) {
49
+ console.log('\n\x1b[33mSigning credentials not configured.\x1b[0m');
50
+ console.log('Run \x1b[36msetup-signing\x1b[0m to configure credentials.\n');
51
+ return undefined;
52
+ }
53
+ const signingUsername = project.devProperties.signingUsername;
54
+ const password = await resolveSigningPassword(signingUsername);
55
+ if (!password) {
56
+ console.log('\x1b[31mError: Password is required for signed mode\x1b[0m');
57
+ return undefined;
58
+ }
59
+ return {
60
+ username: signingUsername,
61
+ password,
62
+ certificateName: project.devProperties.certificateName,
373
63
  };
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" }))] })] }));
377
64
  }
378
65
  export const devCommand = {
379
66
  name: 'dev',
@@ -388,21 +75,22 @@ export const devCommand = {
388
75
  },
389
76
  },
390
77
  async execute({ project, flags }) {
391
- // Check if dev properties are configured
392
- if (!project.hasDevProperties || !project.devProperties) {
78
+ const dev = project.devProperties;
79
+ if (!project.hasDevProperties || !dev) {
393
80
  console.log('\n\x1b[33mDeployment credentials not configured.\x1b[0m');
394
81
  console.log('Create a .dev_properties.json file with domain, siteName, addonName, and username, then run setup.\n');
395
82
  return;
396
83
  }
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');
84
+ // The standalone command only prompts for a basic password; OAuth2/cookie
85
+ // logins are interactive and live in the shell (`svc`) or `svc deploy`.
86
+ if ((dev.authMethod ?? 'basic') !== 'basic' &&
87
+ !dev.accessToken &&
88
+ !dev.sessionCookie) {
89
+ console.log('\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');
401
90
  return;
402
91
  }
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;
92
+ if ((dev.authMethod ?? 'basic') === 'basic' && !dev.password) {
93
+ const { domain, username } = dev;
406
94
  console.log('');
407
95
  const pw = await promptPassword(`Deploy password for ${username}@${domain}: `);
408
96
  if (!pw) {
@@ -413,31 +101,28 @@ export const devCommand = {
413
101
  if (remember && domain && username) {
414
102
  setDeployPassword(domain, username, pw);
415
103
  }
416
- project.devProperties.password = pw;
104
+ dev.password = pw;
417
105
  }
418
- const signed = Boolean(flags['signed']);
419
106
  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');
107
+ if (flags['signed']) {
108
+ signingCredentials = await resolveSigningForCli(project);
109
+ if (!signingCredentials)
426
110
  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');
432
- return;
433
- }
434
- signingCredentials = {
435
- username: signingUsername,
436
- password,
437
- certificateName: project.devProperties.certificateName,
438
- };
439
111
  }
440
- const { waitUntilExit } = render(_jsx(DevScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: project.devProperties, signed: signed, signingCredentials: signingCredentials }));
112
+ const { waitUntilExit } = render(_jsx(DevScreen, { project: project, options: {
113
+ deploy: true,
114
+ signingCredentials,
115
+ deployConfig: {
116
+ domain: dev.domain,
117
+ siteName: dev.siteName,
118
+ addonName: dev.addonName,
119
+ username: dev.username,
120
+ password: dev.password,
121
+ accessToken: dev.accessToken,
122
+ sessionCookie: dev.sessionCookie,
123
+ useHTTP: dev.useHTTPForDevDeploy,
124
+ },
125
+ } }));
441
126
  await waitUntilExit();
442
127
  },
443
128
  };
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { render } from 'ink';
3
- import { DevScreen } from './dev.js';
4
- import { resolveSigningPassword } from '../utils/signing-password.js';
3
+ import { DevScreen, resolveSigningForCli } from './dev.js';
5
4
  export const watchCommand = {
6
5
  name: 'watch',
7
6
  description: 'Watch and rebuild (optionally signing) without deploying',
@@ -15,30 +14,13 @@ export const watchCommand = {
15
14
  },
16
15
  },
17
16
  async execute({ project, flags }) {
18
- const signed = Boolean(flags['signed']);
19
17
  let signingCredentials;
20
- // Signed mode: resolve signing credentials (keychain → env → prompt).
21
- // No deploy credentials are needed — watch never deploys.
22
- if (signed) {
23
- if (!project.hasSigningProperties ||
24
- !project.devProperties?.signingUsername) {
25
- console.log('\n\x1b[33mSigning credentials not configured.\x1b[0m');
26
- console.log('Run \x1b[36msetup-signing\x1b[0m to configure credentials.\n');
18
+ if (flags['signed']) {
19
+ signingCredentials = await resolveSigningForCli(project);
20
+ if (!signingCredentials)
27
21
  return;
28
- }
29
- const signingUsername = project.devProperties.signingUsername;
30
- const password = await resolveSigningPassword(signingUsername);
31
- if (!password) {
32
- console.log('\x1b[31mError: Password is required for signed mode\x1b[0m');
33
- return;
34
- }
35
- signingCredentials = {
36
- username: signingUsername,
37
- password,
38
- certificateName: project.devProperties.certificateName,
39
- };
40
22
  }
41
- const { waitUntilExit } = render(_jsx(DevScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: project.devProperties, signed: signed, deploy: false, signingCredentials: signingCredentials }));
23
+ const { waitUntilExit } = render(_jsx(DevScreen, { project: project, options: { deploy: false, signingCredentials } }));
42
24
  await waitUntilExit();
43
25
  },
44
26
  };
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
4
  import Spinner from 'ink-spinner';
5
+ import { t } from '../utils/i18n.js';
5
6
  import { beginOAuth2Login, openBrowser } from '../utils/oauth2-auth.js';
6
7
  import { beginCookieLogin, } from '../utils/session-cookie-auth.js';
7
8
  /**
@@ -83,5 +84,5 @@ export function AuthLoginScreen({ method, devProperties, onComplete, onError, on
83
84
  })();
84
85
  }
85
86
  });
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
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: method === 'oauth2' ? t('OAuth2 login') : t('Session cookie login') }) }), method === 'oauth2' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", t('Waiting for you to finish login in the browser…')] })] }), authUrl && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: t("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" }) }), _jsxs(Text, { children: [" ", t('Opening browser…')] })] })), phase === 'awaiting' && (_jsx(Text, { children: t('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" }) }), _jsxs(Text, { children: [" ", t('Capturing session…')] })] })), note && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: note }) }))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('Press Esc to cancel.') }) })] }));
87
88
  }
@@ -66,11 +66,14 @@ export function DevPropertiesForm({ projectRoot, initialProperties, packageJson,
66
66
  if (cancelled)
67
67
  return;
68
68
  if (discovered) {
69
+ // Endpoints only — do NOT pre-fill scopes. `scopes_supported` is the
70
+ // provider's list, not what this client is granted (and casing may
71
+ // differ, e.g. advertised `all` vs client `ALL`), so requesting them
72
+ // causes `invalid_scope`. Empty scopes → the client's default scopes.
69
73
  setOauth(previous => ({
70
74
  ...previous,
71
75
  authorizationEndpoint: previous.authorizationEndpoint || discovered.authorizationEndpoint,
72
76
  tokenEndpoint: previous.tokenEndpoint || discovered.tokenEndpoint,
73
- scopes: previous.scopes || (discovered.scopesSupported?.join(' ') ?? ''),
74
77
  }));
75
78
  setDiscoveryNote('Endpoints auto-filled from the site OpenID config.');
76
79
  }
@@ -196,9 +199,9 @@ export function DevPropertiesForm({ projectRoot, initialProperties, packageJson,
196
199
  case 'oauthTokenEndpoint':
197
200
  return (_jsx(TextInput, { label: "Token Endpoint URL", defaultValue: oauth.tokenEndpoint, onSubmit: value => submitOAuth('tokenEndpoint', value), onCancel: onCancel }, "oauthTokenEndpoint"));
198
201
  case 'oauthScopes':
199
- return (_jsx(TextInput, { label: "Scopes (space-separated, optional)", defaultValue: oauth.scopes, onSubmit: value => submitOAuth('scopes', value), onCancel: onCancel }, "oauthScopes"));
202
+ return (_jsx(TextInput, { label: "Scopes (space-separated \u2014 leave empty to use the client's default scopes; add offline_access, matching your client's casing, for a refresh token)", defaultValue: oauth.scopes, onSubmit: value => submitOAuth('scopes', value), onCancel: onCancel }, "oauthScopes"));
200
203
  case 'oauthClientSecret':
201
- return (_jsx(TextInput, { label: "Client Secret (OS keychain \u2014 leave empty for a public/PKCE client)", type: "password", defaultValue: oauth.clientSecret, onSubmit: value => submitOAuth('clientSecret', value), onCancel: onCancel }, "oauthClientSecret"));
204
+ return (_jsx(TextInput, { label: "Client Secret (OS keychain \u2014 required if your Sitevision client has a secret; leave empty only for a public client)", type: "password", defaultValue: oauth.clientSecret, onSubmit: value => submitOAuth('clientSecret', value), onCancel: onCancel }, "oauthClientSecret"));
202
205
  case 'sessionLoginUrl':
203
206
  return (_jsx(TextInput, { label: "Login URL (opened in a browser; blank = site root)", defaultValue: properties.sessionLoginUrl ??
204
207
  (properties.domain ? `https://${properties.domain}/` : ''), onSubmit: value => submitProperty('sessionLoginUrl', value), onCancel: onCancel }, "sessionLoginUrl"));
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
+ import { t } from '../utils/i18n.js';
4
5
  export function PasswordInput({ label = 'Enter Signing Password', showRememberOption = false, defaultRemember = false, rememberLabel = 'Save to OS keychain: ', onSubmit, onCancel, }) {
5
6
  const [password, setPassword] = useState('');
6
7
  const [remember, setRemember] = useState(defaultRemember);
@@ -26,5 +27,5 @@ export function PasswordInput({ label = 'Enter Signing Password', showRememberOp
26
27
  setPassword(prev => prev + input);
27
28
  }
28
29
  });
29
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: label }) }), _jsx(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: '*'.repeat(password.length) }) }), showRememberOption && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: rememberLabel }), _jsxs(Text, { color: remember ? 'green' : 'gray', children: ["[", remember ? 'x' : ' ', "]"] }), _jsx(Text, { dimColor: true, children: " (Tab to toggle)" })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to submit, Esc to cancel" }) })] }));
30
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: label }) }), _jsx(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: '*'.repeat(password.length) }) }), showRememberOption && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: rememberLabel }), _jsxs(Text, { color: remember ? 'green' : 'gray', children: ["[", remember ? 'x' : ' ', "]"] }), _jsxs(Text, { dimColor: true, children: [" ", t('(Tab to toggle)')] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('Press Enter to submit, Esc to cancel') }) })] }));
30
31
  }
@@ -0,0 +1,14 @@
1
+ import type { SimpleAppType } from '../types/index.js';
2
+ import { type AddonNode } from '../utils/sitevision-api.js';
3
+ export declare function AddonPicker({ domain, appType, initialQuery, load, onSelect, onClose, height, }: {
4
+ domain: string;
5
+ appType?: SimpleAppType;
6
+ initialQuery: string;
7
+ load: () => Promise<{
8
+ addons?: AddonNode[];
9
+ error?: string;
10
+ }>;
11
+ onSelect: (name: string) => void;
12
+ onClose: () => void;
13
+ height: number;
14
+ }): import("react").JSX.Element;