@laioutr/cli 0.3.2 → 0.4.1

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 (44) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +168 -25
  3. package/dist/api/deploy.d.ts +66 -0
  4. package/dist/api/deploy.js +34 -0
  5. package/dist/api/fetchRc.d.ts +1 -0
  6. package/dist/api/fetchRc.js +12 -28
  7. package/dist/api/http.d.ts +30 -0
  8. package/dist/api/http.js +93 -0
  9. package/dist/api/releaseAppVersion.d.ts +1 -0
  10. package/dist/api/releaseAppVersion.js +9 -20
  11. package/dist/commands/app/release.d.ts +2 -1
  12. package/dist/commands/app/release.js +2 -1
  13. package/dist/commands/deploy/list.d.ts +32 -0
  14. package/dist/commands/deploy/list.js +55 -0
  15. package/dist/commands/deploy/logs.d.ts +23 -0
  16. package/dist/commands/deploy/logs.js +54 -0
  17. package/dist/commands/deploy/status.d.ts +31 -0
  18. package/dist/commands/deploy/status.js +41 -0
  19. package/dist/commands/deploy/trigger.d.ts +24 -0
  20. package/dist/commands/deploy/trigger.js +130 -0
  21. package/dist/commands/rc/fetch.d.ts +4 -3
  22. package/dist/commands/rc/fetch.js +24 -3
  23. package/dist/commands/rc/update.d.ts +2 -1
  24. package/dist/commands/rc/update.js +2 -1
  25. package/dist/deploy/attribution.d.ts +12 -0
  26. package/dist/deploy/attribution.js +16 -0
  27. package/dist/deploy/log-renderer.d.ts +13 -0
  28. package/dist/deploy/log-renderer.js +21 -0
  29. package/dist/deploy/polling.d.ts +4 -0
  30. package/dist/deploy/polling.js +12 -0
  31. package/dist/deploy/validation.d.ts +81 -0
  32. package/dist/deploy/validation.js +47 -0
  33. package/dist/deploy-base-command.d.ts +12 -0
  34. package/dist/deploy-base-command.js +27 -0
  35. package/dist/format/color.d.ts +5 -0
  36. package/dist/format/color.js +10 -0
  37. package/dist/format/detail.d.ts +19 -0
  38. package/dist/format/detail.js +18 -0
  39. package/dist/laioutr-base-command.d.ts +9 -1
  40. package/dist/laioutr-base-command.js +21 -3
  41. package/dist/project.d.ts +14 -0
  42. package/dist/project.js +16 -0
  43. package/oclif.manifest.json +443 -22
  44. package/package.json +4 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @laioutr/cli
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - b62dff8: Follow CLI output conventions for deployment logs, add actionable timeout-aware HTTP errors and a versioned API user agent, and introduce kebab-case flags with deprecated camelCase aliases.
8
+
9
+ ## 0.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - e5eb46a: Add a `deploy` command group for managing project deployments from the CLI. Every command targets a project as `--project org/project` and authenticates with an organization API key (`--key`, or the `LAIOUTR_API_KEY` env var).
14
+
15
+ - `laioutr deploy trigger` — trigger a production or named-preview deployment (`--preview <name>`). Waits for completion by default (`--no-wait` to return immediately) and can stream build logs with `--logs`.
16
+ - `laioutr deploy status` — show a deployment's status, environment, URL, and who created it.
17
+ - `laioutr deploy list` — list recent deployments for a project (`--limit`, default 20, max 100).
18
+ - `laioutr deploy logs` — fetch and follow a deployment's build logs.
19
+
3
20
  ## 0.3.1
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @laioutr/cli
20
20
  $ laioutr COMMAND
21
21
  running command...
22
22
  $ laioutr (--version)
23
- @laioutr/cli/0.3.2 darwin-arm64 node-v22.22.2
23
+ @laioutr/cli/0.4.1 linux-x64 node-v24.18.0
24
24
  $ laioutr --help [COMMAND]
25
25
  USAGE
26
26
  $ laioutr COMMAND
@@ -32,6 +32,10 @@ USAGE
32
32
 
33
33
  <!-- commands -->
34
34
  * [`laioutr app release`](#laioutr-app-release)
35
+ * [`laioutr deploy list`](#laioutr-deploy-list)
36
+ * [`laioutr deploy logs`](#laioutr-deploy-logs)
37
+ * [`laioutr deploy status`](#laioutr-deploy-status)
38
+ * [`laioutr deploy trigger`](#laioutr-deploy-trigger)
35
39
  * [`laioutr help [COMMAND]`](#laioutr-help-command)
36
40
  * [`laioutr plugins`](#laioutr-plugins)
37
41
  * [`laioutr plugins add PLUGIN`](#laioutr-plugins-add-plugin)
@@ -53,14 +57,16 @@ Publish an app version to the Laioutr platform.
53
57
 
54
58
  ```
55
59
  USAGE
56
- $ laioutr app release --cockpitApiHost <value> --cwd <value> -k <value> [-c stable|testing]
60
+ $ laioutr app release --cockpit-api-host <value> --cwd <value> -k <value> [--no-color] [-c stable|testing]
57
61
 
58
62
  FLAGS
59
- -c, --channel=<option> Release channel (auto-detected from version if omitted)
60
- <options: stable|testing>
61
- -k, --key=<value> (required) API key for authentication
62
- --cockpitApiHost=<value> (required) [default: https://cockpit.laioutr.cloud] cockpit api host
63
- --cwd=<value> (required) [default: /Users/sl/src/laioutr/apps/cli] current working directory
63
+ -c, --channel=<option> Release channel (auto-detected from version if omitted)
64
+ <options: stable|testing>
65
+ -k, --key=<value> (required) API key for authentication
66
+ --cockpit-api-host=<value> (required) [default: https://cockpit.laioutr.cloud] Cockpit API host
67
+ --cwd=<value> (required) [default: /home/runner/work/laioutr/laioutr/apps/cli] Current working
68
+ directory
69
+ --no-color Disable color in output
64
70
 
65
71
  DESCRIPTION
66
72
  Publish an app version to the Laioutr platform.
@@ -73,7 +79,137 @@ EXAMPLES
73
79
  LAIOUTR_API_KEY=orgKey_xxx laioutr app release
74
80
  ```
75
81
 
76
- _See code: [src/commands/app/release.ts](https://github.com/laioutr/laioutr/blob/v0.3.2/src/commands/app/release.ts)_
82
+ _See code: [src/commands/app/release.ts](https://github.com/laioutr/laioutr/blob/v0.4.1/src/commands/app/release.ts)_
83
+
84
+ ## `laioutr deploy list`
85
+
86
+ List recent deployments for a project.
87
+
88
+ ```
89
+ USAGE
90
+ $ laioutr deploy list --cockpit-api-host <value> --cwd <value> -p <value> -k <value> [--json] [--no-color]
91
+ [--limit <value>]
92
+
93
+ FLAGS
94
+ -k, --key=<value> (required) Organization API key with project:deploy scope
95
+ -p, --project=<value> (required) Project identifier in org/project format
96
+ --cockpit-api-host=<value> (required) [default: https://cockpit.laioutr.cloud] Cockpit API host
97
+ --cwd=<value> (required) [default: /home/runner/work/laioutr/laioutr/apps/cli] Current working
98
+ directory
99
+ --limit=<value> [default: 20] Maximum number of deployments to list (1-100)
100
+ --no-color Disable color in output
101
+
102
+ GLOBAL FLAGS
103
+ --json Format output as json.
104
+
105
+ DESCRIPTION
106
+ List recent deployments for a project.
107
+
108
+ EXAMPLES
109
+ $ laioutr deploy list --project org/project --key orgKey_xxx
110
+
111
+ $ laioutr deploy list --project org/project --key orgKey_xxx --limit 50
112
+ ```
113
+
114
+ _See code: [src/commands/deploy/list.ts](https://github.com/laioutr/laioutr/blob/v0.4.1/src/commands/deploy/list.ts)_
115
+
116
+ ## `laioutr deploy logs`
117
+
118
+ Fetch and display build logs for a deployment.
119
+
120
+ ```
121
+ USAGE
122
+ $ laioutr deploy logs --cockpit-api-host <value> --cwd <value> -p <value> -k <value> --deployment-id <value>
123
+ [--json] [--no-color]
124
+
125
+ FLAGS
126
+ -k, --key=<value> (required) Organization API key with project:deploy scope
127
+ -p, --project=<value> (required) Project identifier in org/project format
128
+ --cockpit-api-host=<value> (required) [default: https://cockpit.laioutr.cloud] Cockpit API host
129
+ --cwd=<value> (required) [default: /home/runner/work/laioutr/laioutr/apps/cli] Current working
130
+ directory
131
+ --deployment-id=<value> (required) Deployment ID
132
+ --no-color Disable color in output
133
+
134
+ GLOBAL FLAGS
135
+ --json Format output as json.
136
+
137
+ DESCRIPTION
138
+ Fetch and display build logs for a deployment.
139
+
140
+ EXAMPLES
141
+ $ laioutr deploy logs --project org/project --key orgKey_xxx --deployment-id dep_abc123
142
+ ```
143
+
144
+ _See code: [src/commands/deploy/logs.ts](https://github.com/laioutr/laioutr/blob/v0.4.1/src/commands/deploy/logs.ts)_
145
+
146
+ ## `laioutr deploy status`
147
+
148
+ Show status of a deployment.
149
+
150
+ ```
151
+ USAGE
152
+ $ laioutr deploy status --cockpit-api-host <value> --cwd <value> -p <value> -k <value> --deployment-id <value>
153
+ [--json] [--no-color]
154
+
155
+ FLAGS
156
+ -k, --key=<value> (required) Organization API key with project:deploy scope
157
+ -p, --project=<value> (required) Project identifier in org/project format
158
+ --cockpit-api-host=<value> (required) [default: https://cockpit.laioutr.cloud] Cockpit API host
159
+ --cwd=<value> (required) [default: /home/runner/work/laioutr/laioutr/apps/cli] Current working
160
+ directory
161
+ --deployment-id=<value> (required) Deployment ID
162
+ --no-color Disable color in output
163
+
164
+ GLOBAL FLAGS
165
+ --json Format output as json.
166
+
167
+ DESCRIPTION
168
+ Show status of a deployment.
169
+
170
+ EXAMPLES
171
+ $ laioutr deploy status --project org/project --key orgKey_xxx --deployment-id dep_abc123
172
+ ```
173
+
174
+ _See code: [src/commands/deploy/status.ts](https://github.com/laioutr/laioutr/blob/v0.4.1/src/commands/deploy/status.ts)_
175
+
176
+ ## `laioutr deploy trigger`
177
+
178
+ Trigger a project deployment.
179
+
180
+ ```
181
+ USAGE
182
+ $ laioutr deploy trigger --cockpit-api-host <value> --cwd <value> -p <value> -k <value> [--json] [--no-color]
183
+ [--wait] [--logs] [--preview <value>]
184
+
185
+ FLAGS
186
+ -k, --key=<value> (required) Organization API key with project:deploy scope
187
+ -p, --project=<value> (required) Project identifier in org/project format
188
+ --cockpit-api-host=<value> (required) [default: https://cockpit.laioutr.cloud] Cockpit API host
189
+ --cwd=<value> (required) [default: /home/runner/work/laioutr/laioutr/apps/cli] Current working
190
+ directory
191
+ --logs Stream build log output instead of spinner
192
+ --no-color Disable color in output
193
+ --preview=<value> Create a preview deployment with optional name
194
+ --[no-]wait Wait for deployment to complete
195
+
196
+ GLOBAL FLAGS
197
+ --json Format output as json.
198
+
199
+ DESCRIPTION
200
+ Trigger a project deployment.
201
+
202
+ EXAMPLES
203
+ $ laioutr deploy trigger --project org/project --key orgKey_xxx
204
+
205
+ $ laioutr deploy trigger --project org/project --key orgKey_xxx --no-wait
206
+
207
+ $ laioutr deploy trigger --project org/project --key orgKey_xxx --logs
208
+
209
+ $ laioutr deploy trigger --project org/project --key orgKey_xxx --preview my-feature
210
+ ```
211
+
212
+ _See code: [src/commands/deploy/trigger.ts](https://github.com/laioutr/laioutr/blob/v0.4.1/src/commands/deploy/trigger.ts)_
77
213
 
78
214
  ## `laioutr help [COMMAND]`
79
215
 
@@ -391,14 +527,17 @@ Fetches the laioutrrc.json of a project from the cockpit api.
391
527
 
392
528
  ```
393
529
  USAGE
394
- $ laioutr project fetch-rc [FILENAME] --cockpitApiHost <value> --cwd <value> -e <value> -s <value> -p <value>
530
+ $ laioutr project fetch-rc [FILENAME] --cockpit-api-host <value> --cwd <value> -e <value> -s <value> -p <value>
531
+ [--no-color]
395
532
 
396
533
  FLAGS
397
- -e, --environmentName=<value> (required) [default: main] environment name
398
- -p, --project=<value> (required) <organization slug>/<project slug>
399
- -s, --projectSecret=<value> (required) project secret
400
- --cockpitApiHost=<value> (required) [default: https://cockpit.laioutr.cloud] cockpit api host
401
- --cwd=<value> (required) [default: /Users/sl/src/laioutr/apps/cli] current working directory
534
+ -e, --environment-name=<value> (required) [default: main] Environment name
535
+ -p, --project=<value> (required) <organization slug>/<project slug>
536
+ -s, --project-secret=<value> (required) Project secret
537
+ --cockpit-api-host=<value> (required) [default: https://cockpit.laioutr.cloud] Cockpit API host
538
+ --cwd=<value> (required) [default: /home/runner/work/laioutr/laioutr/apps/cli] Current working
539
+ directory
540
+ --no-color Disable color in output
402
541
 
403
542
  DESCRIPTION
404
543
  Fetches the laioutrrc.json of a project from the cockpit api.
@@ -416,14 +555,17 @@ Fetches the laioutrrc.json of a project from the cockpit api.
416
555
 
417
556
  ```
418
557
  USAGE
419
- $ laioutr rc fetch [FILENAME] --cockpitApiHost <value> --cwd <value> -e <value> -s <value> -p <value>
558
+ $ laioutr rc fetch [FILENAME] --cockpit-api-host <value> --cwd <value> -e <value> -s <value> -p <value>
559
+ [--no-color]
420
560
 
421
561
  FLAGS
422
- -e, --environmentName=<value> (required) [default: main] environment name
423
- -p, --project=<value> (required) <organization slug>/<project slug>
424
- -s, --projectSecret=<value> (required) project secret
425
- --cockpitApiHost=<value> (required) [default: https://cockpit.laioutr.cloud] cockpit api host
426
- --cwd=<value> (required) [default: /Users/sl/src/laioutr/apps/cli] current working directory
562
+ -e, --environment-name=<value> (required) [default: main] Environment name
563
+ -p, --project=<value> (required) <organization slug>/<project slug>
564
+ -s, --project-secret=<value> (required) Project secret
565
+ --cockpit-api-host=<value> (required) [default: https://cockpit.laioutr.cloud] Cockpit API host
566
+ --cwd=<value> (required) [default: /home/runner/work/laioutr/laioutr/apps/cli] Current working
567
+ directory
568
+ --no-color Disable color in output
427
569
 
428
570
  DESCRIPTION
429
571
  Fetches the laioutrrc.json of a project from the cockpit api.
@@ -435,7 +577,7 @@ EXAMPLES
435
577
  $ laioutr rc fetch
436
578
  ```
437
579
 
438
- _See code: [src/commands/rc/fetch.ts](https://github.com/laioutr/laioutr/blob/v0.3.2/src/commands/rc/fetch.ts)_
580
+ _See code: [src/commands/rc/fetch.ts](https://github.com/laioutr/laioutr/blob/v0.4.1/src/commands/rc/fetch.ts)_
439
581
 
440
582
  ## `laioutr rc update [FILENAME]`
441
583
 
@@ -443,11 +585,12 @@ Updates an existing laioutrrc.json file with the latest from the cockpit api.
443
585
 
444
586
  ```
445
587
  USAGE
446
- $ laioutr rc update [FILENAME] --cockpitApiHost <value> --cwd <value>
588
+ $ laioutr rc update [FILENAME] --cockpit-api-host <value> --cwd <value> [--no-color]
447
589
 
448
590
  FLAGS
449
- --cockpitApiHost=<value> (required) [default: https://cockpit.laioutr.cloud] cockpit api host
450
- --cwd=<value> (required) [default: /Users/sl/src/laioutr/apps/cli] current working directory
591
+ --cockpit-api-host=<value> (required) [default: https://cockpit.laioutr.cloud] Cockpit API host
592
+ --cwd=<value> (required) [default: /home/runner/work/laioutr/laioutr/apps/cli] Current working directory
593
+ --no-color Disable color in output
451
594
 
452
595
  DESCRIPTION
453
596
  Updates an existing laioutrrc.json file with the latest from the cockpit api.
@@ -456,5 +599,5 @@ EXAMPLES
456
599
  $ laioutr rc update
457
600
  ```
458
601
 
459
- _See code: [src/commands/rc/update.ts](https://github.com/laioutr/laioutr/blob/v0.3.2/src/commands/rc/update.ts)_
602
+ _See code: [src/commands/rc/update.ts](https://github.com/laioutr/laioutr/blob/v0.4.1/src/commands/rc/update.ts)_
460
603
  <!-- commandsstop -->
@@ -0,0 +1,66 @@
1
+ export interface DeployApiOptions {
2
+ cockpitApiHost: string;
3
+ apiKey: string;
4
+ organizationSlug: string;
5
+ projectSlug: string;
6
+ userAgent: string;
7
+ }
8
+ export interface TriggerOptions extends DeployApiOptions {
9
+ previewName?: string;
10
+ }
11
+ export declare function triggerDeployment(options: TriggerOptions): Promise<{
12
+ deploymentId: string;
13
+ status: string;
14
+ environment: string;
15
+ previewName: string | null;
16
+ }>;
17
+ export declare function fetchDeploymentStatus(options: DeployApiOptions & {
18
+ deploymentId: string;
19
+ }): Promise<{
20
+ deploymentId: string;
21
+ status: string;
22
+ environment: string;
23
+ previewName: string | null;
24
+ url: string | null;
25
+ lastError: string | null;
26
+ createdAt: string;
27
+ stoppedAt: string | null;
28
+ attribution: {
29
+ principalType: "user" | "api-key" | "system";
30
+ displayName: string;
31
+ email: string | null;
32
+ agentName: string | null;
33
+ redacted: boolean;
34
+ } | null;
35
+ }>;
36
+ export declare function fetchDeployments(options: DeployApiOptions & {
37
+ limit?: number;
38
+ }): Promise<{
39
+ deployments: {
40
+ deploymentId: string;
41
+ status: string;
42
+ environment: string;
43
+ previewName: string | null;
44
+ url: string | null;
45
+ createdAt: string;
46
+ stoppedAt: string | null;
47
+ attribution: {
48
+ principalType: "user" | "api-key" | "system";
49
+ displayName: string;
50
+ email: string | null;
51
+ agentName: string | null;
52
+ redacted: boolean;
53
+ } | null;
54
+ }[];
55
+ }>;
56
+ export declare function fetchDeploymentLogs(options: DeployApiOptions & {
57
+ deploymentId: string;
58
+ }): Promise<{
59
+ lines: {
60
+ text: string;
61
+ level?: "error" | "warning" | null | undefined;
62
+ timestamp?: number | null | undefined;
63
+ }[] | null;
64
+ supported: boolean;
65
+ deploymentStatus: string;
66
+ }>;
@@ -0,0 +1,34 @@
1
+ import { postJson } from './http.js';
2
+ import { deploymentLogsResponseSchema, deploymentsListResponseSchema, deploymentStatusResponseSchema, deployTriggerResponseSchema, } from '../deploy/validation.js';
3
+ export async function triggerDeployment(options) {
4
+ const data = await postJson(`${options.cockpitApiHost}/api/v1/project/deploy`, {
5
+ organizationSlug: options.organizationSlug,
6
+ projectSlug: options.projectSlug,
7
+ previewName: options.previewName,
8
+ }, { apiKey: options.apiKey, userAgent: options.userAgent });
9
+ return deployTriggerResponseSchema.parse(data);
10
+ }
11
+ export async function fetchDeploymentStatus(options) {
12
+ const data = await postJson(`${options.cockpitApiHost}/api/v1/project/deployment-status`, {
13
+ organizationSlug: options.organizationSlug,
14
+ projectSlug: options.projectSlug,
15
+ deploymentId: options.deploymentId,
16
+ }, { apiKey: options.apiKey, userAgent: options.userAgent });
17
+ return deploymentStatusResponseSchema.parse(data);
18
+ }
19
+ export async function fetchDeployments(options) {
20
+ const data = await postJson(`${options.cockpitApiHost}/api/v1/project/deployments`, {
21
+ organizationSlug: options.organizationSlug,
22
+ projectSlug: options.projectSlug,
23
+ limit: options.limit,
24
+ }, { apiKey: options.apiKey, userAgent: options.userAgent });
25
+ return deploymentsListResponseSchema.parse(data);
26
+ }
27
+ export async function fetchDeploymentLogs(options) {
28
+ const data = await postJson(`${options.cockpitApiHost}/api/v1/project/deployment-logs`, {
29
+ organizationSlug: options.organizationSlug,
30
+ projectSlug: options.projectSlug,
31
+ deploymentId: options.deploymentId,
32
+ }, { apiKey: options.apiKey, userAgent: options.userAgent });
33
+ return deploymentLogsResponseSchema.parse(data);
34
+ }
@@ -1,5 +1,6 @@
1
1
  interface HostApiOptions {
2
2
  cockpitApiHost: string;
3
+ userAgent: string;
3
4
  }
4
5
  export interface ProjectApiOptions extends HostApiOptions {
5
6
  project: string;
@@ -1,35 +1,19 @@
1
- /* eslint-disable no-console */
2
1
  import { z } from 'zod/v4';
2
+ import { postJson } from './http.js';
3
+ import { parseProjectSlug } from '../project.js';
3
4
  const apiResponseSchema = z.object({
4
5
  content: z.string(),
5
6
  });
6
7
  export const fetchRc = async (flags) => {
7
- const url = `${flags.cockpitApiHost}/api/v1/project/fetch-rc`;
8
- const [organizationSlug, projectSlug] = flags.project.replace('/p/', '').split('/');
9
- const response = await fetch(url, {
10
- body: JSON.stringify({
11
- environmentName: flags.environmentName,
12
- organizationSlug,
13
- projectSecret: flags.projectSecret,
14
- projectSlug,
15
- }),
16
- headers: {
17
- 'Content-Type': 'application/json',
18
- },
19
- method: 'POST',
20
- });
21
- const body = await response.text();
22
- let data = null;
23
- if (response.status !== 200) {
24
- throw new Error(`Failed to fetch RC file. Status: ${response.status} ${response.statusText}. Body: ${body}`);
8
+ const parsed = parseProjectSlug(flags.project);
9
+ if (!parsed) {
10
+ throw new Error(`Invalid project "${flags.project}" — expected <organization slug>/<project slug>.`);
25
11
  }
26
- try {
27
- data = JSON.parse(body);
28
- }
29
- catch (error) {
30
- console.error(error, response);
31
- throw new Error('Failed to parse RC file. See error message above.');
32
- }
33
- const parsed = apiResponseSchema.parse(data);
34
- return parsed.content;
12
+ const data = await postJson(`${flags.cockpitApiHost}/api/v1/project/fetch-rc`, {
13
+ environmentName: flags.environmentName,
14
+ organizationSlug: parsed.organizationSlug,
15
+ projectSecret: flags.projectSecret,
16
+ projectSlug: parsed.projectSlug,
17
+ }, { userAgent: flags.userAgent });
18
+ return apiResponseSchema.parse(data).content;
35
19
  };
@@ -0,0 +1,30 @@
1
+ type HttpErrorCode = 'HTTP_INVALID_RESPONSE' | 'HTTP_NETWORK_ERROR' | 'HTTP_RESPONSE_ERROR' | 'HTTP_TIMEOUT';
2
+ interface HttpRequestErrorOptions {
3
+ cause?: unknown;
4
+ status?: number;
5
+ }
6
+ export declare class HttpRequestError extends Error {
7
+ readonly code: HttpErrorCode;
8
+ readonly status?: number;
9
+ constructor(code: HttpErrorCode, message: string, options?: HttpRequestErrorOptions);
10
+ }
11
+ export interface PostJsonOptions {
12
+ /** When set, sent as an `Authorization: Bearer <apiKey>` header. */
13
+ apiKey?: string;
14
+ /** Extra headers, merged over the defaults (`Content-Type` + auth). */
15
+ headers?: Record<string, string>;
16
+ /** Request timeout in milliseconds. */
17
+ timeoutMs?: number;
18
+ /** CLI identity sent as the HTTP User-Agent. */
19
+ userAgent?: string;
20
+ }
21
+ /**
22
+ * POSTs a JSON body to `url` and returns the parsed JSON response.
23
+ *
24
+ * Throws a safe, actionable `HttpRequestError` for connection failures,
25
+ * timeouts, non-success responses, and malformed JSON. Callers validate the
26
+ * returned shape themselves — typically with a zod schema — so this returns
27
+ * `unknown`.
28
+ */
29
+ export declare function postJson(url: string, body: unknown, options?: PostJsonOptions): Promise<unknown>;
30
+ export {};
@@ -0,0 +1,93 @@
1
+ const DEFAULT_TIMEOUT_MS = 30_000;
2
+ export class HttpRequestError extends Error {
3
+ code;
4
+ status;
5
+ constructor(code, message, options = {}) {
6
+ super(message, { cause: options.cause });
7
+ this.name = 'Error';
8
+ this.code = code;
9
+ this.status = options.status;
10
+ }
11
+ }
12
+ function apiOrigin(url) {
13
+ try {
14
+ return new URL(url).origin;
15
+ }
16
+ catch {
17
+ return 'the configured endpoint';
18
+ }
19
+ }
20
+ function timeoutError(url, timeoutMs, cause) {
21
+ return new HttpRequestError('HTTP_TIMEOUT', `Request to ${apiOrigin(url)} timed out after ${timeoutMs} ms. Check the network connection, then try again.`, { cause });
22
+ }
23
+ function responseError(response) {
24
+ const status = `${response.status}${response.statusText ? ` ${response.statusText}` : ''}`;
25
+ let message;
26
+ if (response.status === 401 || response.status === 403) {
27
+ message = `Laioutr API authentication failed (${status}). Check the API key and required permissions, then try again.`;
28
+ }
29
+ else if (response.status === 404) {
30
+ message = `The requested Laioutr API resource was not found (${status}). Check the API host and command identifiers, then try again.`;
31
+ }
32
+ else if (response.status === 429) {
33
+ message = `The Laioutr API rate limit was exceeded (${status}). Wait briefly, then try again.`;
34
+ }
35
+ else if (response.status >= 500) {
36
+ message = `The Laioutr API is currently unavailable (${status}). Try again later.`;
37
+ }
38
+ else {
39
+ message = `The Laioutr API rejected the request (${status}). Check the command input, then try again.`;
40
+ }
41
+ return new HttpRequestError('HTTP_RESPONSE_ERROR', message, { status: response.status });
42
+ }
43
+ /**
44
+ * POSTs a JSON body to `url` and returns the parsed JSON response.
45
+ *
46
+ * Throws a safe, actionable `HttpRequestError` for connection failures,
47
+ * timeouts, non-success responses, and malformed JSON. Callers validate the
48
+ * returned shape themselves — typically with a zod schema — so this returns
49
+ * `unknown`.
50
+ */
51
+ export async function postJson(url, body, options = {}) {
52
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
53
+ const signal = AbortSignal.timeout(timeoutMs);
54
+ let response;
55
+ try {
56
+ response = await fetch(url, {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ ...(options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {}),
61
+ ...(options.userAgent ? { 'User-Agent': options.userAgent } : {}),
62
+ ...options.headers,
63
+ },
64
+ body: JSON.stringify(body),
65
+ signal,
66
+ });
67
+ }
68
+ catch (error) {
69
+ if (signal.aborted) {
70
+ throw timeoutError(url, timeoutMs, error);
71
+ }
72
+ throw new HttpRequestError('HTTP_NETWORK_ERROR', `Unable to reach the Laioutr API at ${apiOrigin(url)}. Check the network connection and API host, then try again.`, { cause: error });
73
+ }
74
+ if (!response.ok) {
75
+ throw responseError(response);
76
+ }
77
+ let text;
78
+ try {
79
+ text = await response.text();
80
+ }
81
+ catch (error) {
82
+ if (signal.aborted) {
83
+ throw timeoutError(url, timeoutMs, error);
84
+ }
85
+ throw new HttpRequestError('HTTP_INVALID_RESPONSE', 'The Laioutr API response could not be read. Please try again; if the problem persists, contact Laioutr support.', { cause: error, status: response.status });
86
+ }
87
+ try {
88
+ return JSON.parse(text);
89
+ }
90
+ catch (error) {
91
+ throw new HttpRequestError('HTTP_INVALID_RESPONSE', 'The Laioutr API returned invalid JSON. Please try again; if the problem persists, contact Laioutr support.', { cause: error, status: response.status });
92
+ }
93
+ }
@@ -5,6 +5,7 @@ export interface ReleaseOptions {
5
5
  version: string;
6
6
  configSchema: Record<string, unknown>;
7
7
  peerDependencies: Record<string, string>;
8
+ userAgent: string;
8
9
  channel?: 'stable' | 'testing';
9
10
  }
10
11
  export declare const releaseAppVersion: (options: ReleaseOptions) => Promise<{
@@ -1,27 +1,16 @@
1
1
  import { z } from 'zod/v4';
2
+ import { postJson } from './http.js';
2
3
  const releaseResponseSchema = z.object({
3
4
  message: z.string(),
4
5
  versionId: z.string().optional(),
5
6
  });
6
7
  export const releaseAppVersion = async (options) => {
7
- const url = `${options.cockpitApiHost}/api/v1/app/release`;
8
- const response = await fetch(url, {
9
- method: 'POST',
10
- headers: {
11
- 'Content-Type': 'application/json',
12
- Authorization: `Bearer ${options.apiKey}`,
13
- },
14
- body: JSON.stringify({
15
- packageName: options.packageName,
16
- version: options.version,
17
- configSchema: options.configSchema,
18
- peerDependencies: options.peerDependencies,
19
- channel: options.channel,
20
- }),
21
- });
22
- const body = await response.text();
23
- if (response.status !== 200) {
24
- throw new Error(`Release failed (${response.status}): ${body}`);
25
- }
26
- return releaseResponseSchema.parse(JSON.parse(body));
8
+ const data = await postJson(`${options.cockpitApiHost}/api/v1/app/release`, {
9
+ packageName: options.packageName,
10
+ version: options.version,
11
+ configSchema: options.configSchema,
12
+ peerDependencies: options.peerDependencies,
13
+ channel: options.channel,
14
+ }, { apiKey: options.apiKey, userAgent: options.userAgent });
15
+ return releaseResponseSchema.parse(data);
27
16
  };
@@ -5,7 +5,8 @@ export default class AppRelease extends LaioutrBaseCommand {
5
5
  static flags: {
6
6
  key: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
7
  channel: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
- cockpitApiHost: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ 'cockpit-api-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
10
  cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
11
  };
11
12
  run(): Promise<void>;
@@ -50,12 +50,13 @@ export default class AppRelease extends LaioutrBaseCommand {
50
50
  this.log(`Publishing ${pkg.name}@${pkg.version}...`);
51
51
  // 5. Call the release API
52
52
  const result = await releaseAppVersion({
53
- cockpitApiHost: flags.cockpitApiHost,
53
+ cockpitApiHost: flags['cockpit-api-host'],
54
54
  apiKey: flags.key,
55
55
  packageName: pkg.name,
56
56
  version: pkg.version,
57
57
  configSchema,
58
58
  peerDependencies,
59
+ userAgent: this.config.userAgent,
59
60
  channel,
60
61
  });
61
62
  this.log(result.message);