@kora-platform/cli 0.8.0-rc3 → 0.8.0-rc6

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/format.d.ts CHANGED
@@ -10,6 +10,7 @@ export declare function renderJsonEnvelope(input: {
10
10
  meta?: Record<string, unknown>;
11
11
  }): string;
12
12
  export declare function renderProblemJson(input: {
13
+ code: string;
13
14
  detail: string;
14
15
  details?: Record<string, unknown>;
15
16
  instance: string;
package/dist/format.js CHANGED
@@ -7,12 +7,17 @@ export function renderJsonEnvelope(input) {
7
7
  }
8
8
  export function renderProblemJson(input) {
9
9
  return `${JSON.stringify({
10
- detail: input.detail,
11
- ...(input.details ? { details: input.details } : {}),
12
- instance: input.instance,
13
- status: input.status,
14
- title: input.title,
15
- type: input.type
10
+ error: {
11
+ code: input.code,
12
+ details: {
13
+ ...(input.details ?? {}),
14
+ instance: input.instance,
15
+ status: input.status,
16
+ title: input.title,
17
+ type: input.type
18
+ },
19
+ message: input.detail
20
+ }
16
21
  }, null, 2)}\n`;
17
22
  }
18
23
  export function renderTable(rows, columns) {
package/dist/runner.js CHANGED
@@ -89,6 +89,7 @@ export async function runCli(argv, input = {}) {
89
89
  : `${problem.title}: ${formatProblemDetail(problem.detail, details)}`}\n`,
90
90
  stdout: wantsJson
91
91
  ? renderProblemJson({
92
+ code: problem.code,
92
93
  detail: problem.detail,
93
94
  ...(details ? { details } : {}),
94
95
  instance: problem.instance,
@@ -10,6 +10,19 @@ export interface CliAuthSettings {
10
10
  oidcEnabled: boolean;
11
11
  selfServiceOrgCreationEnabled: boolean;
12
12
  }
13
+ export interface CliDeviceLoginStart {
14
+ deviceCode: string;
15
+ expiresAt: string;
16
+ pollIntervalSeconds: number;
17
+ userCode: string;
18
+ verificationPath: string;
19
+ }
20
+ export type CliDeviceLoginClaim = {
21
+ session: CliSessionState;
22
+ status: "approved";
23
+ } | {
24
+ status: "denied" | "expired" | "pending";
25
+ };
13
26
  export type ApiErrorDetails = Record<string, unknown>;
14
27
  type RequestBody = unknown | ((session: CliSessionState) => unknown);
15
28
  type BytesRequest = {
@@ -26,6 +39,7 @@ type BytesRequest = {
26
39
  validateHeaders?: (headers: Headers) => void;
27
40
  };
28
41
  export declare class ApiError extends Error {
42
+ readonly code: string;
29
43
  readonly detail: string;
30
44
  readonly details?: ApiErrorDetails;
31
45
  readonly instance: string;
@@ -34,6 +48,7 @@ export declare class ApiError extends Error {
34
48
  readonly title: string;
35
49
  readonly type: string;
36
50
  constructor(input: {
51
+ code?: string;
37
52
  detail: string;
38
53
  details?: ApiErrorDetails;
39
54
  instance: string;
@@ -59,6 +74,11 @@ export declare function createPlatformTransport(input: {
59
74
  name: string;
60
75
  password: string;
61
76
  }): Promise<CliSessionState>;
77
+ startDeviceLogin(baseUrl: string): Promise<CliDeviceLoginStart>;
78
+ claimDeviceLogin(claim: {
79
+ baseUrl: string;
80
+ deviceCode: string;
81
+ }): Promise<CliDeviceLoginClaim>;
62
82
  refreshSession(session: CliSessionState): Promise<CliSessionState>;
63
83
  requestJson<T>(request: {
64
84
  body?: RequestBody;
package/dist/transport.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { normalizePublicErrorCode, readPublicErrorCodeFromType } from "./error-code.js";
1
2
  import { extractRefreshTokenFromHeaders } from "./session.js";
2
3
  export class ApiError extends Error {
4
+ code;
3
5
  detail;
4
6
  details;
5
7
  instance;
@@ -10,6 +12,7 @@ export class ApiError extends Error {
10
12
  constructor(input) {
11
13
  super(input.detail);
12
14
  this.name = "ApiError";
15
+ this.code = normalizePublicErrorCode(input.code ?? readPublicErrorCodeFromType(input.type));
13
16
  this.detail = input.detail;
14
17
  if (input.details) {
15
18
  this.details = input.details;
@@ -66,6 +69,52 @@ export function createPlatformTransport(input) {
66
69
  await input.sessionStore.write(session);
67
70
  return session;
68
71
  },
72
+ async startDeviceLogin(baseUrl) {
73
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
74
+ const path = "/api/v1/auth/device/start";
75
+ const response = await fetch(joinBaseUrl(normalizedBaseUrl, path), {
76
+ method: "POST"
77
+ });
78
+ return handleJsonResponse(response, path);
79
+ },
80
+ async claimDeviceLogin(claim) {
81
+ const normalizedBaseUrl = normalizeBaseUrl(claim.baseUrl);
82
+ const path = "/api/v1/auth/device/claim";
83
+ const response = await fetch(joinBaseUrl(normalizedBaseUrl, path), {
84
+ body: JSON.stringify({
85
+ deviceCode: claim.deviceCode
86
+ }),
87
+ headers: {
88
+ "content-type": "application/json"
89
+ },
90
+ method: "POST"
91
+ });
92
+ if (!response.ok) {
93
+ throw await toApiError(response, path);
94
+ }
95
+ const payload = await response.json();
96
+ if (payload.status !== "approved") {
97
+ return { status: payload.status };
98
+ }
99
+ if (!payload.tokens || !payload.user) {
100
+ throw new Error("Device login approval response did not include a session.");
101
+ }
102
+ const refreshToken = extractRefreshTokenFromHeaders(response.headers);
103
+ if (!refreshToken) {
104
+ throw new Error("Refresh token cookie was not returned by the Platform API.");
105
+ }
106
+ const session = {
107
+ accessToken: payload.tokens.accessToken,
108
+ accessTokenPayload: payload.tokens.accessTokenPayload,
109
+ activeOrg: null,
110
+ baseUrl: normalizedBaseUrl,
111
+ refreshToken,
112
+ refreshTokenExpiresAt: payload.tokens.refreshTokenExpiresAt,
113
+ user: payload.user
114
+ };
115
+ await input.sessionStore.write(session);
116
+ return { session, status: "approved" };
117
+ },
69
118
  async refreshSession(session) {
70
119
  const response = await fetch(joinBaseUrl(session.baseUrl, "/api/v1/auth/refresh"), {
71
120
  body: JSON.stringify({
@@ -243,11 +292,12 @@ async function readResponseBytes(response, request) {
243
292
  }
244
293
  function createMaxBytesError(request, actualBytes, maxBytes) {
245
294
  return request.createMaxBytesError?.({ actualBytes, maxBytes }) ?? new ApiError({
295
+ code: "request/too_large",
246
296
  detail: `Response body is ${String(actualBytes)} bytes, which exceeds the limit of ${String(maxBytes)} bytes.`,
247
297
  instance: request.path,
248
298
  status: 413,
249
299
  title: "Payload Too Large",
250
- type: "https://errors.kora.dev/request/too-large"
300
+ type: "https://errors.kora.dev/request/too_large"
251
301
  });
252
302
  }
253
303
  function concatBytes(chunks, totalBytes) {
@@ -271,10 +321,11 @@ async function toApiError(response, path) {
271
321
  if (contentType.includes("application/json")) {
272
322
  const rawBody = await response.text();
273
323
  const parsed = tryParseJsonErrorBody(rawBody);
274
- const code = parsed?.error?.code ?? "internal/error";
324
+ const code = normalizePublicErrorCode(parsed?.error?.code ?? "internal/error");
275
325
  const rawDetail = (parsed?.error?.message ?? rawBody) || response.statusText;
276
326
  const detail = relabelBackendText(rawDetail || response.statusText);
277
327
  return new ApiError({
328
+ code,
278
329
  detail,
279
330
  ...(parsed?.error?.details ? { details: parsed.error.details } : {}),
280
331
  instance: path,
@@ -286,6 +337,7 @@ async function toApiError(response, path) {
286
337
  }
287
338
  const rawDetail = await response.text();
288
339
  return new ApiError({
340
+ code: "internal/error",
289
341
  detail: relabelBackendText(rawDetail),
290
342
  instance: path,
291
343
  ...(rawDetail ? { rawDetail } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kora-platform/cli",
3
- "version": "0.8.0-rc3",
3
+ "version": "0.8.0-rc6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/library.js",