@runloop/rl-cli 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/commands/devbox/list.js +17 -1
- package/dist/commands/devbox/rsync.js +69 -41
- package/dist/commands/devbox/scp.js +180 -39
- package/dist/commands/gateway-config/create.js +22 -13
- package/dist/commands/gateway-config/get.js +7 -4
- package/dist/commands/gateway-config/list.js +11 -11
- package/dist/commands/gateway-config/update.js +37 -27
- package/dist/components/DevboxActionsMenu.js +17 -1
- package/dist/components/DevboxCreatePage.js +330 -99
- package/dist/components/DevboxDetailPage.js +46 -2
- package/dist/components/GatewayConfigCreatePage.js +35 -28
- package/dist/components/ResourcePicker.js +21 -7
- package/dist/components/SecretCreatePage.js +69 -23
- package/dist/components/SettingsMenu.js +1 -1
- package/dist/screens/GatewayConfigDetailScreen.js +14 -14
- package/dist/screens/SecretDetailScreen.js +26 -2
- package/dist/services/gatewayConfigService.js +39 -0
- package/dist/utils/commands.js +29 -13
- package/dist/utils/gatewayConfigValidation.js +58 -0
- package/package.json +2 -1
|
@@ -68,6 +68,45 @@ export async function getGatewayConfig(id) {
|
|
|
68
68
|
account_id: config.account_id ?? undefined,
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Get a single gateway config by ID or name
|
|
73
|
+
*/
|
|
74
|
+
export async function getGatewayConfigByIdOrName(idOrName) {
|
|
75
|
+
const client = getClient();
|
|
76
|
+
// Try to retrieve directly by ID first
|
|
77
|
+
try {
|
|
78
|
+
const config = await client.gatewayConfigs.retrieve(idOrName);
|
|
79
|
+
return {
|
|
80
|
+
id: config.id,
|
|
81
|
+
name: config.name,
|
|
82
|
+
description: config.description ?? undefined,
|
|
83
|
+
endpoint: config.endpoint,
|
|
84
|
+
create_time_ms: config.create_time_ms,
|
|
85
|
+
auth_mechanism: {
|
|
86
|
+
type: config.auth_mechanism.type,
|
|
87
|
+
key: config.auth_mechanism.key ?? undefined,
|
|
88
|
+
},
|
|
89
|
+
account_id: config.account_id ?? undefined,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Not found by ID, try by name
|
|
94
|
+
}
|
|
95
|
+
// Search by name
|
|
96
|
+
const queryParams = {
|
|
97
|
+
limit: 100,
|
|
98
|
+
name: idOrName,
|
|
99
|
+
};
|
|
100
|
+
const pagePromise = client.gatewayConfigs.list(queryParams);
|
|
101
|
+
const page = (await pagePromise);
|
|
102
|
+
const configs = page.gateway_configs || [];
|
|
103
|
+
if (configs.length === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
// Return the first exact match, or first result if no exact match
|
|
107
|
+
const match = configs.find((g) => g.name === idOrName) || configs[0];
|
|
108
|
+
return getGatewayConfig(match.id);
|
|
109
|
+
}
|
|
71
110
|
/**
|
|
72
111
|
* Delete a gateway config
|
|
73
112
|
*/
|
package/dist/utils/commands.js
CHANGED
|
@@ -14,7 +14,9 @@ export function createProgram() {
|
|
|
14
14
|
program
|
|
15
15
|
.name("rli")
|
|
16
16
|
.description("Beautiful CLI for Runloop devbox management")
|
|
17
|
-
.version(VERSION)
|
|
17
|
+
.version(VERSION)
|
|
18
|
+
.showHelpAfterError()
|
|
19
|
+
.showSuggestionAfterError();
|
|
18
20
|
// Devbox commands
|
|
19
21
|
const devbox = program
|
|
20
22
|
.command("devbox")
|
|
@@ -119,22 +121,36 @@ export function createProgram() {
|
|
|
119
121
|
await sshDevbox(id, options);
|
|
120
122
|
});
|
|
121
123
|
devbox
|
|
122
|
-
.command("scp <
|
|
123
|
-
.description("Copy files to/from a devbox using scp"
|
|
124
|
+
.command("scp <src> <dst>")
|
|
125
|
+
.description("Copy files to/from a devbox using scp. Use the devbox ID (dbx_*) as a hostname in src or dst.\n\n" +
|
|
126
|
+
" Examples:\n" +
|
|
127
|
+
" $ rli devbox scp dbx_abc123:/home/user/file.txt ./file.txt # download from devbox\n" +
|
|
128
|
+
" $ rli devbox scp ./file.txt dbx_abc123:/home/user/file.txt # upload to devbox\n" +
|
|
129
|
+
" $ rli devbox scp root@dbx_abc123:/etc/hosts ./hosts # with explicit user\n" +
|
|
130
|
+
" $ rli devbox scp dbx_src:/data/file.txt dbx_dst:/data/file.txt # devbox to devbox\n\n" +
|
|
131
|
+
" If no user is specified, the devbox's configured user is used.\n" +
|
|
132
|
+
" Paths without a dbx_ hostname are treated as local.\n" +
|
|
133
|
+
" Devbox-to-devbox transfers route through your local machine via scp -3.")
|
|
124
134
|
.option("--scp-options <options>", "Additional scp options (quoted)")
|
|
125
135
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
126
|
-
.action(async (
|
|
136
|
+
.action(async (src, dst, options) => {
|
|
127
137
|
const { scpFiles } = await import("../commands/devbox/scp.js");
|
|
128
|
-
await scpFiles(
|
|
138
|
+
await scpFiles(src, dst, options);
|
|
129
139
|
});
|
|
130
140
|
devbox
|
|
131
|
-
.command("rsync <
|
|
132
|
-
.description("Sync files to/from a devbox using rsync"
|
|
141
|
+
.command("rsync <src> <dst>")
|
|
142
|
+
.description("Sync files to/from a devbox using rsync. Use the devbox ID (dbx_*) as a hostname in src or dst.\n\n" +
|
|
143
|
+
" Examples:\n" +
|
|
144
|
+
" $ rli devbox rsync dbx_abc123:/home/user/data/ ./data/ # download from devbox\n" +
|
|
145
|
+
" $ rli devbox rsync ./data/ dbx_abc123:/home/user/data/ # upload to devbox\n" +
|
|
146
|
+
" $ rli devbox rsync root@dbx_abc123:/etc/config/ ./config/ # with explicit user\n\n" +
|
|
147
|
+
" If no user is specified, the devbox's configured user is used.\n" +
|
|
148
|
+
" Paths without a dbx_ hostname are treated as local.")
|
|
133
149
|
.option("--rsync-options <options>", "Additional rsync options (quoted)")
|
|
134
150
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
135
|
-
.action(async (
|
|
151
|
+
.action(async (src, dst, options) => {
|
|
136
152
|
const { rsyncFiles } = await import("../commands/devbox/rsync.js");
|
|
137
|
-
await rsyncFiles(
|
|
153
|
+
await rsyncFiles(src, dst, options);
|
|
138
154
|
});
|
|
139
155
|
devbox
|
|
140
156
|
.command("tunnel <id> <ports>")
|
|
@@ -530,8 +546,8 @@ export function createProgram() {
|
|
|
530
546
|
.description("Create a new gateway configuration")
|
|
531
547
|
.requiredOption("--name <name>", "Gateway config name (required)")
|
|
532
548
|
.requiredOption("--endpoint <url>", "Target endpoint URL (required)")
|
|
533
|
-
.
|
|
534
|
-
.option("--auth
|
|
549
|
+
.option("--bearer-auth", "Use Bearer token authentication (default)")
|
|
550
|
+
.option("--header-auth <header>", "Use custom header authentication (specify header key name)")
|
|
535
551
|
.option("--description <description>", "Description")
|
|
536
552
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
537
553
|
.action(async (options) => {
|
|
@@ -551,8 +567,8 @@ export function createProgram() {
|
|
|
551
567
|
.description("Update a gateway configuration")
|
|
552
568
|
.option("--name <name>", "New name")
|
|
553
569
|
.option("--endpoint <url>", "New endpoint URL")
|
|
554
|
-
.option("--auth
|
|
555
|
-
.option("--auth
|
|
570
|
+
.option("--bearer-auth", "Use Bearer token authentication")
|
|
571
|
+
.option("--header-auth <header>", "Use custom header authentication (specify header key name)")
|
|
556
572
|
.option("--description <description>", "New description")
|
|
557
573
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
558
574
|
.action(async (id, options) => {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared validation for gateway config create/update operations.
|
|
3
|
+
* Used by both CLI commands and UI components.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Validate and sanitize gateway config fields.
|
|
7
|
+
*
|
|
8
|
+
* @param input - The fields to validate
|
|
9
|
+
* @param opts.requireName - Whether name is required (true for create, false for update)
|
|
10
|
+
* @param opts.requireEndpoint - Whether endpoint is required (true for create, false for update)
|
|
11
|
+
*/
|
|
12
|
+
export function validateGatewayConfig(input, opts = {}) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
const name = input.name?.trim();
|
|
15
|
+
const endpoint = input.endpoint?.trim();
|
|
16
|
+
const authType = input.authType?.toLowerCase();
|
|
17
|
+
const authKey = input.authKey?.trim();
|
|
18
|
+
// Name validation
|
|
19
|
+
if (opts.requireName && !name) {
|
|
20
|
+
errors.push("Name is required");
|
|
21
|
+
}
|
|
22
|
+
// Endpoint validation
|
|
23
|
+
if (opts.requireEndpoint && !endpoint) {
|
|
24
|
+
errors.push("Endpoint URL is required");
|
|
25
|
+
}
|
|
26
|
+
if (endpoint) {
|
|
27
|
+
if (!endpoint.startsWith("https://") && !endpoint.startsWith("http://")) {
|
|
28
|
+
errors.push("Endpoint must be a valid URL starting with https:// or http://");
|
|
29
|
+
}
|
|
30
|
+
// Basic URL structure validation
|
|
31
|
+
try {
|
|
32
|
+
new URL(endpoint);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
errors.push("Endpoint is not a valid URL");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Auth validation
|
|
39
|
+
if (authType && authType !== "bearer" && authType !== "header") {
|
|
40
|
+
errors.push('Auth type must be either "bearer" or "header"');
|
|
41
|
+
}
|
|
42
|
+
if (authType === "header" && !authKey) {
|
|
43
|
+
errors.push("Auth header key is required when using header authentication");
|
|
44
|
+
}
|
|
45
|
+
if (errors.length > 0) {
|
|
46
|
+
return { valid: false, errors };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
valid: true,
|
|
50
|
+
errors: [],
|
|
51
|
+
sanitized: {
|
|
52
|
+
name,
|
|
53
|
+
endpoint,
|
|
54
|
+
authType,
|
|
55
|
+
authKey,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runloop/rl-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Beautiful CLI for the Runloop platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"test:watch": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
|
|
104
104
|
"test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
|
|
105
105
|
"test:components": "NODE_OPTIONS='--experimental-vm-modules' jest --config jest.components.config.js --coverage --forceExit",
|
|
106
|
+
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules' jest --config jest.e2e.config.js --forceExit",
|
|
106
107
|
"docs:commands": "pnpm run build && node scripts/generate-command-docs.js"
|
|
107
108
|
}
|
|
108
109
|
}
|