@kora-platform/cli 0.7.0-rc1 → 0.8.0-rc10
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 +21 -0
- package/dist/api-client.d.ts +274 -106
- package/dist/api-client.js +192 -167
- package/dist/api-types.d.ts +301 -163
- package/dist/artifact-api-client.d.ts +28 -1
- package/dist/artifact-api-client.js +33 -0
- package/dist/artifact-commands.d.ts +5 -0
- package/dist/artifact-commands.js +177 -4
- package/dist/audit-commands.d.ts +12 -0
- package/dist/audit-commands.js +74 -0
- package/dist/auth-commands.d.ts +1 -0
- package/dist/auth-commands.js +195 -32
- package/dist/cli-errors.d.ts +7 -1
- package/dist/cli-errors.js +12 -1
- package/dist/command-builders.d.ts +1 -0
- package/dist/command-builders.js +1 -0
- package/dist/command-flags.d.ts +1 -0
- package/dist/command-flags.js +7 -0
- package/dist/command-groups.js +10 -12
- package/dist/command-registry.js +595 -277
- package/dist/commands.js +728 -636
- package/dist/environment-context.d.ts +9 -0
- package/dist/environment-context.js +32 -0
- package/dist/error-code.d.ts +2 -0
- package/dist/error-code.js +9 -0
- package/dist/{integration-commands.d.ts → extension-commands.d.ts} +3 -2
- package/dist/extension-commands.js +446 -0
- package/dist/files.d.ts +44 -4
- package/dist/files.js +349 -26
- package/dist/format.d.ts +6 -0
- package/dist/format.js +83 -1
- package/dist/runner.js +28 -10
- package/dist/schema-registry-data.d.ts +318 -571
- package/dist/schema-registry-data.js +356 -698
- package/dist/session-store.js +80 -0
- package/dist/session.d.ts +1 -0
- package/dist/transport-refresh.d.ts +10 -0
- package/dist/transport-refresh.js +51 -0
- package/dist/transport.d.ts +31 -0
- package/dist/transport.js +102 -36
- package/dist/types.d.ts +2 -1
- package/dist/workspace-source.d.ts +1 -0
- package/dist/workspace-source.js +13 -0
- package/package.json +2 -1
- package/dist/dotenv.d.ts +0 -1
- package/dist/dotenv.js +0 -26
- package/dist/integration-api-client.d.ts +0 -29
- package/dist/integration-api-client.js +0 -50
- package/dist/integration-commands.js +0 -208
package/dist/session-store.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { mkdir, open, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
1
3
|
import { readSessionFile, removeSessionFile, writeSessionFile } from "./session.js";
|
|
4
|
+
const REFRESH_LOCK_STALE_MS = 30_000;
|
|
5
|
+
const REFRESH_LOCK_RETRY_MS = 25;
|
|
2
6
|
export function createFileSessionStore(path) {
|
|
7
|
+
const refreshLockPath = `${path}.refresh.lock`;
|
|
3
8
|
return {
|
|
4
9
|
async clear() {
|
|
5
10
|
await removeSessionFile(path);
|
|
@@ -9,11 +14,15 @@ export function createFileSessionStore(path) {
|
|
|
9
14
|
},
|
|
10
15
|
async write(session) {
|
|
11
16
|
await writeSessionFile(path, session);
|
|
17
|
+
},
|
|
18
|
+
async withRefreshLock(operation) {
|
|
19
|
+
return withFileRefreshLock(refreshLockPath, operation);
|
|
12
20
|
}
|
|
13
21
|
};
|
|
14
22
|
}
|
|
15
23
|
export function createMemorySessionStore(initial = null) {
|
|
16
24
|
let current = initial;
|
|
25
|
+
let refreshLock = Promise.resolve();
|
|
17
26
|
return {
|
|
18
27
|
async clear() {
|
|
19
28
|
current = null;
|
|
@@ -23,6 +32,77 @@ export function createMemorySessionStore(initial = null) {
|
|
|
23
32
|
},
|
|
24
33
|
async write(session) {
|
|
25
34
|
current = session;
|
|
35
|
+
},
|
|
36
|
+
async withRefreshLock(operation) {
|
|
37
|
+
const previous = refreshLock;
|
|
38
|
+
let release;
|
|
39
|
+
refreshLock = new Promise((resolve) => {
|
|
40
|
+
release = resolve;
|
|
41
|
+
});
|
|
42
|
+
await previous;
|
|
43
|
+
try {
|
|
44
|
+
return await operation();
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
release();
|
|
48
|
+
}
|
|
26
49
|
}
|
|
27
50
|
};
|
|
28
51
|
}
|
|
52
|
+
async function withFileRefreshLock(lockPath, operation) {
|
|
53
|
+
const release = await acquireFileRefreshLock(lockPath);
|
|
54
|
+
try {
|
|
55
|
+
return await operation();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await release();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function acquireFileRefreshLock(lockPath) {
|
|
62
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
63
|
+
while (true) {
|
|
64
|
+
try {
|
|
65
|
+
const handle = await open(lockPath, "wx", 0o600);
|
|
66
|
+
try {
|
|
67
|
+
await handle.writeFile(`${String(process.pid)} ${new Date().toISOString()}\n`);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await handle.close();
|
|
71
|
+
}
|
|
72
|
+
return async () => {
|
|
73
|
+
await rm(lockPath, { force: true });
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (!isFileExistsError(error)) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
await removeStaleFileRefreshLock(lockPath);
|
|
81
|
+
await sleep(REFRESH_LOCK_RETRY_MS);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function removeStaleFileRefreshLock(lockPath) {
|
|
86
|
+
try {
|
|
87
|
+
const fileStat = await stat(lockPath);
|
|
88
|
+
if (Date.now() - fileStat.mtimeMs > REFRESH_LOCK_STALE_MS) {
|
|
89
|
+
await rm(lockPath, { force: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (!isMissingFileError(error)) {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function sleep(ms) {
|
|
99
|
+
await new Promise((resolve) => {
|
|
100
|
+
setTimeout(resolve, ms);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function isFileExistsError(error) {
|
|
104
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "EEXIST");
|
|
105
|
+
}
|
|
106
|
+
function isMissingFileError(error) {
|
|
107
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
108
|
+
}
|
package/dist/session.d.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CliSessionState } from "./session.js";
|
|
2
|
+
import type { SessionStore } from "./transport.js";
|
|
3
|
+
export declare function refreshSessionWithCoordination(input: {
|
|
4
|
+
isRefreshTokenInvalidError: (error: unknown) => boolean;
|
|
5
|
+
refreshSessionDirect: (session: CliSessionState) => Promise<CliSessionState>;
|
|
6
|
+
session: CliSessionState;
|
|
7
|
+
sessionStore: SessionStore;
|
|
8
|
+
shouldRefresh: (session: CliSessionState) => boolean;
|
|
9
|
+
}): Promise<CliSessionState>;
|
|
10
|
+
export declare function resolveEffectiveSession(requestSession: CliSessionState | null, sessionStore: SessionStore): Promise<CliSessionState | null>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export async function refreshSessionWithCoordination(input) {
|
|
2
|
+
return withRefreshLock(input.sessionStore, async () => {
|
|
3
|
+
const effectiveSession = (await resolveEffectiveSession(input.session, input.sessionStore)) ?? input.session;
|
|
4
|
+
if (isSessionNewer(effectiveSession, input.session) && !input.shouldRefresh(effectiveSession)) {
|
|
5
|
+
return effectiveSession;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
return await input.refreshSessionDirect(effectiveSession);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (input.isRefreshTokenInvalidError(error)) {
|
|
12
|
+
const recoveredSession = await resolveEffectiveSession(input.session, input.sessionStore);
|
|
13
|
+
if (recoveredSession &&
|
|
14
|
+
isSessionNewer(recoveredSession, input.session) &&
|
|
15
|
+
!input.shouldRefresh(recoveredSession)) {
|
|
16
|
+
return recoveredSession;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function withRefreshLock(sessionStore, operation) {
|
|
24
|
+
return sessionStore.withRefreshLock
|
|
25
|
+
? sessionStore.withRefreshLock(operation)
|
|
26
|
+
: operation();
|
|
27
|
+
}
|
|
28
|
+
export async function resolveEffectiveSession(requestSession, sessionStore) {
|
|
29
|
+
if (!requestSession) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const storedSession = await sessionStore.read();
|
|
33
|
+
if (!storedSession || !isSameSessionIdentity(storedSession, requestSession)) {
|
|
34
|
+
return requestSession;
|
|
35
|
+
}
|
|
36
|
+
return isSessionNewer(storedSession, requestSession) ? storedSession : requestSession;
|
|
37
|
+
}
|
|
38
|
+
function isSameSessionIdentity(candidate, current) {
|
|
39
|
+
return candidate.baseUrl === current.baseUrl
|
|
40
|
+
&& candidate.user.id === current.user.id
|
|
41
|
+
&& candidate.accessTokenPayload.sub === current.accessTokenPayload.sub;
|
|
42
|
+
}
|
|
43
|
+
function isSessionNewer(candidate, current) {
|
|
44
|
+
if (candidate.accessToken === current.accessToken && candidate.refreshToken === current.refreshToken) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (candidate.accessTokenPayload.exp !== current.accessTokenPayload.exp) {
|
|
48
|
+
return candidate.accessTokenPayload.exp > current.accessTokenPayload.exp;
|
|
49
|
+
}
|
|
50
|
+
return candidate.accessTokenPayload.iat >= current.accessTokenPayload.iat;
|
|
51
|
+
}
|
package/dist/transport.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type CliSessionState } from "./session.js";
|
|
|
2
2
|
export interface SessionStore {
|
|
3
3
|
clear(): Promise<void>;
|
|
4
4
|
read(): Promise<CliSessionState | null>;
|
|
5
|
+
withRefreshLock?<T>(operation: () => Promise<T>): Promise<T>;
|
|
5
6
|
write(session: CliSessionState): Promise<void>;
|
|
6
7
|
}
|
|
7
8
|
export interface CliAuthSettings {
|
|
@@ -10,6 +11,20 @@ export interface CliAuthSettings {
|
|
|
10
11
|
oidcEnabled: boolean;
|
|
11
12
|
selfServiceOrgCreationEnabled: boolean;
|
|
12
13
|
}
|
|
14
|
+
export interface CliDeviceLoginStart {
|
|
15
|
+
deviceCode: string;
|
|
16
|
+
expiresAt: string;
|
|
17
|
+
pollIntervalSeconds: number;
|
|
18
|
+
userCode: string;
|
|
19
|
+
verificationPath: string;
|
|
20
|
+
}
|
|
21
|
+
export type CliDeviceLoginClaim = {
|
|
22
|
+
session: CliSessionState;
|
|
23
|
+
status: "approved";
|
|
24
|
+
} | {
|
|
25
|
+
status: "denied" | "expired" | "pending";
|
|
26
|
+
};
|
|
27
|
+
export type ApiErrorDetails = Record<string, unknown>;
|
|
13
28
|
type RequestBody = unknown | ((session: CliSessionState) => unknown);
|
|
14
29
|
type BytesRequest = {
|
|
15
30
|
createMaxBytesError?: (input: {
|
|
@@ -20,18 +35,23 @@ type BytesRequest = {
|
|
|
20
35
|
maxBytes?: number;
|
|
21
36
|
method?: string;
|
|
22
37
|
path: string;
|
|
38
|
+
rawBody?: Uint8Array;
|
|
23
39
|
session: CliSessionState | null;
|
|
24
40
|
validateHeaders?: (headers: Headers) => void;
|
|
25
41
|
};
|
|
26
42
|
export declare class ApiError extends Error {
|
|
43
|
+
readonly code: string;
|
|
27
44
|
readonly detail: string;
|
|
45
|
+
readonly details?: ApiErrorDetails;
|
|
28
46
|
readonly instance: string;
|
|
29
47
|
readonly rawDetail?: string;
|
|
30
48
|
readonly status: number;
|
|
31
49
|
readonly title: string;
|
|
32
50
|
readonly type: string;
|
|
33
51
|
constructor(input: {
|
|
52
|
+
code?: string;
|
|
34
53
|
detail: string;
|
|
54
|
+
details?: ApiErrorDetails;
|
|
35
55
|
instance: string;
|
|
36
56
|
rawDetail?: string;
|
|
37
57
|
status: number;
|
|
@@ -49,6 +69,17 @@ export declare function createPlatformTransport(input: {
|
|
|
49
69
|
email: string;
|
|
50
70
|
password: string;
|
|
51
71
|
}): Promise<CliSessionState>;
|
|
72
|
+
signup(credentials: {
|
|
73
|
+
baseUrl: string;
|
|
74
|
+
email: string;
|
|
75
|
+
name: string;
|
|
76
|
+
password: string;
|
|
77
|
+
}): Promise<CliSessionState>;
|
|
78
|
+
startDeviceLogin(baseUrl: string): Promise<CliDeviceLoginStart>;
|
|
79
|
+
claimDeviceLogin(claim: {
|
|
80
|
+
baseUrl: string;
|
|
81
|
+
deviceCode: string;
|
|
82
|
+
}): Promise<CliDeviceLoginClaim>;
|
|
52
83
|
refreshSession(session: CliSessionState): Promise<CliSessionState>;
|
|
53
84
|
requestJson<T>(request: {
|
|
54
85
|
body?: RequestBody;
|
package/dist/transport.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { normalizePublicErrorCode, readPublicErrorCodeFromType } from "./error-code.js";
|
|
1
2
|
import { extractRefreshTokenFromHeaders } from "./session.js";
|
|
3
|
+
import { refreshSessionWithCoordination, resolveEffectiveSession } from "./transport-refresh.js";
|
|
2
4
|
export class ApiError extends Error {
|
|
5
|
+
code;
|
|
3
6
|
detail;
|
|
7
|
+
details;
|
|
4
8
|
instance;
|
|
5
9
|
rawDetail;
|
|
6
10
|
status;
|
|
@@ -9,7 +13,11 @@ export class ApiError extends Error {
|
|
|
9
13
|
constructor(input) {
|
|
10
14
|
super(input.detail);
|
|
11
15
|
this.name = "ApiError";
|
|
16
|
+
this.code = normalizePublicErrorCode(input.code ?? readPublicErrorCodeFromType(input.type));
|
|
12
17
|
this.detail = input.detail;
|
|
18
|
+
if (input.details) {
|
|
19
|
+
this.details = input.details;
|
|
20
|
+
}
|
|
13
21
|
this.instance = input.instance;
|
|
14
22
|
if (input.rawDetail !== undefined) {
|
|
15
23
|
this.rawDetail = input.rawDetail;
|
|
@@ -21,6 +29,13 @@ export class ApiError extends Error {
|
|
|
21
29
|
}
|
|
22
30
|
export function createPlatformTransport(input) {
|
|
23
31
|
const now = input.now ?? Date.now;
|
|
32
|
+
const refreshSession = async (session) => refreshSessionWithCoordination({
|
|
33
|
+
isRefreshTokenInvalidError,
|
|
34
|
+
refreshSessionDirect: async (effectiveSession) => refreshSessionDirect(input.sessionStore, effectiveSession),
|
|
35
|
+
session,
|
|
36
|
+
sessionStore: input.sessionStore,
|
|
37
|
+
shouldRefresh: (effectiveSession) => shouldRefresh(effectiveSession, now)
|
|
38
|
+
});
|
|
24
39
|
return {
|
|
25
40
|
async getAuthSettings(baseUrl) {
|
|
26
41
|
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
@@ -45,19 +60,71 @@ export function createPlatformTransport(input) {
|
|
|
45
60
|
await input.sessionStore.write(session);
|
|
46
61
|
return session;
|
|
47
62
|
},
|
|
48
|
-
async
|
|
49
|
-
const
|
|
63
|
+
async signup(credentials) {
|
|
64
|
+
const normalizedBaseUrl = normalizeBaseUrl(credentials.baseUrl);
|
|
65
|
+
const response = await fetch(joinBaseUrl(normalizedBaseUrl, "/api/v1/auth/signup"), {
|
|
66
|
+
body: JSON.stringify({
|
|
67
|
+
email: credentials.email,
|
|
68
|
+
name: credentials.name,
|
|
69
|
+
password: credentials.password
|
|
70
|
+
}),
|
|
71
|
+
headers: {
|
|
72
|
+
"content-type": "application/json"
|
|
73
|
+
},
|
|
74
|
+
method: "POST"
|
|
75
|
+
});
|
|
76
|
+
const session = await parseSessionResponse(response, normalizedBaseUrl, "/api/v1/auth/signup");
|
|
77
|
+
await input.sessionStore.write(session);
|
|
78
|
+
return session;
|
|
79
|
+
},
|
|
80
|
+
async startDeviceLogin(baseUrl) {
|
|
81
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
82
|
+
const path = "/api/v1/auth/device/start";
|
|
83
|
+
const response = await fetch(joinBaseUrl(normalizedBaseUrl, path), {
|
|
84
|
+
method: "POST"
|
|
85
|
+
});
|
|
86
|
+
return handleJsonResponse(response, path);
|
|
87
|
+
},
|
|
88
|
+
async claimDeviceLogin(claim) {
|
|
89
|
+
const normalizedBaseUrl = normalizeBaseUrl(claim.baseUrl);
|
|
90
|
+
const path = "/api/v1/auth/device/claim";
|
|
91
|
+
const response = await fetch(joinBaseUrl(normalizedBaseUrl, path), {
|
|
50
92
|
body: JSON.stringify({
|
|
51
|
-
|
|
93
|
+
deviceCode: claim.deviceCode
|
|
52
94
|
}),
|
|
53
95
|
headers: {
|
|
54
96
|
"content-type": "application/json"
|
|
55
97
|
},
|
|
56
98
|
method: "POST"
|
|
57
99
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw await toApiError(response, path);
|
|
102
|
+
}
|
|
103
|
+
const payload = await response.json();
|
|
104
|
+
if (payload.status !== "approved") {
|
|
105
|
+
return { status: payload.status };
|
|
106
|
+
}
|
|
107
|
+
if (!payload.tokens || !payload.user) {
|
|
108
|
+
throw new Error("Device login approval response did not include a session.");
|
|
109
|
+
}
|
|
110
|
+
const refreshToken = extractRefreshTokenFromHeaders(response.headers);
|
|
111
|
+
if (!refreshToken) {
|
|
112
|
+
throw new Error("Refresh token cookie was not returned by the Platform API.");
|
|
113
|
+
}
|
|
114
|
+
const session = {
|
|
115
|
+
accessToken: payload.tokens.accessToken,
|
|
116
|
+
accessTokenPayload: payload.tokens.accessTokenPayload,
|
|
117
|
+
activeOrg: null,
|
|
118
|
+
baseUrl: normalizedBaseUrl,
|
|
119
|
+
refreshToken,
|
|
120
|
+
refreshTokenExpiresAt: payload.tokens.refreshTokenExpiresAt,
|
|
121
|
+
user: payload.user
|
|
122
|
+
};
|
|
123
|
+
await input.sessionStore.write(session);
|
|
124
|
+
return { session, status: "approved" };
|
|
125
|
+
},
|
|
126
|
+
async refreshSession(session) {
|
|
127
|
+
return refreshSession(session);
|
|
61
128
|
},
|
|
62
129
|
async requestJson(request) {
|
|
63
130
|
let session = await resolveEffectiveSession(request.session, input.sessionStore);
|
|
@@ -71,11 +138,11 @@ export function createPlatformTransport(input) {
|
|
|
71
138
|
});
|
|
72
139
|
}
|
|
73
140
|
if (shouldRefresh(session, now) && canRefresh(session)) {
|
|
74
|
-
session = await
|
|
141
|
+
session = await refreshSession(session);
|
|
75
142
|
}
|
|
76
143
|
const firstResponse = await authenticatedFetch(session, request);
|
|
77
144
|
if (firstResponse.status === 401 && canRefresh(session)) {
|
|
78
|
-
session = await
|
|
145
|
+
session = await refreshSession(session);
|
|
79
146
|
return handleJsonResponse(await authenticatedFetch(session, request), request.path);
|
|
80
147
|
}
|
|
81
148
|
return handleJsonResponse(firstResponse, request.path);
|
|
@@ -92,17 +159,36 @@ export function createPlatformTransport(input) {
|
|
|
92
159
|
});
|
|
93
160
|
}
|
|
94
161
|
if (shouldRefresh(session, now) && canRefresh(session)) {
|
|
95
|
-
session = await
|
|
162
|
+
session = await refreshSession(session);
|
|
96
163
|
}
|
|
97
164
|
const firstResponse = await authenticatedFetch(session, request);
|
|
98
165
|
if (firstResponse.status === 401 && canRefresh(session)) {
|
|
99
|
-
session = await
|
|
166
|
+
session = await refreshSession(session);
|
|
100
167
|
return handleBytesResponse(await authenticatedFetch(session, request), request);
|
|
101
168
|
}
|
|
102
169
|
return handleBytesResponse(firstResponse, request);
|
|
103
170
|
}
|
|
104
171
|
};
|
|
105
172
|
}
|
|
173
|
+
async function refreshSessionDirect(sessionStore, session) {
|
|
174
|
+
const response = await fetch(joinBaseUrl(session.baseUrl, "/api/v1/auth/refresh"), {
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
refreshToken: session.refreshToken
|
|
177
|
+
}),
|
|
178
|
+
headers: {
|
|
179
|
+
"content-type": "application/json"
|
|
180
|
+
},
|
|
181
|
+
method: "POST"
|
|
182
|
+
});
|
|
183
|
+
const nextSession = await parseSessionResponse(response, session.baseUrl, "/api/v1/auth/refresh", session.activeOrg);
|
|
184
|
+
await sessionStore.write(nextSession);
|
|
185
|
+
return nextSession;
|
|
186
|
+
}
|
|
187
|
+
function isRefreshTokenInvalidError(error) {
|
|
188
|
+
return error instanceof ApiError &&
|
|
189
|
+
error.code === "auth/token_invalid" &&
|
|
190
|
+
error.instance === "/api/v1/auth/refresh";
|
|
191
|
+
}
|
|
106
192
|
async function authenticatedFetch(session, request) {
|
|
107
193
|
const body = resolveRequestBody(request.body, session);
|
|
108
194
|
const hasJsonBody = body !== undefined;
|
|
@@ -222,11 +308,12 @@ async function readResponseBytes(response, request) {
|
|
|
222
308
|
}
|
|
223
309
|
function createMaxBytesError(request, actualBytes, maxBytes) {
|
|
224
310
|
return request.createMaxBytesError?.({ actualBytes, maxBytes }) ?? new ApiError({
|
|
311
|
+
code: "request/too_large",
|
|
225
312
|
detail: `Response body is ${String(actualBytes)} bytes, which exceeds the limit of ${String(maxBytes)} bytes.`,
|
|
226
313
|
instance: request.path,
|
|
227
314
|
status: 413,
|
|
228
315
|
title: "Payload Too Large",
|
|
229
|
-
type: "https://errors.kora.dev/request/
|
|
316
|
+
type: "https://errors.kora.dev/request/too_large"
|
|
230
317
|
});
|
|
231
318
|
}
|
|
232
319
|
function concatBytes(chunks, totalBytes) {
|
|
@@ -250,11 +337,13 @@ async function toApiError(response, path) {
|
|
|
250
337
|
if (contentType.includes("application/json")) {
|
|
251
338
|
const rawBody = await response.text();
|
|
252
339
|
const parsed = tryParseJsonErrorBody(rawBody);
|
|
253
|
-
const code = parsed?.error?.code ?? "internal/error";
|
|
340
|
+
const code = normalizePublicErrorCode(parsed?.error?.code ?? "internal/error");
|
|
254
341
|
const rawDetail = (parsed?.error?.message ?? rawBody) || response.statusText;
|
|
255
342
|
const detail = relabelBackendText(rawDetail || response.statusText);
|
|
256
343
|
return new ApiError({
|
|
344
|
+
code,
|
|
257
345
|
detail,
|
|
346
|
+
...(parsed?.error?.details ? { details: parsed.error.details } : {}),
|
|
258
347
|
instance: path,
|
|
259
348
|
...(rawDetail ? { rawDetail } : {}),
|
|
260
349
|
status: response.status,
|
|
@@ -264,6 +353,7 @@ async function toApiError(response, path) {
|
|
|
264
353
|
}
|
|
265
354
|
const rawDetail = await response.text();
|
|
266
355
|
return new ApiError({
|
|
356
|
+
code: "internal/error",
|
|
267
357
|
detail: relabelBackendText(rawDetail),
|
|
268
358
|
instance: path,
|
|
269
359
|
...(rawDetail ? { rawDetail } : {}),
|
|
@@ -315,27 +405,3 @@ function shouldRefresh(session, now) {
|
|
|
315
405
|
function canRefresh(session) {
|
|
316
406
|
return session.refreshToken.trim().length > 0;
|
|
317
407
|
}
|
|
318
|
-
async function resolveEffectiveSession(requestSession, sessionStore) {
|
|
319
|
-
if (!requestSession) {
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
const storedSession = await sessionStore.read();
|
|
323
|
-
if (!storedSession || !isSameSessionIdentity(storedSession, requestSession)) {
|
|
324
|
-
return requestSession;
|
|
325
|
-
}
|
|
326
|
-
return isSessionNewer(storedSession, requestSession) ? storedSession : requestSession;
|
|
327
|
-
}
|
|
328
|
-
function isSameSessionIdentity(candidate, current) {
|
|
329
|
-
return candidate.baseUrl === current.baseUrl
|
|
330
|
-
&& candidate.user.id === current.user.id
|
|
331
|
-
&& candidate.accessTokenPayload.sub === current.accessTokenPayload.sub;
|
|
332
|
-
}
|
|
333
|
-
function isSessionNewer(candidate, current) {
|
|
334
|
-
if (candidate.accessToken === current.accessToken && candidate.refreshToken === current.refreshToken) {
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
if (candidate.accessTokenPayload.exp !== current.accessTokenPayload.exp) {
|
|
338
|
-
return candidate.accessTokenPayload.exp > current.accessTokenPayload.exp;
|
|
339
|
-
}
|
|
340
|
-
return candidate.accessTokenPayload.iat >= current.accessTokenPayload.iat;
|
|
341
|
-
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type CommandPagination = "none" | "limit" | "limit-offset" | "cursor";
|
|
2
|
-
export type CommandLabel = "access" | "action" | "activity" | "admin" | "artifact" | "auth" | "chat" | "completion" | "credential" | "destructive" | "env" | "
|
|
2
|
+
export type CommandLabel = "access" | "action" | "activity" | "admin" | "artifact" | "audit" | "auth" | "chat" | "chat-execution" | "chat-read" | "chat-write" | "completion" | "credential" | "destructive" | "env" | "environment" | "execution" | "extension" | "help" | "local" | "org" | "org-model" | "output" | "probe" | "publish" | "read" | "release" | "deployment" | "run" | "schema" | "secret" | "status" | "task" | "workspace" | "workflow" | "write";
|
|
3
3
|
export interface CommandArgDefinition {
|
|
4
4
|
description: string;
|
|
5
5
|
name: string;
|
|
@@ -9,6 +9,7 @@ export interface CommandFlagDefinition {
|
|
|
9
9
|
acceptsValue: boolean;
|
|
10
10
|
defaultValue?: boolean | number | string;
|
|
11
11
|
description: string;
|
|
12
|
+
hiddenWhenCommandFiltered?: boolean;
|
|
12
13
|
knownValues?: string[];
|
|
13
14
|
name: string;
|
|
14
15
|
repeatable?: boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shouldIgnoreWorkspacePath(pathValue: string): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const IGNORED_WORKSPACE_DIRECTORY_NAMES = new Set([
|
|
2
|
+
".git",
|
|
3
|
+
".kora",
|
|
4
|
+
"coverage",
|
|
5
|
+
"dist",
|
|
6
|
+
"node_modules"
|
|
7
|
+
]);
|
|
8
|
+
const IGNORED_WORKSPACE_FILE_NAMES = new Set([".DS_Store", "Thumbs.db"]);
|
|
9
|
+
export function shouldIgnoreWorkspacePath(pathValue) {
|
|
10
|
+
const segments = pathValue.replace(/\\/gu, "/").split("/").filter((segment) => segment.length > 0);
|
|
11
|
+
return segments.some((segment) => IGNORED_WORKSPACE_DIRECTORY_NAMES.has(segment)) ||
|
|
12
|
+
IGNORED_WORKSPACE_FILE_NAMES.has(segments.at(-1) ?? "");
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kora-platform/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-rc10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/library.js",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=24.0.0"
|
|
36
36
|
},
|
|
37
|
+
"dependencies": {},
|
|
37
38
|
"scripts": {
|
|
38
39
|
"build": "pnpm run clean && pnpm run generate:schema-registry && tsc -b tsconfig.build.json",
|
|
39
40
|
"check:package": "pnpm run build && node ../../../scripts/check-cli-pack-output.mjs",
|
package/dist/dotenv.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function parseDotEnv(source: string): Record<string, string>;
|
package/dist/dotenv.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
2
|
-
export function parseDotEnv(source) {
|
|
3
|
-
const parsed = {};
|
|
4
|
-
const lines = source.split(/\r?\n/u);
|
|
5
|
-
for (const rawLine of lines) {
|
|
6
|
-
const line = rawLine.trim();
|
|
7
|
-
if (line.length === 0 || line.startsWith("#")) {
|
|
8
|
-
continue;
|
|
9
|
-
}
|
|
10
|
-
const separatorIndex = line.indexOf("=");
|
|
11
|
-
if (separatorIndex <= 0) {
|
|
12
|
-
continue;
|
|
13
|
-
}
|
|
14
|
-
const rawName = line.slice(0, separatorIndex).trim();
|
|
15
|
-
if (!ENV_NAME_PATTERN.test(rawName)) {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
let value = line.slice(separatorIndex + 1).trim();
|
|
19
|
-
if ((value.startsWith("\"") && value.endsWith("\"")) ||
|
|
20
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
21
|
-
value = value.slice(1, -1);
|
|
22
|
-
}
|
|
23
|
-
parsed[rawName] = value;
|
|
24
|
-
}
|
|
25
|
-
return parsed;
|
|
26
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ManagedIntegrationActionDescriptor, ManagedIntegrationActionInput, ManagedIntegrationActionResult, ManagedIntegrationConnectionSummary, ManagedIntegrationDiagnostic, ManagedIntegrationPromptGuidance, ManagedIntegrationSummary } from "./api-types.js";
|
|
2
|
-
import type { CliSessionState } from "./transport.js";
|
|
3
|
-
import { createPlatformTransport } from "./transport.js";
|
|
4
|
-
export interface PlatformIntegrationApiClientInput {
|
|
5
|
-
sessionStore: Parameters<typeof createPlatformTransport>[0]["sessionStore"];
|
|
6
|
-
}
|
|
7
|
-
export declare function createPlatformIntegrationApiClient(input: PlatformIntegrationApiClientInput): {
|
|
8
|
-
listManagedIntegrations(session: CliSessionState, orgId: string): Promise<{
|
|
9
|
-
integrations: ManagedIntegrationSummary[];
|
|
10
|
-
}>;
|
|
11
|
-
listManagedIntegrationDiagnostics(session: CliSessionState, orgId: string): Promise<{
|
|
12
|
-
integrations: ManagedIntegrationDiagnostic[];
|
|
13
|
-
}>;
|
|
14
|
-
getManagedIntegration(session: CliSessionState, orgId: string, integrationId: string): Promise<{
|
|
15
|
-
integration: ManagedIntegrationSummary;
|
|
16
|
-
}>;
|
|
17
|
-
listManagedIntegrationConnections(session: CliSessionState, orgId: string, integrationId: string): Promise<{
|
|
18
|
-
connections: ManagedIntegrationConnectionSummary[];
|
|
19
|
-
}>;
|
|
20
|
-
listManagedIntegrationActions(session: CliSessionState, orgId: string, integrationId: string): Promise<{
|
|
21
|
-
actions: ManagedIntegrationActionDescriptor[];
|
|
22
|
-
}>;
|
|
23
|
-
executeManagedIntegrationAction(session: CliSessionState, orgId: string, integrationId: string, actionId: string, inputData: ManagedIntegrationActionInput): Promise<{
|
|
24
|
-
result: ManagedIntegrationActionResult;
|
|
25
|
-
}>;
|
|
26
|
-
listManagedIntegrationPromptGuidance(session: CliSessionState, orgId: string): Promise<{
|
|
27
|
-
guidance: ManagedIntegrationPromptGuidance[];
|
|
28
|
-
}>;
|
|
29
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { createPlatformTransport } from "./transport.js";
|
|
2
|
-
export function createPlatformIntegrationApiClient(input) {
|
|
3
|
-
const transport = createPlatformTransport(input);
|
|
4
|
-
return {
|
|
5
|
-
async listManagedIntegrations(session, orgId) {
|
|
6
|
-
return transport.requestJson({
|
|
7
|
-
path: `/api/v1/orgs/${encodeURIComponent(orgId)}/integration-host/integrations`,
|
|
8
|
-
session
|
|
9
|
-
});
|
|
10
|
-
},
|
|
11
|
-
async listManagedIntegrationDiagnostics(session, orgId) {
|
|
12
|
-
return transport.requestJson({
|
|
13
|
-
path: `/api/v1/orgs/${encodeURIComponent(orgId)}/integration-host/integrations/diagnostics`,
|
|
14
|
-
session
|
|
15
|
-
});
|
|
16
|
-
},
|
|
17
|
-
async getManagedIntegration(session, orgId, integrationId) {
|
|
18
|
-
return transport.requestJson({
|
|
19
|
-
path: `/api/v1/orgs/${encodeURIComponent(orgId)}/integration-host/integrations/${encodeURIComponent(integrationId)}`,
|
|
20
|
-
session
|
|
21
|
-
});
|
|
22
|
-
},
|
|
23
|
-
async listManagedIntegrationConnections(session, orgId, integrationId) {
|
|
24
|
-
return transport.requestJson({
|
|
25
|
-
path: `/api/v1/orgs/${encodeURIComponent(orgId)}/integration-host/integrations/${encodeURIComponent(integrationId)}/connections`,
|
|
26
|
-
session
|
|
27
|
-
});
|
|
28
|
-
},
|
|
29
|
-
async listManagedIntegrationActions(session, orgId, integrationId) {
|
|
30
|
-
return transport.requestJson({
|
|
31
|
-
path: `/api/v1/orgs/${encodeURIComponent(orgId)}/integration-host/integrations/${encodeURIComponent(integrationId)}/actions`,
|
|
32
|
-
session
|
|
33
|
-
});
|
|
34
|
-
},
|
|
35
|
-
async executeManagedIntegrationAction(session, orgId, integrationId, actionId, inputData) {
|
|
36
|
-
return transport.requestJson({
|
|
37
|
-
body: inputData,
|
|
38
|
-
method: "POST",
|
|
39
|
-
path: `/api/v1/orgs/${encodeURIComponent(orgId)}/integration-host/integrations/${encodeURIComponent(integrationId)}/actions/${encodeURIComponent(actionId)}`,
|
|
40
|
-
session
|
|
41
|
-
});
|
|
42
|
-
},
|
|
43
|
-
async listManagedIntegrationPromptGuidance(session, orgId) {
|
|
44
|
-
return transport.requestJson({
|
|
45
|
-
path: `/api/v1/orgs/${encodeURIComponent(orgId)}/integration-host/prompt-guidance`,
|
|
46
|
-
session
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|