@levr-one/cli 0.1.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 +172 -0
- package/dist/app-0F0Zyyt7.js +350 -0
- package/dist/bash-complete.js +13 -0
- package/dist/cli.js +15 -0
- package/dist/credentials-CfHLkU7k.js +54 -0
- package/dist/currentHandler-oXFeNNgd.js +11 -0
- package/dist/listHandler-BiwIo88i.js +44 -0
- package/dist/loginHandler-C8D6DgjW.js +363 -0
- package/dist/logoutHandler-BBtdpxHX.js +14 -0
- package/dist/pushHandler-DkCrbV7y.js +529 -0
- package/dist/resolve-token-BL8vL_ok.js +33 -0
- package/dist/resolve-workspace-lHEoGPHe.js +89 -0
- package/dist/sdk-client-DunBmYLR.js +30559 -0
- package/dist/selectHandler-C2UDAWOl.js +40 -0
- package/dist/statusHandler-DQDmEOGI.js +51 -0
- package/dist/token-refresh-waF23pyw.js +48 -0
- package/dist/workspace-store-BcyMJAht.js +29 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# @levr-one/cli
|
|
2
|
+
|
|
3
|
+
The command-line interface for [Levr](https://www.levr.one). Push test results
|
|
4
|
+
from any CI pipeline, manage authentication, and select workspaces. The binary
|
|
5
|
+
is `levr`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @levr-one/cli # global install for daily use
|
|
11
|
+
# or run once, no install:
|
|
12
|
+
npx @levr-one/cli --help
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The package is self-contained — no peer setup required.
|
|
16
|
+
|
|
17
|
+
### Shell completion (optional)
|
|
18
|
+
|
|
19
|
+
Tab-completion is an explicit opt-in step (it is not installed automatically):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
levr install # add shell completion for the current shell
|
|
23
|
+
levr uninstall # remove it
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Authentication
|
|
27
|
+
|
|
28
|
+
The CLI supports three authentication modes.
|
|
29
|
+
|
|
30
|
+
### Interactive (browser) — default
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
levr auth login
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Opens a browser for PKCE-based OAuth login.
|
|
37
|
+
|
|
38
|
+
### Device code (SSH / headless)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
levr auth login --device-code
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A code is displayed in the terminal. Open the provided URL on any device, enter
|
|
45
|
+
the code, and approve.
|
|
46
|
+
|
|
47
|
+
### Personal Access Token (CI/CD)
|
|
48
|
+
|
|
49
|
+
Set the `LEVR_TOKEN` environment variable and the CLI uses it automatically —
|
|
50
|
+
no interactive login:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export LEVR_TOKEN=<your-personal-access-token>
|
|
54
|
+
levr push ./results.xml
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Other auth commands
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
levr auth status # show current authentication state
|
|
61
|
+
levr auth logout # clear stored credentials
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Workspaces
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
levr workspace list # list the workspaces you belong to
|
|
68
|
+
levr workspace select # choose the active workspace
|
|
69
|
+
levr workspace current # show the active workspace
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Push test results
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
levr push <file> [options]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The backend auto-detects the file format. In CI, the automation source name and
|
|
79
|
+
run metadata are auto-detected. Team ID is optional — when omitted, the server
|
|
80
|
+
resolves the team from the automation source (if `--source` matches a known
|
|
81
|
+
source) or the workspace's default team.
|
|
82
|
+
|
|
83
|
+
**Examples:**
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Basic push (server resolves the default team)
|
|
87
|
+
levr push ./test-results.xml
|
|
88
|
+
|
|
89
|
+
# With an explicit team
|
|
90
|
+
levr push ./test-results.xml --team-id <uuid>
|
|
91
|
+
|
|
92
|
+
# With a custom source name and run name
|
|
93
|
+
levr push ./results.xml --source "backend-unit-tests" --run-name "nightly"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Flags:**
|
|
97
|
+
|
|
98
|
+
| Flag | Alias | Description |
|
|
99
|
+
| --------------------------- | ----- | ------------------------------------------------------------------------------------------ |
|
|
100
|
+
| `--team-id <uuid>` | `-t` | Team ID (optional; server resolves default if omitted. Or set `LEVR_TEAM_ID`) |
|
|
101
|
+
| `--source <name>` | `-s` | Automation source name — groups recurring imports and remembers team (auto-detected in CI) |
|
|
102
|
+
| `--run-name <name>` | `-r` | Name for the test run |
|
|
103
|
+
| `--format <type>` | `-f` | File format: `junit`, `gherkin`, `cucumber-json` (auto-detected if omitted) |
|
|
104
|
+
| `--parent-folder-id <uuid>` | | Destination folder ID |
|
|
105
|
+
| `--create-run` | | Force run creation for structure-only imports |
|
|
106
|
+
| `--update-mode <mode>` | | `update` (default) or `create_new` |
|
|
107
|
+
| `--verbose` | `-v` | Show detailed output |
|
|
108
|
+
|
|
109
|
+
### Automation sources
|
|
110
|
+
|
|
111
|
+
An automation source groups recurring imports from the same CI pipeline or test
|
|
112
|
+
suite. When you pass `--source`, the server creates the source on first use and
|
|
113
|
+
remembers which team it belongs to. On the first push, omit `--team-id` to link
|
|
114
|
+
the source to the workspace's default team, or pass `--team-id` to link it to a
|
|
115
|
+
specific team. Subsequent pushes with the same `--source` route to that team
|
|
116
|
+
automatically. Source names are normalized (lowercased, trimmed).
|
|
117
|
+
|
|
118
|
+
## CI/CD integration
|
|
119
|
+
|
|
120
|
+
### GitHub Actions
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
- name: Push test results to Levr
|
|
124
|
+
env:
|
|
125
|
+
LEVR_TOKEN: ${{ secrets.LEVR_TOKEN }}
|
|
126
|
+
# LEVR_TEAM_ID is optional — server resolves from automation source or workspace default
|
|
127
|
+
run: npx @levr-one/cli push ./test-results.xml
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### GitLab CI
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
push-results:
|
|
134
|
+
script:
|
|
135
|
+
- npx @levr-one/cli push ./test-results.xml
|
|
136
|
+
variables:
|
|
137
|
+
LEVR_TOKEN: $LEVR_TOKEN
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Jenkins
|
|
141
|
+
|
|
142
|
+
```groovy
|
|
143
|
+
withEnv(["LEVR_TOKEN=${LEVR_TOKEN}"]) {
|
|
144
|
+
sh 'npx @levr-one/cli push ./test-results.xml'
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
All configuration is via environment variables. Flags take precedence.
|
|
151
|
+
|
|
152
|
+
| Variable | Description | Default |
|
|
153
|
+
| --------------- | ---------------------------------------------------------------------------------------- | ----------------------- |
|
|
154
|
+
| `LEVR_TOKEN` | Personal Access Token (for CI / headless) | |
|
|
155
|
+
| `LEVR_URL` | API base URL | `https://api.levr.one` |
|
|
156
|
+
| `LEVR_AUTH_URL` | Auth server URL | `https://auth.levr.one` |
|
|
157
|
+
| `LEVR_TEAM_ID` | Default team ID (optional; server resolves from automation source or workspace default) | |
|
|
158
|
+
| `LEVR_SOURCE` | Automation source name override (groups imports, remembers team) | |
|
|
159
|
+
|
|
160
|
+
## Troubleshooting
|
|
161
|
+
|
|
162
|
+
**`Authentication required. Run: levr auth login`** — No valid credentials
|
|
163
|
+
found. Run `levr auth login` or set `LEVR_TOKEN`.
|
|
164
|
+
|
|
165
|
+
**`Token expired`** — Run `levr auth login` to re-authenticate.
|
|
166
|
+
|
|
167
|
+
**Browser doesn't open during `levr auth login`** — Use
|
|
168
|
+
`levr auth login --device-code` for headless environments.
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { buildApplication, buildCommand, buildRouteMap, text_en } from "@stricli/core";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { buildInstallCommand, buildUninstallCommand } from "@stricli/auto-complete";
|
|
7
|
+
|
|
8
|
+
//#region src/utils/logger.ts
|
|
9
|
+
var Logger = class {
|
|
10
|
+
verbose;
|
|
11
|
+
stdout;
|
|
12
|
+
stderr;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.verbose = options.verbose ?? false;
|
|
15
|
+
this.stdout = options.stdout ?? process.stdout;
|
|
16
|
+
this.stderr = options.stderr ?? process.stderr;
|
|
17
|
+
}
|
|
18
|
+
info(message) {
|
|
19
|
+
this.stdout.write(`${chalk.blue("info")} ${message}\n`);
|
|
20
|
+
}
|
|
21
|
+
success(message) {
|
|
22
|
+
this.stdout.write(`${chalk.green("ok")} ${message}\n`);
|
|
23
|
+
}
|
|
24
|
+
error(message) {
|
|
25
|
+
this.stderr.write(`${chalk.red("error")} ${message}\n`);
|
|
26
|
+
}
|
|
27
|
+
warning(message) {
|
|
28
|
+
this.stdout.write(`${chalk.yellow("warn")} ${message}\n`);
|
|
29
|
+
}
|
|
30
|
+
debug(message) {
|
|
31
|
+
if (this.verbose) this.stdout.write(`${chalk.gray("debug")} ${message}\n`);
|
|
32
|
+
}
|
|
33
|
+
setVerbose(verbose) {
|
|
34
|
+
this.verbose = verbose;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/context.ts
|
|
40
|
+
function buildContext(process$1) {
|
|
41
|
+
return {
|
|
42
|
+
process: process$1,
|
|
43
|
+
os,
|
|
44
|
+
fs,
|
|
45
|
+
path,
|
|
46
|
+
logger: new Logger({
|
|
47
|
+
verbose: false,
|
|
48
|
+
stdout: process$1.stdout,
|
|
49
|
+
stderr: process$1.stderr
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/commands/auth/login.ts
|
|
56
|
+
const loginCommand = buildCommand({
|
|
57
|
+
docs: {
|
|
58
|
+
brief: "Authenticate with Levr",
|
|
59
|
+
fullDescription: `Authenticate with Levr using OAuth.
|
|
60
|
+
|
|
61
|
+
By default, opens a browser for PKCE-based authentication.
|
|
62
|
+
For headless environments (SSH, containers), use --device-code
|
|
63
|
+
to authenticate via a code displayed in the terminal.
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
levr auth login # Browser-based PKCE login
|
|
67
|
+
levr auth login --device-code # Device flow for SSH/headless`
|
|
68
|
+
},
|
|
69
|
+
parameters: {
|
|
70
|
+
flags: { "device-code": {
|
|
71
|
+
kind: "boolean",
|
|
72
|
+
default: false,
|
|
73
|
+
brief: "Use device code flow (for SSH/headless environments)"
|
|
74
|
+
} },
|
|
75
|
+
aliases: { d: "device-code" }
|
|
76
|
+
},
|
|
77
|
+
loader: async () => {
|
|
78
|
+
const { loginHandler } = await import("./loginHandler-C8D6DgjW.js");
|
|
79
|
+
return loginHandler;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/commands/auth/logout.ts
|
|
85
|
+
const logoutCommand = buildCommand({
|
|
86
|
+
docs: {
|
|
87
|
+
brief: "Log out of Levr",
|
|
88
|
+
fullDescription: `Remove stored credentials.
|
|
89
|
+
|
|
90
|
+
Note: If using LEVR_TOKEN environment variable, it will remain set.
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
levr auth logout`
|
|
94
|
+
},
|
|
95
|
+
parameters: {},
|
|
96
|
+
loader: async () => {
|
|
97
|
+
const { logoutHandler } = await import("./logoutHandler-BBtdpxHX.js");
|
|
98
|
+
return logoutHandler;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/commands/auth/status.ts
|
|
104
|
+
const statusCommand = buildCommand({
|
|
105
|
+
docs: {
|
|
106
|
+
brief: "Check authentication status",
|
|
107
|
+
fullDescription: `Check the current authentication status.
|
|
108
|
+
|
|
109
|
+
Shows whether you are authenticated, the auth method (PAT or JWT),
|
|
110
|
+
and tests API reachability.
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
levr auth status`
|
|
114
|
+
},
|
|
115
|
+
parameters: {},
|
|
116
|
+
loader: async () => {
|
|
117
|
+
const { statusHandler } = await import("./statusHandler-DQDmEOGI.js");
|
|
118
|
+
return statusHandler;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/commands/push.ts
|
|
124
|
+
const pushCommand = buildCommand({
|
|
125
|
+
docs: {
|
|
126
|
+
brief: "Push test results to Levr",
|
|
127
|
+
fullDescription: `Upload a test result file to Levr.
|
|
128
|
+
|
|
129
|
+
The backend auto-detects the file format (JUnit XML, Gherkin, Cucumber JSON).
|
|
130
|
+
In CI environments, the automation source name and CI metadata are auto-detected.
|
|
131
|
+
|
|
132
|
+
Team ID is optional. When omitted, the server resolves the team from:
|
|
133
|
+
1. The existing automation source's team (if --source matches a known source)
|
|
134
|
+
2. The workspace's default team
|
|
135
|
+
|
|
136
|
+
Examples:
|
|
137
|
+
levr push ./test-results.xml
|
|
138
|
+
levr push ./results.xml --source "backend-unit-tests"
|
|
139
|
+
levr push ./report.json --team-id <uuid> # explicit team
|
|
140
|
+
levr push ./report.json # uses LEVR_TOKEN env var, default team`
|
|
141
|
+
},
|
|
142
|
+
parameters: {
|
|
143
|
+
positional: {
|
|
144
|
+
kind: "tuple",
|
|
145
|
+
parameters: [{
|
|
146
|
+
parse: String,
|
|
147
|
+
brief: "Path to test result file (.xml, .feature, .json)",
|
|
148
|
+
placeholder: "file",
|
|
149
|
+
optional: false
|
|
150
|
+
}]
|
|
151
|
+
},
|
|
152
|
+
flags: {
|
|
153
|
+
"workspace-id": {
|
|
154
|
+
kind: "parsed",
|
|
155
|
+
parse: String,
|
|
156
|
+
brief: "Workspace ID (required for multi-workspace JWT auth)",
|
|
157
|
+
placeholder: "uuid",
|
|
158
|
+
optional: true
|
|
159
|
+
},
|
|
160
|
+
"team-id": {
|
|
161
|
+
kind: "parsed",
|
|
162
|
+
parse: String,
|
|
163
|
+
brief: "Team ID (optional; server resolves default if omitted)",
|
|
164
|
+
placeholder: "uuid",
|
|
165
|
+
optional: true
|
|
166
|
+
},
|
|
167
|
+
source: {
|
|
168
|
+
kind: "parsed",
|
|
169
|
+
parse: String,
|
|
170
|
+
brief: "Automation source name (auto-detected in CI)",
|
|
171
|
+
placeholder: "name",
|
|
172
|
+
optional: true
|
|
173
|
+
},
|
|
174
|
+
"automation-source": {
|
|
175
|
+
kind: "parsed",
|
|
176
|
+
parse: String,
|
|
177
|
+
brief: "Automation source UUID. When set, routes to POST /v1/automation-run/ingest (synchronous, bypasses ImportJob queue) instead of POST /v1/imports.",
|
|
178
|
+
placeholder: "uuid",
|
|
179
|
+
optional: true
|
|
180
|
+
},
|
|
181
|
+
"run-name": {
|
|
182
|
+
kind: "parsed",
|
|
183
|
+
parse: String,
|
|
184
|
+
brief: "Name for the test run",
|
|
185
|
+
placeholder: "name",
|
|
186
|
+
optional: true
|
|
187
|
+
},
|
|
188
|
+
format: {
|
|
189
|
+
kind: "enum",
|
|
190
|
+
values: [
|
|
191
|
+
"junit",
|
|
192
|
+
"gherkin",
|
|
193
|
+
"cucumber-json"
|
|
194
|
+
],
|
|
195
|
+
brief: "File format (auto-detected if omitted)",
|
|
196
|
+
optional: true
|
|
197
|
+
},
|
|
198
|
+
"parent-folder-id": {
|
|
199
|
+
kind: "parsed",
|
|
200
|
+
parse: String,
|
|
201
|
+
brief: "Destination folder ID",
|
|
202
|
+
placeholder: "uuid",
|
|
203
|
+
optional: true
|
|
204
|
+
},
|
|
205
|
+
"update-mode": {
|
|
206
|
+
kind: "enum",
|
|
207
|
+
values: ["update", "create_new"],
|
|
208
|
+
default: "update",
|
|
209
|
+
brief: "How to handle existing tests"
|
|
210
|
+
},
|
|
211
|
+
verbose: {
|
|
212
|
+
kind: "boolean",
|
|
213
|
+
default: false,
|
|
214
|
+
brief: "Show detailed output"
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
aliases: {
|
|
218
|
+
w: "workspace-id",
|
|
219
|
+
t: "team-id",
|
|
220
|
+
s: "source",
|
|
221
|
+
a: "automation-source",
|
|
222
|
+
r: "run-name",
|
|
223
|
+
f: "format",
|
|
224
|
+
v: "verbose"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
loader: async () => {
|
|
228
|
+
const { pushHandler } = await import("./pushHandler-DkCrbV7y.js");
|
|
229
|
+
return pushHandler;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/commands/workspace/list.ts
|
|
235
|
+
const listCommand = buildCommand({
|
|
236
|
+
docs: {
|
|
237
|
+
brief: "List available workspaces",
|
|
238
|
+
fullDescription: `List all workspaces you have access to.
|
|
239
|
+
|
|
240
|
+
The current workspace (if selected) is marked with an asterisk (*).
|
|
241
|
+
|
|
242
|
+
Requires JWT authentication (levr auth login).
|
|
243
|
+
|
|
244
|
+
Examples:
|
|
245
|
+
levr workspace list`
|
|
246
|
+
},
|
|
247
|
+
parameters: {},
|
|
248
|
+
loader: async () => {
|
|
249
|
+
const { listHandler } = await import("./listHandler-BiwIo88i.js");
|
|
250
|
+
return listHandler;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/commands/workspace/select.ts
|
|
256
|
+
const selectCommand = buildCommand({
|
|
257
|
+
docs: {
|
|
258
|
+
brief: "Select a workspace",
|
|
259
|
+
fullDescription: `Select a workspace by ID.
|
|
260
|
+
|
|
261
|
+
The selected workspace is used for all subsequent commands.
|
|
262
|
+
Use 'levr workspace list' to see available workspaces.
|
|
263
|
+
|
|
264
|
+
Requires JWT authentication (levr auth login).
|
|
265
|
+
|
|
266
|
+
Examples:
|
|
267
|
+
levr workspace select <workspace-id>`
|
|
268
|
+
},
|
|
269
|
+
parameters: {
|
|
270
|
+
positional: {
|
|
271
|
+
kind: "tuple",
|
|
272
|
+
parameters: [{
|
|
273
|
+
parse: String,
|
|
274
|
+
brief: "Workspace ID",
|
|
275
|
+
placeholder: "workspace-id",
|
|
276
|
+
optional: false
|
|
277
|
+
}]
|
|
278
|
+
},
|
|
279
|
+
flags: {}
|
|
280
|
+
},
|
|
281
|
+
loader: async () => {
|
|
282
|
+
const { selectHandler } = await import("./selectHandler-C2UDAWOl.js");
|
|
283
|
+
return selectHandler;
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/commands/workspace/current.ts
|
|
289
|
+
const currentCommand = buildCommand({
|
|
290
|
+
docs: {
|
|
291
|
+
brief: "Show current workspace",
|
|
292
|
+
fullDescription: `Show the currently selected workspace.
|
|
293
|
+
|
|
294
|
+
Examples:
|
|
295
|
+
levr workspace current`
|
|
296
|
+
},
|
|
297
|
+
parameters: {},
|
|
298
|
+
loader: async () => {
|
|
299
|
+
const { currentHandler } = await import("./currentHandler-oXFeNNgd.js");
|
|
300
|
+
return currentHandler;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/app.ts
|
|
306
|
+
const routes = buildRouteMap({
|
|
307
|
+
routes: {
|
|
308
|
+
auth: buildRouteMap({
|
|
309
|
+
routes: {
|
|
310
|
+
login: loginCommand,
|
|
311
|
+
logout: logoutCommand,
|
|
312
|
+
status: statusCommand
|
|
313
|
+
},
|
|
314
|
+
docs: { brief: "Manage authentication" }
|
|
315
|
+
}),
|
|
316
|
+
workspace: buildRouteMap({
|
|
317
|
+
routes: {
|
|
318
|
+
list: listCommand,
|
|
319
|
+
select: selectCommand,
|
|
320
|
+
current: currentCommand
|
|
321
|
+
},
|
|
322
|
+
docs: { brief: "Manage workspace selection" }
|
|
323
|
+
}),
|
|
324
|
+
push: pushCommand,
|
|
325
|
+
install: buildInstallCommand("levr", { bash: "__levr_bash_complete" }),
|
|
326
|
+
uninstall: buildUninstallCommand("levr", { bash: true })
|
|
327
|
+
},
|
|
328
|
+
docs: {
|
|
329
|
+
brief: "The command-line interface for Levr",
|
|
330
|
+
hideRoute: {
|
|
331
|
+
install: true,
|
|
332
|
+
uninstall: true
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
const app = buildApplication(routes, {
|
|
337
|
+
name: "levr",
|
|
338
|
+
versionInfo: { currentVersion: "0.1.0" },
|
|
339
|
+
localization: { loadText: () => ({
|
|
340
|
+
...text_en,
|
|
341
|
+
exceptionWhileParsingArguments: (exc, ansiColor) => {
|
|
342
|
+
const base = text_en.exceptionWhileParsingArguments(exc, ansiColor);
|
|
343
|
+
const hint = "Run `levr <command> --help` for usage information.";
|
|
344
|
+
return ansiColor ? `${base}\n\x1b[2m${hint}\x1b[22m` : `${base}\n${hint}`;
|
|
345
|
+
}
|
|
346
|
+
}) }
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
//#endregion
|
|
350
|
+
export { app, buildContext };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { app, buildContext } from "./app-0F0Zyyt7.js";
|
|
3
|
+
import { proposeCompletions } from "@stricli/core";
|
|
4
|
+
|
|
5
|
+
//#region src/bin/bash-complete.ts
|
|
6
|
+
const inputs = process.argv.slice(3);
|
|
7
|
+
if (process.env["COMP_LINE"]?.endsWith(" ")) inputs.push("");
|
|
8
|
+
try {
|
|
9
|
+
for (const { completion } of await proposeCompletions(app, inputs, buildContext(process))) process.stdout.write(`${completion}\n`);
|
|
10
|
+
} catch {}
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { app, buildContext } from "./app-0F0Zyyt7.js";
|
|
3
|
+
import { run } from "@stricli/core";
|
|
4
|
+
|
|
5
|
+
//#region src/bin/cli.ts
|
|
6
|
+
let savedExitCode;
|
|
7
|
+
const processProxy = new Proxy(process, { set(target, prop, value) {
|
|
8
|
+
if (prop === "exitCode" && typeof value === "number" && value !== 0) savedExitCode = value;
|
|
9
|
+
return Reflect.set(target, prop, value);
|
|
10
|
+
} });
|
|
11
|
+
await run(app, process.argv.slice(2), buildContext(processProxy));
|
|
12
|
+
if (savedExitCode !== void 0) process.exitCode = savedExitCode;
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/env.ts
|
|
6
|
+
const DEFAULT_API_URL = "https://api.levr.one";
|
|
7
|
+
const DEFAULT_AUTH_URL = "https://auth.levr.one";
|
|
8
|
+
/** OAuth client ID for the CLI (seeded in oauth_clients table) */
|
|
9
|
+
const CLI_CLIENT_ID = "3";
|
|
10
|
+
function getApiUrl() {
|
|
11
|
+
return process.env["LEVR_URL"] || DEFAULT_API_URL;
|
|
12
|
+
}
|
|
13
|
+
function getAuthUrl() {
|
|
14
|
+
return process.env["LEVR_AUTH_URL"] || DEFAULT_AUTH_URL;
|
|
15
|
+
}
|
|
16
|
+
function getTeamId(flagValue) {
|
|
17
|
+
return flagValue || process.env["LEVR_TEAM_ID"] || void 0;
|
|
18
|
+
}
|
|
19
|
+
function getPatToken() {
|
|
20
|
+
return process.env["LEVR_TOKEN"];
|
|
21
|
+
}
|
|
22
|
+
function getSourceOverride() {
|
|
23
|
+
return process.env["LEVR_SOURCE"];
|
|
24
|
+
}
|
|
25
|
+
function getAutomationSourceIdOverride() {
|
|
26
|
+
return process.env["LEVR_AUTOMATION_SOURCE_ID"];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/auth/credentials.ts
|
|
31
|
+
const CREDENTIALS_PATH = join(join(homedir(), ".config", "levr"), "credentials.json");
|
|
32
|
+
function readCredentials() {
|
|
33
|
+
try {
|
|
34
|
+
const raw = readFileSync(CREDENTIALS_PATH, "utf8");
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function writeCredentials(creds) {
|
|
41
|
+
mkdirSync(dirname(CREDENTIALS_PATH), { recursive: true });
|
|
42
|
+
writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 384 });
|
|
43
|
+
}
|
|
44
|
+
function deleteCredentials() {
|
|
45
|
+
try {
|
|
46
|
+
unlinkSync(CREDENTIALS_PATH);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { CLI_CLIENT_ID, deleteCredentials, getApiUrl, getAuthUrl, getAutomationSourceIdOverride, getPatToken, getSourceOverride, getTeamId, readCredentials, writeCredentials };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { loadWorkspace } from "./workspace-store-BcyMJAht.js";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/workspace/currentHandler.ts
|
|
4
|
+
function currentHandler() {
|
|
5
|
+
const workspaceId = loadWorkspace();
|
|
6
|
+
if (workspaceId) this.process.stdout.write(`Current workspace: ${workspaceId}\n`);
|
|
7
|
+
else this.process.stdout.write("No workspace selected.\n");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
export { currentHandler };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import "./credentials-CfHLkU7k.js";
|
|
2
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-DunBmYLR.js";
|
|
3
|
+
import { loadWorkspace } from "./workspace-store-BcyMJAht.js";
|
|
4
|
+
import "./token-refresh-waF23pyw.js";
|
|
5
|
+
import { resolveToken } from "./resolve-token-BL8vL_ok.js";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/workspace/listHandler.ts
|
|
8
|
+
async function listHandler() {
|
|
9
|
+
let auth;
|
|
10
|
+
try {
|
|
11
|
+
auth = await resolveToken();
|
|
12
|
+
} catch {
|
|
13
|
+
this.logger.error("Not authenticated. Run 'levr auth login' first.");
|
|
14
|
+
this.process.exitCode = 1;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (auth.type === "pat") {
|
|
18
|
+
this.logger.error("Workspace listing requires JWT auth. Run 'levr auth login'.");
|
|
19
|
+
this.process.exitCode = 1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
configureClient(auth);
|
|
23
|
+
const result = await authGetSitesV1();
|
|
24
|
+
if (result.error) {
|
|
25
|
+
this.logger.error("Failed to list workspaces.");
|
|
26
|
+
this.process.exitCode = 1;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const sites = result.data.sites;
|
|
30
|
+
if (sites.length === 0) {
|
|
31
|
+
this.logger.info("No workspaces available.");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const currentWs = loadWorkspace();
|
|
35
|
+
this.process.stdout.write("\nWorkspaces:\n\n");
|
|
36
|
+
for (const site of sites) {
|
|
37
|
+
const indicator = site.workspace_id === currentWs ? " *" : "";
|
|
38
|
+
this.process.stdout.write(` ${site.workspace_name} (${site.workspace_id}) [${site.role}]${indicator}\n`);
|
|
39
|
+
}
|
|
40
|
+
this.process.stdout.write("\n");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { listHandler };
|