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.
- package/dist/app.d.ts +1 -1
- package/dist/app.js +59 -8
- package/dist/cli.js +96 -39
- package/dist/commands/build.js +1 -1
- package/dist/commands/deploy.d.ts +2 -2
- package/dist/commands/deploy.js +135 -25
- package/dist/commands/dev.d.ts +8 -10
- package/dist/commands/dev.js +77 -366
- package/dist/commands/info.js +2 -2
- package/dist/commands/watch.js +5 -23
- package/dist/components/AnimatedLogo.js +8 -2
- package/dist/components/AuthLoginScreen.d.ts +21 -0
- package/dist/components/AuthLoginScreen.js +90 -0
- package/dist/components/DevPropertiesForm.d.ts +2 -1
- package/dist/components/DevPropertiesForm.js +198 -33
- package/dist/components/InfoScreen.js +2 -2
- package/dist/components/MainMenu.js +7 -2
- package/dist/components/PasswordInput.js +2 -1
- package/dist/components/SetupFlow.d.ts +2 -1
- package/dist/components/SetupFlow.js +100 -11
- package/dist/shell/AddonPicker.d.ts +14 -0
- package/dist/shell/AddonPicker.js +54 -0
- package/dist/shell/CommandPalette.d.ts +8 -0
- package/dist/shell/CommandPalette.js +63 -0
- package/dist/shell/ConfigForm.d.ts +36 -0
- package/dist/shell/ConfigForm.js +558 -0
- package/dist/shell/Frame.d.ts +59 -0
- package/dist/shell/Frame.js +134 -0
- package/dist/shell/Settings.d.ts +6 -0
- package/dist/shell/Settings.js +96 -0
- package/dist/shell/Shell.d.ts +9 -0
- package/dist/shell/Shell.js +586 -0
- package/dist/shell/Tabs.d.ts +36 -0
- package/dist/shell/Tabs.js +90 -0
- package/dist/shell/actions.d.ts +45 -0
- package/dist/shell/actions.js +0 -0
- package/dist/types/index.d.ts +44 -5
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.js +14 -0
- package/dist/utils/environments.d.ts +20 -0
- package/dist/utils/environments.js +74 -0
- package/dist/utils/i18n.d.ts +12 -0
- package/dist/utils/i18n.js +279 -0
- package/dist/utils/jsonc.d.ts +19 -0
- package/dist/utils/jsonc.js +74 -0
- package/dist/utils/keychain.d.ts +9 -0
- package/dist/utils/keychain.js +54 -0
- package/dist/utils/oauth2-auth.d.ts +64 -0
- package/dist/utils/oauth2-auth.js +242 -0
- package/dist/utils/password-prompt.d.ts +5 -0
- package/dist/utils/password-prompt.js +28 -0
- package/dist/utils/project-detection.d.ts +105 -6
- package/dist/utils/project-detection.js +411 -54
- package/dist/utils/session-cookie-auth.d.ts +35 -0
- package/dist/utils/session-cookie-auth.js +99 -0
- package/dist/utils/sitevision-api.d.ts +64 -5
- package/dist/utils/sitevision-api.js +195 -33
- package/dist/utils/tasks.d.ts +48 -0
- package/dist/utils/tasks.js +371 -0
- package/dist/utils/workspace.d.ts +17 -0
- package/dist/utils/workspace.js +67 -0
- package/package.json +3 -1
- 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-
|
|
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:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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 {};
|