sitevision-cli 1.0.0-beta.2 → 1.0.0-beta.21

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 (63) hide show
  1. package/dist/app.d.ts +1 -1
  2. package/dist/app.js +59 -8
  3. package/dist/cli.js +96 -39
  4. package/dist/commands/build.js +1 -1
  5. package/dist/commands/deploy.d.ts +2 -2
  6. package/dist/commands/deploy.js +135 -25
  7. package/dist/commands/dev.d.ts +8 -10
  8. package/dist/commands/dev.js +77 -366
  9. package/dist/commands/info.js +2 -2
  10. package/dist/commands/watch.js +5 -23
  11. package/dist/components/AnimatedLogo.js +8 -2
  12. package/dist/components/AuthLoginScreen.d.ts +21 -0
  13. package/dist/components/AuthLoginScreen.js +90 -0
  14. package/dist/components/DevPropertiesForm.d.ts +2 -1
  15. package/dist/components/DevPropertiesForm.js +198 -33
  16. package/dist/components/InfoScreen.js +2 -2
  17. package/dist/components/MainMenu.js +7 -2
  18. package/dist/components/PasswordInput.js +2 -1
  19. package/dist/components/SetupFlow.d.ts +2 -1
  20. package/dist/components/SetupFlow.js +100 -11
  21. package/dist/shell/AddonPicker.d.ts +14 -0
  22. package/dist/shell/AddonPicker.js +54 -0
  23. package/dist/shell/CommandPalette.d.ts +8 -0
  24. package/dist/shell/CommandPalette.js +63 -0
  25. package/dist/shell/ConfigForm.d.ts +36 -0
  26. package/dist/shell/ConfigForm.js +558 -0
  27. package/dist/shell/Frame.d.ts +59 -0
  28. package/dist/shell/Frame.js +134 -0
  29. package/dist/shell/Settings.d.ts +6 -0
  30. package/dist/shell/Settings.js +96 -0
  31. package/dist/shell/Shell.d.ts +9 -0
  32. package/dist/shell/Shell.js +586 -0
  33. package/dist/shell/Tabs.d.ts +36 -0
  34. package/dist/shell/Tabs.js +90 -0
  35. package/dist/shell/actions.d.ts +45 -0
  36. package/dist/shell/actions.js +0 -0
  37. package/dist/types/index.d.ts +44 -5
  38. package/dist/utils/config.d.ts +10 -0
  39. package/dist/utils/config.js +14 -0
  40. package/dist/utils/environments.d.ts +20 -0
  41. package/dist/utils/environments.js +74 -0
  42. package/dist/utils/i18n.d.ts +12 -0
  43. package/dist/utils/i18n.js +279 -0
  44. package/dist/utils/jsonc.d.ts +19 -0
  45. package/dist/utils/jsonc.js +74 -0
  46. package/dist/utils/keychain.d.ts +9 -0
  47. package/dist/utils/keychain.js +54 -0
  48. package/dist/utils/oauth2-auth.d.ts +64 -0
  49. package/dist/utils/oauth2-auth.js +242 -0
  50. package/dist/utils/password-prompt.d.ts +5 -0
  51. package/dist/utils/password-prompt.js +28 -0
  52. package/dist/utils/project-detection.d.ts +105 -6
  53. package/dist/utils/project-detection.js +411 -54
  54. package/dist/utils/session-cookie-auth.d.ts +35 -0
  55. package/dist/utils/session-cookie-auth.js +99 -0
  56. package/dist/utils/sitevision-api.d.ts +64 -5
  57. package/dist/utils/sitevision-api.js +195 -33
  58. package/dist/utils/tasks.d.ts +48 -0
  59. package/dist/utils/tasks.js +371 -0
  60. package/dist/utils/workspace.d.ts +17 -0
  61. package/dist/utils/workspace.js +67 -0
  62. package/package.json +3 -1
  63. package/readme.md +102 -121
@@ -1,16 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
- import { getAppType, migrateLegacyPassword, } from '../utils/project-detection.js';
4
+ import { getAppType, localizedText, migrateLegacyPassword, getPackageJsonSyncChanges, syncDevPropertiesToPackageJson, readSvcConfig, writeSvcConfig, writeDevProperties, } from '../utils/project-detection.js';
5
5
  import { ProcessRunner } from '../utils/process-runner.js';
6
6
  import { ProcessOutputComponent } from './ProcessOutput.js';
7
7
  import { StatusIndicator } from './StatusIndicator.js';
8
8
  import { DevPropertiesForm } from './DevPropertiesForm.js';
9
9
  import { SigningPropertiesForm } from './SigningPropertiesForm.js';
10
- export function SetupFlow({ project, onComplete }) {
10
+ export function SetupFlow({ project, onReload, onComplete }) {
11
11
  const [step, setStep] = useState('check-node-modules');
12
12
  const [runner, setRunner] = useState(null);
13
13
  const [commandStatus, setCommandStatus] = useState('running');
14
+ const [syncChanges, setSyncChanges] = useState([]);
15
+ const [syncDecision, setSyncDecision] = useState(false);
16
+ // Set when the user picks OAuth2 for an existing config, so the form opens in
17
+ // the OAuth2 branch without mutating the shared project object.
18
+ const [pendingAuthMethod, setPendingAuthMethod] = useState(undefined);
14
19
  const appType = getAppType(project.manifest);
15
20
  // Auto-advance through checks
16
21
  useEffect(() => {
@@ -27,14 +32,36 @@ export function SetupFlow({ project, onComplete }) {
27
32
  if (project.hasLegacyPassword) {
28
33
  setStep('confirm-password-migration');
29
34
  }
35
+ else if (project.devProperties?.authMethod === undefined) {
36
+ setStep('confirm-auth-method');
37
+ }
30
38
  else {
31
- setStep('check-signing-properties');
39
+ setStep('check-package-sync');
32
40
  }
33
41
  }
34
42
  else {
35
43
  setStep('confirm-dev-setup');
36
44
  }
37
45
  }
46
+ else if (step === 'check-package-sync') {
47
+ const preference = readSvcConfig(project.root).syncPackageJson;
48
+ const properties = project.devProperties;
49
+ const changes = preference !== false && properties
50
+ ? getPackageJsonSyncChanges(project.root, properties)
51
+ : [];
52
+ if (changes.length === 0 || !properties) {
53
+ setStep('check-signing-properties');
54
+ }
55
+ else if (preference === true) {
56
+ syncDevPropertiesToPackageJson(project.root, properties);
57
+ onReload();
58
+ setStep('check-signing-properties');
59
+ }
60
+ else {
61
+ setSyncChanges(changes);
62
+ setStep('confirm-package-sync');
63
+ }
64
+ }
38
65
  else if (step === 'check-signing-properties') {
39
66
  if (project.hasSigningProperties) {
40
67
  setStep('show-info');
@@ -84,6 +111,29 @@ export function SetupFlow({ project, onComplete }) {
84
111
  else if (step === 'confirm-password-migration') {
85
112
  if (input === 'y' || input === 'Y') {
86
113
  migrateLegacyPassword(project);
114
+ // The file was rewritten (plaintext stripped, password moved to
115
+ // keychain) — re-detect so hasLegacyPassword/password reflect that.
116
+ onReload();
117
+ setStep('check-package-sync');
118
+ }
119
+ else if (input === 'n' || input === 'N') {
120
+ setStep('check-package-sync');
121
+ }
122
+ }
123
+ else if (step === 'confirm-package-sync') {
124
+ if (['y', 'Y', 'n', 'N'].includes(input)) {
125
+ const accepted = input.toLowerCase() === 'y';
126
+ if (accepted && project.devProperties) {
127
+ syncDevPropertiesToPackageJson(project.root, project.devProperties);
128
+ onReload();
129
+ }
130
+ setSyncDecision(accepted);
131
+ setStep('confirm-save-sync-choice');
132
+ }
133
+ }
134
+ else if (step === 'confirm-save-sync-choice') {
135
+ if (input === 'y' || input === 'Y') {
136
+ writeSvcConfig(project.root, { syncPackageJson: syncDecision });
87
137
  setStep('check-signing-properties');
88
138
  }
89
139
  else if (input === 'n' || input === 'N') {
@@ -98,21 +148,46 @@ export function SetupFlow({ project, onComplete }) {
98
148
  setStep('show-info');
99
149
  }
100
150
  }
151
+ else if (step === 'confirm-auth-method') {
152
+ if (input === 'o' || input === 'O') {
153
+ // Choosing OAuth2 needs endpoints — collect them in the full form.
154
+ setPendingAuthMethod('oauth2');
155
+ setStep('setup-dev-properties');
156
+ }
157
+ else if (input === 'c' || input === 'C') {
158
+ setPendingAuthMethod('cookie');
159
+ setStep('setup-dev-properties');
160
+ }
161
+ else if (['b', 'B', '\r'].includes(input)) {
162
+ if (project.devProperties) {
163
+ writeDevProperties(project.root, {
164
+ ...project.devProperties,
165
+ authMethod: 'basic',
166
+ });
167
+ onReload();
168
+ }
169
+ setStep('check-package-sync');
170
+ }
171
+ }
101
172
  });
102
173
  // Setup Dev Properties Form
103
174
  if (step === 'setup-dev-properties') {
104
- return (_jsx(DevPropertiesForm, { projectRoot: project.root, initialProperties: project.devProperties, packageJson: project.packageJson, onComplete: () => {
105
- // Manually update project state locally if possible, or just proceed
106
- // Since we can't easily update 'project' prop from here without reloading,
107
- // we just move to next step. The file is written.
108
- project.hasDevProperties = true; // Optimization/Hack to pass check
109
- setStep('check-signing-properties');
175
+ return (_jsx(DevPropertiesForm, { projectRoot: project.root, initialProperties: pendingAuthMethod && project.devProperties
176
+ ? { ...project.devProperties, authMethod: pendingAuthMethod }
177
+ : project.devProperties, packageJson: project.packageJson, onComplete: () => {
178
+ // Re-detect from disk/keychain so devProperties (incl. the keychain
179
+ // password) populate in memory otherwise the rest of this flow and
180
+ // the menu would see stale state until the CLI is restarted.
181
+ onReload();
182
+ setStep('check-package-sync');
110
183
  }, onCancel: () => setStep('check-signing-properties') }));
111
184
  }
112
185
  // Setup Signing Properties Form
113
186
  if (step === 'setup-signing-properties') {
114
187
  return (_jsx(SigningPropertiesForm, { projectRoot: project.root, onComplete: () => {
115
- project.hasSigningProperties = true; // Optimization/Hack
188
+ // Re-detect so signing credentials are reflected in memory before
189
+ // the info screen / menu render.
190
+ onReload();
116
191
  setStep('show-info');
117
192
  }, onCancel: () => setStep('show-info') }));
118
193
  }
@@ -132,13 +207,27 @@ export function SetupFlow({ project, onComplete }) {
132
207
  if (step === 'confirm-password-migration') {
133
208
  return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 Plaintext password found in .dev_properties.json" }) }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { children: "Move it to the OS keychain and remove it from the file? (y/n)" }), _jsx(Text, { dimColor: true, children: "Recommended \u2014 storing passwords in project files is insecure." })] })] }));
134
209
  }
210
+ // Ask which auth method an existing config should use (setting is missing)
211
+ if (step === 'confirm-auth-method') {
212
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 Deploy authentication method not set" }) }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { children: "Which method should deploys use? [B]asic / [O]Auth2 / [C]ookie" }), _jsx(Text, { dimColor: true, children: "B = Basic (default), O = OAuth2 bearer, C = session cookie (SSO)." })] })] }));
213
+ }
214
+ // Confirm package.json sync
215
+ if (step === 'confirm-package-sync') {
216
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 package.json is out of sync with .dev_properties.json" }) }), _jsx(Box, { marginBottom: 1, flexDirection: "column", marginLeft: 2, children: syncChanges.map(change => (_jsxs(Box, { children: [_jsx(Text, { color: change.from === undefined ? 'green' : 'yellow', children: change.from === undefined ? '+ ' : '~ ' }), _jsxs(Text, { bold: true, children: [change.key, ": "] }), change.from !== undefined && (_jsxs(Text, { dimColor: true, children: [change.from, " \u2192 "] })), _jsx(Text, { children: change.to })] }, change.key))) }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { children: "Update package.json from .dev_properties.json? (y/n)" }), _jsx(Text, { dimColor: true, children: "sitevision-scripts reads these fields from package.json." })] })] }));
217
+ }
218
+ // Offer to persist the sync decision in .svcconfig
219
+ if (step === 'confirm-save-sync-choice') {
220
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { children: "Remember this choice in .svcconfig? (y/n)" }), _jsx(Text, { dimColor: true, children: syncDecision
221
+ ? 'svc will update package.json automatically from now on.'
222
+ : 'svc will stop asking about package.json sync.' })] })] }));
223
+ }
135
224
  // Confirm signing setup
136
225
  if (step === 'confirm-signing-setup') {
137
226
  return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision CLI" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 signing credentials not configured" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: "Signing credentials are required for signing apps on developer.sitevision.se" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: "Would you like to set up signing credentials? (y/n)" }) })] }));
138
227
  }
139
228
  // Show info
140
229
  if (step === 'show-info') {
141
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: project.manifest.name })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties configured" }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
230
+ 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: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties configured" }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
142
231
  }
143
232
  return null;
144
233
  }
@@ -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;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import Spinner from 'ink-spinner';
5
+ import { ACCENT } from './Frame.js';
6
+ import { fuzzyMatch } from './actions.js';
7
+ import { t } from '../utils/i18n.js';
8
+ export function AddonPicker({ domain, appType, initialQuery, load, onSelect, onClose, height, }) {
9
+ const [query, setQuery] = useState(initialQuery);
10
+ const [index, setIndex] = useState(0);
11
+ const [addons, setAddons] = useState(null);
12
+ const [error, setError] = useState('');
13
+ useEffect(() => {
14
+ void load().then(result => {
15
+ setAddons(result.addons ?? []);
16
+ setError(result.error ?? '');
17
+ });
18
+ // eslint-disable-next-line react-hooks/exhaustive-deps
19
+ }, []);
20
+ const matches = (addons ?? [])
21
+ .filter(a => fuzzyMatch(query, a.name))
22
+ .toSorted((a, b) => Number(b.appType === appType) - Number(a.appType === appType) ||
23
+ a.name.localeCompare(b.name));
24
+ useInput((input, key) => {
25
+ if (key.escape) {
26
+ onClose();
27
+ }
28
+ else if (key.return) {
29
+ const pick = matches[index];
30
+ if (pick)
31
+ onSelect(pick.name);
32
+ }
33
+ else if (key.upArrow) {
34
+ setIndex(i => (i > 0 ? i - 1 : Math.max(0, matches.length - 1)));
35
+ }
36
+ else if (key.downArrow) {
37
+ setIndex(i => (i < matches.length - 1 ? i + 1 : 0));
38
+ }
39
+ else if (key.backspace || key.delete) {
40
+ setQuery(q => q.slice(0, -1));
41
+ setIndex(0);
42
+ }
43
+ else if (input && !key.ctrl && !key.meta) {
44
+ setQuery(q => q + input);
45
+ setIndex(0);
46
+ }
47
+ });
48
+ const visible = Math.max(3, height - 6);
49
+ const start = Math.max(0, Math.min(index - visible + 1, matches.length - visible));
50
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, width: 64, height: Math.min(height, matches.length + 6), children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Addon Repository') }), _jsxs(Text, { dimColor: true, children: [" ", domain] })] }), _jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), query, _jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { dimColor: true, children: [' ', addons ? t('{n} of {m}', { n: matches.length, m: addons.length }) : ''] })] }), !addons && !error && (_jsxs(Text, { color: ACCENT, children: [_jsx(Spinner, { type: "dots" }), " ", _jsx(Text, { dimColor: true, children: t('fetching addons') })] })), error && _jsx(Text, { color: "red", children: error }), matches.slice(start, start + visible).map((a, i) => {
51
+ const selected = start + i === index;
52
+ return (_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Box, { flexShrink: 1, marginRight: 1, children: _jsxs(Text, { backgroundColor: selected ? ACCENT : undefined, color: selected ? 'black' : undefined, wrap: "truncate", children: [' ', a.name] }) }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, children: a.appType ?? a.type }) })] }, a.id));
53
+ }), _jsx(Text, { dimColor: true, children: t('↑↓ move · Enter select · Esc cancel') })] }));
54
+ }
@@ -0,0 +1,8 @@
1
+ import type { ProjectInfo } from '../types/index.js';
2
+ import { type Action } from './actions.js';
3
+ export declare function CommandPalette({ project, onRun, onClose, height, }: {
4
+ project: ProjectInfo;
5
+ onRun: (action: Action) => void;
6
+ onClose: () => void;
7
+ height: number;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { ACCENT } from './Frame.js';
5
+ import { actions, fuzzyMatch } from './actions.js';
6
+ import { t } from '../utils/i18n.js';
7
+ const GROUPS = [
8
+ { id: 'app', label: 'APP' },
9
+ { id: 'setup', label: 'SETUP' },
10
+ { id: 'auth', label: 'AUTH' },
11
+ ];
12
+ export function CommandPalette({ project, onRun, onClose, height, }) {
13
+ const [query, setQuery] = useState('');
14
+ const [index, setIndex] = useState(0);
15
+ const matches = actions.filter(a => fuzzyMatch(query, t(a.label)));
16
+ const ordered = GROUPS.flatMap(g => matches.filter(a => a.group === g.id));
17
+ useInput((input, key) => {
18
+ if (key.escape) {
19
+ onClose();
20
+ }
21
+ else if (key.return) {
22
+ const action = ordered[index];
23
+ if (action)
24
+ onRun(action);
25
+ }
26
+ else if (key.upArrow) {
27
+ setIndex(i => (i > 0 ? i - 1 : Math.max(0, ordered.length - 1)));
28
+ }
29
+ else if (key.downArrow) {
30
+ setIndex(i => (i < ordered.length - 1 ? i + 1 : 0));
31
+ }
32
+ else if (key.backspace || key.delete) {
33
+ setQuery(q => q.slice(0, -1));
34
+ setIndex(0);
35
+ }
36
+ else if (!key.ctrl && !key.meta && input) {
37
+ setQuery(q => q + input);
38
+ setIndex(0);
39
+ }
40
+ });
41
+ // Rows available inside the box: borders (2), title, query, footer.
42
+ const maxRows = Math.max(3, height - 5);
43
+ const rows = [];
44
+ let lastGroup;
45
+ // Keep the selection visible: skip leading rows when it is far down.
46
+ let skip = 0;
47
+ while (index - skip + 1 + 3 > maxRows)
48
+ skip += 1;
49
+ for (const [i, action] of ordered.entries()) {
50
+ if (i < skip)
51
+ continue;
52
+ if (rows.length >= maxRows)
53
+ break;
54
+ if (action.group !== lastGroup) {
55
+ rows.push(_jsx(Text, { bold: true, dimColor: true, children: t(GROUPS.find(g => g.id === action.group).label) }, `g-${action.group}`));
56
+ lastGroup = action.group;
57
+ }
58
+ const enabled = action.enabled?.(project) ?? true;
59
+ const detail = action.detail?.(project);
60
+ rows.push(_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Box, { flexShrink: 1, marginRight: 1, children: _jsxs(Text, { backgroundColor: i === index ? ACCENT : undefined, color: i === index ? 'black' : undefined, dimColor: !enabled && i !== index, wrap: "truncate", children: [' ', t(action.label), detail && _jsxs(Text, { dimColor: i !== index, children: [" \u00B7 ", detail] })] }) }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: ACCENT, children: action.key ?? '—' }) })] }, action.id));
61
+ }
62
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, width: 64, height: rows.length + 5, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Commands') }), _jsxs(Text, { dimColor: true, children: [" ", project.manifest.id] })] }), _jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), query, _jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { dimColor: true, children: [" ", t('{n} actions', { n: ordered.length })] })] }), rows, _jsx(Text, { dimColor: true, children: t('type to filter · ↑↓ move · Enter run · Esc close') })] }));
63
+ }
@@ -0,0 +1,36 @@
1
+ import type { DevProperties } from '../types/index.js';
2
+ type Method = 'basic' | 'oauth2' | 'cookie';
3
+ interface Field {
4
+ key: string;
5
+ label: string;
6
+ kind?: 'text' | 'method' | 'bool' | 'secret';
7
+ required?: boolean | Method;
8
+ when?: Method;
9
+ section?: string;
10
+ hint?: string;
11
+ perApp?: boolean;
12
+ help: string;
13
+ }
14
+ type Values = Record<string, string>;
15
+ /** What the form edits: an app, or the workspace root (no addon, no package.json). */
16
+ export interface ConfigTarget {
17
+ root: string;
18
+ devProperties?: Partial<DevProperties>;
19
+ base?: Partial<DevProperties>;
20
+ environment?: string;
21
+ workspace?: boolean;
22
+ workspaceRoot?: string;
23
+ }
24
+ /** Apply the form to disk and the keychain. Exported for the test. */
25
+ export declare function saveConfig(project: ConfigTarget, values: Values, edited: Set<string>): void;
26
+ export declare function visibleFields(method: Method, workspace?: boolean, envMode?: boolean): Field[];
27
+ export declare function ConfigForm({ project, active, width, height, pickAddon, onSaved, onEditingChange, }: {
28
+ project: ConfigTarget;
29
+ active: boolean;
30
+ width: number;
31
+ height: number;
32
+ pickAddon: () => Promise<string | null>;
33
+ onSaved: () => void;
34
+ onEditingChange: (editing: boolean) => void;
35
+ }): import("react").JSX.Element;
36
+ export {};