@syncular/cli 0.0.0-108 → 0.0.0-113
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/package.json +12 -15
- package/src/args.ts +0 -113
- package/src/auth-storage.ts +0 -57
- package/src/buildpacks/index.ts +0 -2
- package/src/buildpacks/registry.ts +0 -47
- package/src/buildpacks/types.ts +0 -1
- package/src/command-registry.ts +0 -685
- package/src/commands/auth.ts +0 -426
- package/src/commands/build.ts +0 -210
- package/src/commands/console.ts +0 -252
- package/src/commands/demo.ts +0 -1151
- package/src/commands/doctor.tsx +0 -133
- package/src/commands/migrate.ts +0 -271
- package/src/commands/project.ts +0 -381
- package/src/commands/target.ts +0 -62
- package/src/commands/typegen.ts +0 -404
- package/src/constants.ts +0 -7
- package/src/control-plane-token.ts +0 -46
- package/src/control-plane.ts +0 -64
- package/src/dev-logging.tsx +0 -415
- package/src/extensions/index.ts +0 -1
- package/src/extensions/manifest.ts +0 -37
- package/src/flags.ts +0 -92
- package/src/help.tsx +0 -239
- package/src/index.ts +0 -4
- package/src/interactive.tsx +0 -306
- package/src/main.tsx +0 -268
- package/src/output.tsx +0 -47
- package/src/paths.ts +0 -11
- package/src/spaces-config.ts +0 -2
- package/src/targets/index.ts +0 -13
- package/src/targets/state.ts +0 -99
- package/src/targets/types.ts +0 -8
- package/src/templates/index.ts +0 -2
- package/src/templates/registry.ts +0 -42
- package/src/templates/syncular-types.ts +0 -10
- package/src/types.ts +0 -67
- package/src/update-check.ts +0 -296
package/src/help.tsx
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import process from 'node:process';
|
|
2
|
-
import { Box, renderToString, Text } from 'ink';
|
|
3
|
-
import type { ReactElement } from 'react';
|
|
4
|
-
import { listBuildpackIds } from './buildpacks';
|
|
5
|
-
import { USAGE_LINES } from './command-registry';
|
|
6
|
-
import { CLI_NAME, CLI_VERSION } from './constants';
|
|
7
|
-
import { listTemplateIds } from './templates';
|
|
8
|
-
|
|
9
|
-
interface HelpSection {
|
|
10
|
-
title: string;
|
|
11
|
-
lines: string[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const TEMPLATE_ID_LIST = listTemplateIds().join('|');
|
|
15
|
-
const DEFAULT_BUILDPACK_ID = listBuildpackIds()[0] ?? 'contract-worker';
|
|
16
|
-
|
|
17
|
-
const HELP_SECTIONS: HelpSection[] = [
|
|
18
|
-
{
|
|
19
|
-
title: 'Auth Flags',
|
|
20
|
-
lines: [
|
|
21
|
-
'--control-plane <url> Control plane base URL',
|
|
22
|
-
'--token <jwt> Clerk CLI JWT token',
|
|
23
|
-
'--token-stdin Read token from stdin',
|
|
24
|
-
'--json whoami/create-space: print JSON output',
|
|
25
|
-
'--callback-host <host> Browser callback host for login (default: 127.0.0.1)',
|
|
26
|
-
'--callback-port <port> Browser callback port for login (default: random)',
|
|
27
|
-
'--auth-url <url> Browser auth bridge URL (default: <control-plane>/cli-login)',
|
|
28
|
-
'--timeout-seconds <n> Login browser wait timeout (default: 180)',
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
title: 'Create Space Flags',
|
|
33
|
-
lines: [
|
|
34
|
-
'--name <name> Space name',
|
|
35
|
-
'--database-provider <id> sqlite|neon|postgres (default: sqlite)',
|
|
36
|
-
'--connection-string <url> Required when database-provider is neon/postgres',
|
|
37
|
-
'--region <id> auto|enam|wnam|weur|eeur|apac|oc (default: auto)',
|
|
38
|
-
'--json Print response as JSON (console token redacted)',
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
title: 'Create Flags',
|
|
43
|
-
lines: [
|
|
44
|
-
`--template <id> Template id: ${TEMPLATE_ID_LIST}`,
|
|
45
|
-
'--dir <path> Scaffold target directory (template-specific default)',
|
|
46
|
-
'--name <name> Project display/name override (spaces-app only)',
|
|
47
|
-
'--targets <csv> syncular-libraries targets (server,react,vanilla,expo,react-native,electron,proxy-api)',
|
|
48
|
-
'--server-dialect <id> syncular-libraries server dialect sqlite|postgres',
|
|
49
|
-
'--react-dialect <id> syncular-libraries react dialect wa-sqlite|pglite|bun-sqlite|better-sqlite3|sqlite3',
|
|
50
|
-
'--vanilla-dialect <id> syncular-libraries vanilla dialect wa-sqlite|pglite|bun-sqlite|better-sqlite3|sqlite3',
|
|
51
|
-
'--electron-dialect <id> syncular-libraries electron dialect electron-sqlite|better-sqlite3',
|
|
52
|
-
'--force <true|false> Overwrite scaffold files (default: false)',
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
title: 'Dev Flags',
|
|
57
|
-
lines: [
|
|
58
|
-
'--host <host> Bind host (default: 127.0.0.1)',
|
|
59
|
-
'--port <port> Bind port (default: 4312)',
|
|
60
|
-
'--db-provider <kind> Local DB provider sqlite|neon|postgres',
|
|
61
|
-
'--db <path> Local sqlite db path override',
|
|
62
|
-
'--connection-string <url> Local Postgres/Neon connection string override',
|
|
63
|
-
'--watch <true|false> Watch source files and restart (default: true)',
|
|
64
|
-
'--open <true|false> Open embedded console in browser after start',
|
|
65
|
-
'--console-token <token> Bearer token for /api/console',
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
title: 'Shared Flags',
|
|
70
|
-
lines: [
|
|
71
|
-
'--space <id> Space id to deploy/verify/list/rollback',
|
|
72
|
-
'--target <id> Target profile spaces',
|
|
73
|
-
'--control-plane <url> Control plane base URL',
|
|
74
|
-
'--control-token <token> Bearer token for control-plane auth',
|
|
75
|
-
'--actor-id <id> Actor id fallback when token is missing (dev only)',
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
title: 'Migrate Status Flags',
|
|
80
|
-
lines: [
|
|
81
|
-
'--config <path> Config file path (default: syncular.config.json)',
|
|
82
|
-
],
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
title: 'Typegen Flags',
|
|
86
|
-
lines: [
|
|
87
|
-
'--config <path> Config file path (default: syncular.config.ts)',
|
|
88
|
-
'--out <path> Output file path (default: ./src/types.generated.ts)',
|
|
89
|
-
'--dialect <id> sqlite|postgres (default: sqlite)',
|
|
90
|
-
'--tables <csv> Restrict generation to table names',
|
|
91
|
-
'--extends-sync-client-db <true|false> Extend SyncClientDb (default: true)',
|
|
92
|
-
'--include-version-history <true|false> Include versioned DB interfaces (default: false)',
|
|
93
|
-
'--syncular-import-type <id> scoped|umbrella (default: umbrella)',
|
|
94
|
-
],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
title: 'Migrate Up Flags',
|
|
98
|
-
lines: [
|
|
99
|
-
'--config <path> Config file path (default: syncular.config.json)',
|
|
100
|
-
'--on-checksum-mismatch <id> error|reset (default: error)',
|
|
101
|
-
'--dry-run <true|false> Plan migration without applying',
|
|
102
|
-
'--yes <true|false> Confirm reset mode when using --on-checksum-mismatch reset',
|
|
103
|
-
],
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
title: 'Console Flags',
|
|
107
|
-
lines: [
|
|
108
|
-
'--host <host> Bind host (default: 127.0.0.1)',
|
|
109
|
-
'--port <port> Bind port (default: 4310, 0 for random)',
|
|
110
|
-
'--open <true|false> Open browser after server starts',
|
|
111
|
-
'--server-url <url> Preconfigure console API server URL',
|
|
112
|
-
'--token <token> Preconfigure console API token',
|
|
113
|
-
'--token-env <name> Read console API token from env var',
|
|
114
|
-
],
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
title: 'Build Flags',
|
|
118
|
-
lines: [
|
|
119
|
-
'--target <id> Target profile spaces',
|
|
120
|
-
`--buildpack <id> Buildpack id (default: ${DEFAULT_BUILDPACK_ID})`,
|
|
121
|
-
'--config <path> Contract config path (target default)',
|
|
122
|
-
'--entry <path> Explicit worker entry override (advanced)',
|
|
123
|
-
'--out <path> Artifact output path',
|
|
124
|
-
'--manifest <path> Build manifest output path',
|
|
125
|
-
'--force <true|false> Overwrite output files (default: false)',
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
title: 'Eject Flags',
|
|
130
|
-
lines: [
|
|
131
|
-
'--target <id> Target profile spaces',
|
|
132
|
-
`--buildpack <id> Buildpack id (default: ${DEFAULT_BUILDPACK_ID})`,
|
|
133
|
-
'--config <path> Contract config path (target default)',
|
|
134
|
-
'--entry <path> Explicit worker entry override (advanced)',
|
|
135
|
-
'--out-dir <path> Eject output directory',
|
|
136
|
-
'--force <true|false> Overwrite output files (default: false)',
|
|
137
|
-
],
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
title: 'Target Flags',
|
|
141
|
-
lines: ['--set <id> Set default target profile (spaces)'],
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
title: 'Deploy Flags',
|
|
145
|
-
lines: [
|
|
146
|
-
'--target <id> Target profile spaces (default from state)',
|
|
147
|
-
'--config <path> Contract config path (target default)',
|
|
148
|
-
'--entry <path> Explicit worker entry override (advanced)',
|
|
149
|
-
'--script-name <name> Override target script name',
|
|
150
|
-
'--source <label> Deployment source label (spaces target only, default: syncular-cli)',
|
|
151
|
-
'--dry-run <true|false> Bundle + metadata validation only (no deploy)',
|
|
152
|
-
],
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
title: 'Verify Flags',
|
|
156
|
-
lines: [
|
|
157
|
-
'--target <id> Target profile spaces (default from state)',
|
|
158
|
-
'--base-url <url> Runtime base URL (default from control-plane runtime)',
|
|
159
|
-
'--origin <url> CORS preflight origin to validate (default: http://127.0.0.1:4320)',
|
|
160
|
-
],
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
title: 'Deployments Flags',
|
|
164
|
-
lines: [
|
|
165
|
-
'--limit <n> Number of deployment records to list (default: 20)',
|
|
166
|
-
],
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
title: 'Rollback Flags',
|
|
170
|
-
lines: [
|
|
171
|
-
'--to <deploymentId> Target deployment id to rollback to',
|
|
172
|
-
'--reason <text> Optional rollback reason',
|
|
173
|
-
],
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
title: 'Global Flags',
|
|
177
|
-
lines: [
|
|
178
|
-
'--interactive Force interactive TUI',
|
|
179
|
-
'--no-interactive Disable interactive TUI',
|
|
180
|
-
'--no-update-check Skip npm update check for this run',
|
|
181
|
-
'--forms Enable interactive command input forms',
|
|
182
|
-
'--help Show help',
|
|
183
|
-
'--version Show version',
|
|
184
|
-
],
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
title: 'Env Fallbacks',
|
|
188
|
-
lines: [
|
|
189
|
-
'SPACES_CONTROL_PLANE_BASE_URL, SPACES_TEST_ACTOR_ID,',
|
|
190
|
-
'SPACES_CONTROL_PLANE_TOKEN, SPACES_CONTROL_TOKEN,',
|
|
191
|
-
'SYNCULAR_DB_PROVIDER, SPACES_SQLITE_PATH,',
|
|
192
|
-
'SYNCULAR_PG_CONNECTION_STRING, DATABASE_URL,',
|
|
193
|
-
'SYNCULAR_CLI_DISABLE_UPDATE_CHECK',
|
|
194
|
-
],
|
|
195
|
-
},
|
|
196
|
-
];
|
|
197
|
-
|
|
198
|
-
function HelpView(): ReactElement {
|
|
199
|
-
return (
|
|
200
|
-
<Box flexDirection="column">
|
|
201
|
-
<Text color="cyanBright" bold>
|
|
202
|
-
{CLI_NAME} CLI v{CLI_VERSION}
|
|
203
|
-
</Text>
|
|
204
|
-
<Text dimColor>
|
|
205
|
-
Interactive by default in TTY. Use --no-interactive for script mode.
|
|
206
|
-
</Text>
|
|
207
|
-
<Box marginTop={1} flexDirection="column">
|
|
208
|
-
<Text color="yellowBright" bold>
|
|
209
|
-
Usage
|
|
210
|
-
</Text>
|
|
211
|
-
{USAGE_LINES.map((line) => (
|
|
212
|
-
<Text key={line} color="cyan">
|
|
213
|
-
{line}
|
|
214
|
-
</Text>
|
|
215
|
-
))}
|
|
216
|
-
</Box>
|
|
217
|
-
{HELP_SECTIONS.map((section) => (
|
|
218
|
-
<Box key={section.title} marginTop={1} flexDirection="column">
|
|
219
|
-
<Text color="yellowBright" bold>
|
|
220
|
-
{section.title}
|
|
221
|
-
</Text>
|
|
222
|
-
{section.lines.map((line) => (
|
|
223
|
-
<Text key={`${section.title}:${line}`}>{line}</Text>
|
|
224
|
-
))}
|
|
225
|
-
</Box>
|
|
226
|
-
))}
|
|
227
|
-
</Box>
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function printHelp(): void {
|
|
232
|
-
const rendered = renderToString(<HelpView />, {
|
|
233
|
-
columns: Math.max(process.stdout.columns ?? 140, 140),
|
|
234
|
-
});
|
|
235
|
-
process.stdout.write(rendered);
|
|
236
|
-
if (!rendered.endsWith('\n')) {
|
|
237
|
-
process.stdout.write('\n');
|
|
238
|
-
}
|
|
239
|
-
}
|
package/src/index.ts
DELETED
package/src/interactive.tsx
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
import { Box, Text, useApp, useInput } from 'ink';
|
|
2
|
-
import type { ReactElement } from 'react';
|
|
3
|
-
import { useMemo, useState } from 'react';
|
|
4
|
-
import { listInteractiveCommands } from './command-registry';
|
|
5
|
-
import { CLI_NAME, CLI_VERSION } from './constants';
|
|
6
|
-
import type { InteractiveCommand, RootCommand } from './types';
|
|
7
|
-
|
|
8
|
-
interface InteractiveAppProps {
|
|
9
|
-
initialCommand: RootCommand | null;
|
|
10
|
-
allowForms: boolean;
|
|
11
|
-
onSubmit(argv: string[]): void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type Screen = 'menu' | 'form';
|
|
15
|
-
|
|
16
|
-
function parseBooleanInput(value: string): boolean | null {
|
|
17
|
-
const normalized = value.trim().toLowerCase();
|
|
18
|
-
if (
|
|
19
|
-
normalized === 'true' ||
|
|
20
|
-
normalized === '1' ||
|
|
21
|
-
normalized === 'yes' ||
|
|
22
|
-
normalized === 'on'
|
|
23
|
-
) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
if (
|
|
27
|
-
normalized === 'false' ||
|
|
28
|
-
normalized === '0' ||
|
|
29
|
-
normalized === 'no' ||
|
|
30
|
-
normalized === 'off'
|
|
31
|
-
) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function validateFields(
|
|
38
|
-
command: InteractiveCommand,
|
|
39
|
-
values: Record<string, string>
|
|
40
|
-
): string | null {
|
|
41
|
-
for (const field of command.fields ?? []) {
|
|
42
|
-
const value = values[field.id] ?? '';
|
|
43
|
-
if (field.required && value.trim().length === 0) {
|
|
44
|
-
return `${field.label} is required.`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (field.kind === 'number' && value.trim().length > 0) {
|
|
48
|
-
const parsed = Number(value.trim());
|
|
49
|
-
if (!Number.isFinite(parsed)) {
|
|
50
|
-
return `${field.label} must be a number.`;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (field.kind === 'boolean' && value.trim().length > 0) {
|
|
55
|
-
if (parseBooleanInput(value) === null) {
|
|
56
|
-
return `${field.label} must be true/false, yes/no, on/off, or 1/0.`;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function InteractiveApp(props: InteractiveAppProps): ReactElement {
|
|
64
|
-
const { exit } = useApp();
|
|
65
|
-
const commands = useMemo(
|
|
66
|
-
() => listInteractiveCommands(props.initialCommand),
|
|
67
|
-
[props.initialCommand]
|
|
68
|
-
);
|
|
69
|
-
const [screen, setScreen] = useState<Screen>('menu');
|
|
70
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
71
|
-
const [activeCommand, setActiveCommand] = useState<InteractiveCommand | null>(
|
|
72
|
-
null
|
|
73
|
-
);
|
|
74
|
-
const [activeFieldIndex, setActiveFieldIndex] = useState(0);
|
|
75
|
-
const [fieldValues, setFieldValues] = useState<Record<string, string>>({});
|
|
76
|
-
const [hint, setHint] = useState<string | null>(null);
|
|
77
|
-
|
|
78
|
-
const submitArgv = (argv: string[] | null): void => {
|
|
79
|
-
if (!argv) {
|
|
80
|
-
exit();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
props.onSubmit(argv);
|
|
84
|
-
exit();
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const startForm = (command: InteractiveCommand): void => {
|
|
88
|
-
const initialValues: Record<string, string> = {};
|
|
89
|
-
for (const field of command.fields ?? []) {
|
|
90
|
-
initialValues[field.id] = field.defaultValue ?? '';
|
|
91
|
-
}
|
|
92
|
-
setActiveCommand(command);
|
|
93
|
-
setActiveFieldIndex(0);
|
|
94
|
-
setFieldValues(initialValues);
|
|
95
|
-
setHint(null);
|
|
96
|
-
setScreen('form');
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const submitForm = (): void => {
|
|
100
|
-
if (!activeCommand) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const validationError = validateFields(activeCommand, fieldValues);
|
|
105
|
-
if (validationError) {
|
|
106
|
-
setHint(validationError);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
submitArgv(activeCommand.buildArgv(fieldValues));
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
useInput((input, key) => {
|
|
114
|
-
if (input.toLowerCase() === 'q') {
|
|
115
|
-
exit();
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (screen === 'menu') {
|
|
120
|
-
if (key.upArrow) {
|
|
121
|
-
setSelectedIndex((index) =>
|
|
122
|
-
index === 0 ? commands.length - 1 : index - 1
|
|
123
|
-
);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (key.downArrow) {
|
|
128
|
-
setSelectedIndex((index) =>
|
|
129
|
-
index === commands.length - 1 ? 0 : index + 1
|
|
130
|
-
);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!key.return) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const selected = commands[selectedIndex];
|
|
139
|
-
if (!selected) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if ((selected.fields?.length ?? 0) === 0) {
|
|
144
|
-
submitArgv(selected.buildArgv({}));
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!props.allowForms) {
|
|
149
|
-
submitArgv(selected.buildArgv({}));
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
startForm(selected);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (!activeCommand || screen !== 'form') {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (key.escape) {
|
|
162
|
-
setScreen('menu');
|
|
163
|
-
setActiveCommand(null);
|
|
164
|
-
setHint(null);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const fields = activeCommand.fields ?? [];
|
|
169
|
-
if (fields.length === 0) {
|
|
170
|
-
submitArgv(activeCommand.buildArgv(fieldValues));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const activeField = fields[activeFieldIndex];
|
|
175
|
-
if (!activeField) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (key.upArrow) {
|
|
180
|
-
setActiveFieldIndex((index) =>
|
|
181
|
-
index === 0 ? fields.length - 1 : index - 1
|
|
182
|
-
);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (key.downArrow || key.tab) {
|
|
187
|
-
setActiveFieldIndex((index) =>
|
|
188
|
-
index === fields.length - 1 ? 0 : index + 1
|
|
189
|
-
);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (key.ctrl && input.toLowerCase() === 'r') {
|
|
194
|
-
submitForm();
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (key.backspace || key.delete) {
|
|
199
|
-
setFieldValues((current) => ({
|
|
200
|
-
...current,
|
|
201
|
-
[activeField.id]: (current[activeField.id] ?? '').slice(0, -1),
|
|
202
|
-
}));
|
|
203
|
-
setHint(null);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (key.return) {
|
|
208
|
-
if (activeFieldIndex === fields.length - 1) {
|
|
209
|
-
submitForm();
|
|
210
|
-
} else {
|
|
211
|
-
setActiveFieldIndex((index) => index + 1);
|
|
212
|
-
}
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (!input || key.ctrl || key.meta) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
setFieldValues((current) => ({
|
|
221
|
-
...current,
|
|
222
|
-
[activeField.id]: `${current[activeField.id] ?? ''}${input}`,
|
|
223
|
-
}));
|
|
224
|
-
setHint(null);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<Box flexDirection="column">
|
|
229
|
-
<Box borderStyle="round" borderColor="cyan" paddingX={1} paddingY={0}>
|
|
230
|
-
<Text color="cyanBright">
|
|
231
|
-
{CLI_NAME} interactive {`v${CLI_VERSION}`}
|
|
232
|
-
</Text>
|
|
233
|
-
</Box>
|
|
234
|
-
<Text color="gray">
|
|
235
|
-
{screen === 'menu'
|
|
236
|
-
? props.allowForms
|
|
237
|
-
? 'Up/Down: select Enter: open form q: quit'
|
|
238
|
-
: 'Up/Down: select Enter: run q: quit (use --forms for input forms)'
|
|
239
|
-
: 'Type to edit Up/Down: field Enter: next/run Ctrl+R: run Esc: back'}
|
|
240
|
-
</Text>
|
|
241
|
-
<Box marginTop={1}>
|
|
242
|
-
{screen === 'menu' ? (
|
|
243
|
-
<Box flexDirection="column" width={88}>
|
|
244
|
-
{commands.map((command, index) => {
|
|
245
|
-
const active = index === selectedIndex;
|
|
246
|
-
return (
|
|
247
|
-
<Box key={command.id}>
|
|
248
|
-
<Text color={active ? 'greenBright' : undefined}>
|
|
249
|
-
{active ? '> ' : ' '}
|
|
250
|
-
{command.label.padEnd(18)}
|
|
251
|
-
</Text>
|
|
252
|
-
<Text color={active ? 'white' : 'gray'}>
|
|
253
|
-
{command.description}
|
|
254
|
-
</Text>
|
|
255
|
-
</Box>
|
|
256
|
-
);
|
|
257
|
-
})}
|
|
258
|
-
</Box>
|
|
259
|
-
) : (
|
|
260
|
-
<Box
|
|
261
|
-
flexDirection="column"
|
|
262
|
-
borderStyle="single"
|
|
263
|
-
borderColor="green"
|
|
264
|
-
paddingX={1}
|
|
265
|
-
paddingY={0}
|
|
266
|
-
width={88}
|
|
267
|
-
>
|
|
268
|
-
<Text color="greenBright">{activeCommand?.label}</Text>
|
|
269
|
-
<Box marginTop={1} flexDirection="column">
|
|
270
|
-
{(activeCommand?.fields ?? []).map((field, index) => {
|
|
271
|
-
const active = index === activeFieldIndex;
|
|
272
|
-
const value = fieldValues[field.id] ?? '';
|
|
273
|
-
const required = field.required ? ' *' : '';
|
|
274
|
-
return (
|
|
275
|
-
<Box key={field.id}>
|
|
276
|
-
<Text color={active ? 'yellowBright' : 'cyan'}>
|
|
277
|
-
{active ? '> ' : ' '}
|
|
278
|
-
{field.label}
|
|
279
|
-
{required}:{' '}
|
|
280
|
-
</Text>
|
|
281
|
-
<Text color={value.length > 0 ? 'white' : 'gray'}>
|
|
282
|
-
{value.length > 0
|
|
283
|
-
? value
|
|
284
|
-
: (field.placeholder ?? field.defaultValue ?? '')}
|
|
285
|
-
</Text>
|
|
286
|
-
</Box>
|
|
287
|
-
);
|
|
288
|
-
})}
|
|
289
|
-
</Box>
|
|
290
|
-
<Box marginTop={1}>
|
|
291
|
-
<Text color="gray">
|
|
292
|
-
command:{' '}
|
|
293
|
-
{(activeCommand?.buildArgv(fieldValues) ?? ['']).join(' ')}
|
|
294
|
-
</Text>
|
|
295
|
-
</Box>
|
|
296
|
-
{hint ? (
|
|
297
|
-
<Box marginTop={1}>
|
|
298
|
-
<Text color="redBright">{hint}</Text>
|
|
299
|
-
</Box>
|
|
300
|
-
) : null}
|
|
301
|
-
</Box>
|
|
302
|
-
)}
|
|
303
|
-
</Box>
|
|
304
|
-
</Box>
|
|
305
|
-
);
|
|
306
|
-
}
|