@fluid-app/fluid-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/dist/bin/fluid.d.mts +1 -0
- package/dist/bin/fluid.mjs +460 -0
- package/dist/bin/fluid.mjs.map +1 -0
- package/dist/index.d.mts +210 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +301 -0
- package/dist/index.mjs.map +1 -0
- package/dist/token-DCpSVmEk.mjs +322 -0
- package/dist/token-DCpSVmEk.mjs.map +1 -0
- package/package.json +50 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
//#region src/config/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Configuration types for the Fluid CLI
|
|
6
|
+
*/
|
|
7
|
+
interface FluidProfile {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly token: string;
|
|
10
|
+
readonly companyName: string;
|
|
11
|
+
readonly storedAt: string;
|
|
12
|
+
}
|
|
13
|
+
interface FluidConfig {
|
|
14
|
+
activeProfile: string | null;
|
|
15
|
+
profiles: Record<string, FluidProfile>;
|
|
16
|
+
plugins: Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Allow-list of plugin package names.
|
|
19
|
+
*
|
|
20
|
+
* - `null` (default) — auto-discover and load all `@fluid-app/fluid-cli-*`
|
|
21
|
+
* and `*-cli-commands` packages found in `node_modules` or the pnpm
|
|
22
|
+
* workspace. Only `@fluid-app`-scoped packages matching the naming
|
|
23
|
+
* convention are eligible; no third-party code is loaded.
|
|
24
|
+
* - `string[]` — only load plugins whose names appear in the array.
|
|
25
|
+
* Set to `[]` to disable all plugins.
|
|
26
|
+
*/
|
|
27
|
+
enabledPlugins: string[] | null;
|
|
28
|
+
}
|
|
29
|
+
declare function createDefaultConfig(): FluidConfig;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/config/paths.d.ts
|
|
32
|
+
/**
|
|
33
|
+
* XDG-compliant config directory resolution
|
|
34
|
+
*
|
|
35
|
+
* Priority:
|
|
36
|
+
* 1. FLUID_CONFIG_DIR env var (explicit override)
|
|
37
|
+
* 2. ~/.fluid/ on macOS (convention for CLI tools)
|
|
38
|
+
* 3. %APPDATA%/fluid/ on Windows
|
|
39
|
+
* 4. $XDG_CONFIG_HOME/fluid/ on Linux (XDG spec)
|
|
40
|
+
* 5. ~/.config/fluid/ fallback on Linux
|
|
41
|
+
*/
|
|
42
|
+
declare function getConfigDir(): string;
|
|
43
|
+
declare function getConfigFilePath(): string;
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/config/config.d.ts
|
|
46
|
+
declare function readConfig(): FluidConfig;
|
|
47
|
+
declare function writeConfig(config: FluidConfig): void;
|
|
48
|
+
declare function updateConfig(updater: (config: FluidConfig) => FluidConfig): FluidConfig;
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/utils/errors.d.ts
|
|
51
|
+
/**
|
|
52
|
+
* Base error interface for CLI domain errors.
|
|
53
|
+
* All domain-specific error types should extend this.
|
|
54
|
+
*/
|
|
55
|
+
interface CliError {
|
|
56
|
+
readonly code: string;
|
|
57
|
+
readonly message: string;
|
|
58
|
+
readonly details?: string;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/utils/result.d.ts
|
|
62
|
+
/**
|
|
63
|
+
* Result type utilities for type-safe error handling
|
|
64
|
+
*
|
|
65
|
+
* The Result<T, E> pattern provides a discriminated union for fallible operations,
|
|
66
|
+
* enabling exhaustive handling without try/catch blocks.
|
|
67
|
+
*/
|
|
68
|
+
interface Success<T> {
|
|
69
|
+
readonly success: true;
|
|
70
|
+
readonly value: T;
|
|
71
|
+
}
|
|
72
|
+
interface Failure<E> {
|
|
73
|
+
readonly success: false;
|
|
74
|
+
readonly error: E;
|
|
75
|
+
}
|
|
76
|
+
type Result<T, E = Error> = Success<T> | Failure<E>;
|
|
77
|
+
declare function success<T>(value: T): Success<T>;
|
|
78
|
+
declare function failure<E>(error: E): Failure<E>;
|
|
79
|
+
declare function isSuccess<T, E>(result: Result<T, E>): result is Success<T>;
|
|
80
|
+
declare function isFailure<T, E>(result: Result<T, E>): result is Failure<E>;
|
|
81
|
+
declare function tryCatch<T>(fn: () => T): Result<T, Error>;
|
|
82
|
+
declare function tryCatchAsync<T>(fn: () => Promise<T>): Promise<Result<T, Error>>;
|
|
83
|
+
declare function unwrap<T, E>(result: Result<T, E>): T;
|
|
84
|
+
declare function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T;
|
|
85
|
+
declare function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
|
|
86
|
+
declare function mapError<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;
|
|
87
|
+
declare function isError(value: unknown): value is Error;
|
|
88
|
+
declare function isNodeError(value: unknown): value is NodeJS.ErrnoException;
|
|
89
|
+
declare function getErrorMessage(error: unknown): string;
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/auth/fluid-api.d.ts
|
|
92
|
+
interface FluidApiError extends CliError {
|
|
93
|
+
readonly code: string;
|
|
94
|
+
readonly message: string;
|
|
95
|
+
readonly details?: string;
|
|
96
|
+
}
|
|
97
|
+
interface CompanyInfo {
|
|
98
|
+
readonly name: string;
|
|
99
|
+
}
|
|
100
|
+
interface MfaResponse {
|
|
101
|
+
readonly uuid: string;
|
|
102
|
+
readonly expiresAt: string;
|
|
103
|
+
}
|
|
104
|
+
interface CompanyChoice {
|
|
105
|
+
readonly id: number;
|
|
106
|
+
readonly name: string;
|
|
107
|
+
readonly shopName: string;
|
|
108
|
+
readonly jwt: string;
|
|
109
|
+
}
|
|
110
|
+
interface ConfirmMfaResponse {
|
|
111
|
+
readonly authType: string;
|
|
112
|
+
readonly companies: CompanyChoice[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Validate a token against the Fluid API and return company info
|
|
116
|
+
*/
|
|
117
|
+
declare function validateToken(token: string): Promise<Result<CompanyInfo, FluidApiError>>;
|
|
118
|
+
/**
|
|
119
|
+
* Send a multi-factor authentication code to the given email address.
|
|
120
|
+
* Always returns 201 from the API (anti-enumeration).
|
|
121
|
+
*/
|
|
122
|
+
declare function sendMfa(email: string): Promise<Result<MfaResponse, FluidApiError>>;
|
|
123
|
+
/**
|
|
124
|
+
* Confirm a multi-factor authentication code and retrieve company JWTs.
|
|
125
|
+
*/
|
|
126
|
+
declare function confirmMfa(uuid: string, code: string): Promise<Result<ConfirmMfaResponse, FluidApiError>>;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/auth/token.d.ts
|
|
129
|
+
/**
|
|
130
|
+
* Get the auth token for a named profile, or the active profile when omitted.
|
|
131
|
+
*/
|
|
132
|
+
declare function getAuthToken(profileName?: string): string | null;
|
|
133
|
+
/**
|
|
134
|
+
* Get the active profile, or null if not logged in
|
|
135
|
+
*/
|
|
136
|
+
declare function getActiveProfile(): FluidProfile | null;
|
|
137
|
+
/**
|
|
138
|
+
* List all stored profile names
|
|
139
|
+
*/
|
|
140
|
+
declare function listProfileNames(): string[];
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/plugins/types.d.ts
|
|
143
|
+
interface PluginContext {
|
|
144
|
+
/** Commander program instance — plugins add subcommands here */
|
|
145
|
+
readonly program: Command;
|
|
146
|
+
/** Read the stored auth token for the active profile */
|
|
147
|
+
readonly getAuthToken: () => string | null;
|
|
148
|
+
/** Path to the config directory (~/.fluid/) */
|
|
149
|
+
readonly configDir: string;
|
|
150
|
+
}
|
|
151
|
+
interface FluidPlugin {
|
|
152
|
+
readonly name: string;
|
|
153
|
+
readonly version: string;
|
|
154
|
+
register(ctx: PluginContext): void | Promise<void>;
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region ../../platform/api-client-core/src/fetch-client.d.ts
|
|
158
|
+
interface RequestOptions {
|
|
159
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
160
|
+
headers?: Record<string, string>;
|
|
161
|
+
params?: Record<string, unknown>;
|
|
162
|
+
body?: unknown;
|
|
163
|
+
signal?: AbortSignal;
|
|
164
|
+
}
|
|
165
|
+
interface FetchClientInstance {
|
|
166
|
+
request: <TResponse = unknown>(endpoint: string, options?: RequestOptions) => Promise<TResponse>;
|
|
167
|
+
requestWithFormData: <TResponse = unknown>(endpoint: string, formData: FormData, options?: Omit<RequestOptions, "body" | "params"> & {
|
|
168
|
+
method?: "POST" | "PUT" | "PATCH";
|
|
169
|
+
}) => Promise<TResponse>;
|
|
170
|
+
get: <TResponse = unknown>(endpoint: string, params?: Record<string, unknown>, options?: Omit<RequestOptions, "method" | "params">) => Promise<TResponse>;
|
|
171
|
+
post: <TResponse = unknown>(endpoint: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) => Promise<TResponse>;
|
|
172
|
+
put: <TResponse = unknown>(endpoint: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) => Promise<TResponse>;
|
|
173
|
+
patch: <TResponse = unknown>(endpoint: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) => Promise<TResponse>;
|
|
174
|
+
delete: <TResponse = unknown>(endpoint: string, options?: Omit<RequestOptions, "method">) => Promise<TResponse>;
|
|
175
|
+
}
|
|
176
|
+
type FetchClient = FetchClientInstance;
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/domain/types.d.ts
|
|
179
|
+
interface OutputOptions {
|
|
180
|
+
format: "json" | "table";
|
|
181
|
+
compact: boolean;
|
|
182
|
+
jq?: string;
|
|
183
|
+
}
|
|
184
|
+
interface CommandContext {
|
|
185
|
+
getClient(): Promise<FetchClient>;
|
|
186
|
+
output(data: unknown): void;
|
|
187
|
+
parseBody(raw: string): unknown;
|
|
188
|
+
verbose: boolean;
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/domain/command.d.ts
|
|
192
|
+
type RegisterFn = (parent: Command, ctx: CommandContext) => void;
|
|
193
|
+
declare function createDomainCommand(name: string, description: string, register: RegisterFn): Command;
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/domain/context.d.ts
|
|
196
|
+
declare function createCommandContext(opts: {
|
|
197
|
+
token?: string;
|
|
198
|
+
baseUrl?: string;
|
|
199
|
+
profile?: string;
|
|
200
|
+
format: "json" | "table";
|
|
201
|
+
compact: boolean;
|
|
202
|
+
jq?: string;
|
|
203
|
+
verbose: boolean;
|
|
204
|
+
}): CommandContext;
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/domain/output.d.ts
|
|
207
|
+
declare function formatOutput(data: unknown, options: OutputOptions): string;
|
|
208
|
+
//#endregion
|
|
209
|
+
export { type CliError, type CommandContext, type CompanyChoice, type CompanyInfo, type ConfirmMfaResponse, type Failure, type FetchClient, type FluidApiError, type FluidConfig, type FluidPlugin, type FluidProfile, type MfaResponse, type OutputOptions, type PluginContext, type Result, type Success, confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
|
|
210
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/utils/errors.ts","../src/utils/result.ts","../src/auth/fluid-api.ts","../src/auth/token.ts","../src/plugins/types.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/domain/types.ts","../src/domain/command.ts","../src/domain/context.ts","../src/domain/output.ts"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { C as tryCatchAsync, S as tryCatch, T as unwrapOr, _ as isNodeError, a as updateConfig, b as mapResult, c as getConfigFilePath, d as sendMfa, f as validateToken, g as isFailure, h as isError, i as readConfig, l as createDefaultConfig, m as getErrorMessage, n as getAuthToken, o as writeConfig, p as failure, r as listProfileNames, s as getConfigDir, t as getActiveProfile, u as confirmMfa, v as isSuccess, w as unwrap, x as success, y as mapError } from "./token-DCpSVmEk.mjs";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
//#region ../../platform/api-client-core/src/fetch-client.ts
|
|
4
|
+
/**
|
|
5
|
+
* API Error class compatible with fluid-admin's ApiError
|
|
6
|
+
*/
|
|
7
|
+
var ApiError = class ApiError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
data;
|
|
10
|
+
constructor(message, status, data) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "ApiError";
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.data = data;
|
|
15
|
+
if ("captureStackTrace" in Error) Error.captureStackTrace(this, ApiError);
|
|
16
|
+
}
|
|
17
|
+
toJSON() {
|
|
18
|
+
return {
|
|
19
|
+
name: this.name,
|
|
20
|
+
message: this.message,
|
|
21
|
+
status: this.status,
|
|
22
|
+
data: this.data
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Creates a configured fetch client instance
|
|
28
|
+
*/
|
|
29
|
+
function createFetchClient(config) {
|
|
30
|
+
const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {} } = config;
|
|
31
|
+
/**
|
|
32
|
+
* Build headers for a request
|
|
33
|
+
*/
|
|
34
|
+
async function buildHeaders(customHeaders) {
|
|
35
|
+
const headers = {
|
|
36
|
+
Accept: "application/json",
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
...defaultHeaders,
|
|
39
|
+
...customHeaders
|
|
40
|
+
};
|
|
41
|
+
if (getAuthToken) {
|
|
42
|
+
const token = await getAuthToken();
|
|
43
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
44
|
+
}
|
|
45
|
+
return headers;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Join baseUrl + endpoint via string concatenation (matches fetchApi).
|
|
49
|
+
* Using `new URL(endpoint, baseUrl)` would strip any path prefix from
|
|
50
|
+
* baseUrl (e.g. "/api") when the endpoint starts with "/".
|
|
51
|
+
*/
|
|
52
|
+
function joinUrl(endpoint) {
|
|
53
|
+
return `${baseUrl}${endpoint}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build URL with query parameters for GET requests
|
|
57
|
+
* Compatible with fluid-admin's query param handling
|
|
58
|
+
*/
|
|
59
|
+
function buildUrl(endpoint, params) {
|
|
60
|
+
const fullUrl = joinUrl(endpoint);
|
|
61
|
+
if (!params || Object.keys(params).length === 0) return fullUrl;
|
|
62
|
+
const queryString = new URLSearchParams();
|
|
63
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
64
|
+
if (value === void 0 || value === null) return;
|
|
65
|
+
if (Array.isArray(value)) value.forEach((item) => queryString.append(`${key}[]`, String(item)));
|
|
66
|
+
else if (typeof value === "object") Object.entries(value).forEach(([subKey, subValue]) => {
|
|
67
|
+
if (subValue === void 0 || subValue === null) return;
|
|
68
|
+
if (Array.isArray(subValue)) subValue.forEach((item) => queryString.append(`${key}[${subKey}][]`, String(item)));
|
|
69
|
+
else queryString.append(`${key}[${subKey}]`, String(subValue));
|
|
70
|
+
});
|
|
71
|
+
else queryString.append(key, String(value));
|
|
72
|
+
});
|
|
73
|
+
const qs = queryString.toString();
|
|
74
|
+
return qs ? `${fullUrl}?${qs}` : fullUrl;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Shared response handler for both JSON and FormData requests.
|
|
78
|
+
* Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.
|
|
79
|
+
*/
|
|
80
|
+
async function handleResponse(response, method, _url) {
|
|
81
|
+
if (response.status === 401 && onAuthError) onAuthError();
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
const errorText = await response.text().catch(() => "");
|
|
84
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
85
|
+
let data;
|
|
86
|
+
try {
|
|
87
|
+
data = JSON.parse(errorText);
|
|
88
|
+
} catch {
|
|
89
|
+
throw new ApiError(errorText.slice(0, 200) || `${method} request failed with status ${response.status}`, response.status, null);
|
|
90
|
+
}
|
|
91
|
+
throw new ApiError(data.message || data.error_message || `${method} request failed`, response.status, data.errors || data);
|
|
92
|
+
} else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
93
|
+
}
|
|
94
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") return null;
|
|
95
|
+
if (response.headers.get("content-type")?.includes("application/json")) try {
|
|
96
|
+
return await response.json();
|
|
97
|
+
} catch {
|
|
98
|
+
try {
|
|
99
|
+
return await response.text();
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Main request function
|
|
108
|
+
*/
|
|
109
|
+
async function request(endpoint, options = {}) {
|
|
110
|
+
const { method = "GET", headers: customHeaders, params, body, signal } = options;
|
|
111
|
+
const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);
|
|
112
|
+
const headers = await buildHeaders(customHeaders);
|
|
113
|
+
let response;
|
|
114
|
+
try {
|
|
115
|
+
const fetchOptions = {
|
|
116
|
+
method,
|
|
117
|
+
headers
|
|
118
|
+
};
|
|
119
|
+
const serializedBody = body && method !== "GET" ? JSON.stringify(body) : null;
|
|
120
|
+
if (serializedBody) fetchOptions.body = serializedBody;
|
|
121
|
+
if (signal) fetchOptions.signal = signal;
|
|
122
|
+
response = await fetch(url, fetchOptions);
|
|
123
|
+
} catch (networkError) {
|
|
124
|
+
throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
125
|
+
}
|
|
126
|
+
return handleResponse(response, method, url);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Request with FormData (for file uploads)
|
|
130
|
+
*/
|
|
131
|
+
async function requestWithFormData(endpoint, formData, options = {}) {
|
|
132
|
+
const { method = "POST", headers: customHeaders, signal } = options;
|
|
133
|
+
const url = joinUrl(endpoint);
|
|
134
|
+
const headers = await buildHeaders(customHeaders);
|
|
135
|
+
delete headers["Content-Type"];
|
|
136
|
+
let response;
|
|
137
|
+
try {
|
|
138
|
+
const fetchOptions = {
|
|
139
|
+
method,
|
|
140
|
+
headers,
|
|
141
|
+
body: formData
|
|
142
|
+
};
|
|
143
|
+
if (signal) fetchOptions.signal = signal;
|
|
144
|
+
response = await fetch(url, fetchOptions);
|
|
145
|
+
} catch (networkError) {
|
|
146
|
+
throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
147
|
+
}
|
|
148
|
+
return handleResponse(response, method, url);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
request,
|
|
152
|
+
requestWithFormData,
|
|
153
|
+
get: (endpoint, params, options) => request(endpoint, {
|
|
154
|
+
...options,
|
|
155
|
+
method: "GET",
|
|
156
|
+
...params && { params }
|
|
157
|
+
}),
|
|
158
|
+
post: (endpoint, body, options) => request(endpoint, {
|
|
159
|
+
...options,
|
|
160
|
+
method: "POST",
|
|
161
|
+
body
|
|
162
|
+
}),
|
|
163
|
+
put: (endpoint, body, options) => request(endpoint, {
|
|
164
|
+
...options,
|
|
165
|
+
method: "PUT",
|
|
166
|
+
body
|
|
167
|
+
}),
|
|
168
|
+
patch: (endpoint, body, options) => request(endpoint, {
|
|
169
|
+
...options,
|
|
170
|
+
method: "PATCH",
|
|
171
|
+
body
|
|
172
|
+
}),
|
|
173
|
+
delete: (endpoint, options) => request(endpoint, {
|
|
174
|
+
...options,
|
|
175
|
+
method: "DELETE"
|
|
176
|
+
})
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/domain/output.ts
|
|
181
|
+
function formatOutput(data, options) {
|
|
182
|
+
let result = data;
|
|
183
|
+
if (options.jq) result = extractPath(result, options.jq);
|
|
184
|
+
if (options.format === "table") return formatTable(result);
|
|
185
|
+
if (options.compact) return JSON.stringify(result);
|
|
186
|
+
return JSON.stringify(result, null, 2);
|
|
187
|
+
}
|
|
188
|
+
function extractPath(data, path) {
|
|
189
|
+
const parts = path.replace(/^\.*/, "").split(/\.|\[/).filter(Boolean).map((p) => p.replace(/\]$/, ""));
|
|
190
|
+
let current = data;
|
|
191
|
+
for (const part of parts) {
|
|
192
|
+
if (current == null) return void 0;
|
|
193
|
+
current = current[part];
|
|
194
|
+
}
|
|
195
|
+
return current;
|
|
196
|
+
}
|
|
197
|
+
function formatTable(data) {
|
|
198
|
+
if (Array.isArray(data)) {
|
|
199
|
+
if (data.length === 0) return "(empty)";
|
|
200
|
+
if (typeof data[0] === "object" && data[0] !== null) {
|
|
201
|
+
const keys = Object.keys(data[0]);
|
|
202
|
+
const widths = keys.map((k) => Math.max(k.length, ...data.map((row) => String(row[k] ?? "").length)));
|
|
203
|
+
return [
|
|
204
|
+
keys.map((k, i) => k.toUpperCase().padEnd(widths[i])).join(" "),
|
|
205
|
+
widths.map((w) => "-".repeat(w)).join(" "),
|
|
206
|
+
...data.map((row) => keys.map((k, i) => String(row[k] ?? "").padEnd(widths[i])).join(" "))
|
|
207
|
+
].join("\n");
|
|
208
|
+
}
|
|
209
|
+
return data.map(String).join("\n");
|
|
210
|
+
}
|
|
211
|
+
if (typeof data === "object" && data !== null) {
|
|
212
|
+
const entries = Object.entries(data);
|
|
213
|
+
const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
|
|
214
|
+
return entries.map(([k, v]) => `${k.padEnd(maxKeyLen)} ${JSON.stringify(v)}`).join("\n");
|
|
215
|
+
}
|
|
216
|
+
return String(data);
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/domain/context.ts
|
|
220
|
+
function createCommandContext(opts) {
|
|
221
|
+
let clientInstance = null;
|
|
222
|
+
const outputOptions = {
|
|
223
|
+
format: opts.format,
|
|
224
|
+
compact: opts.compact,
|
|
225
|
+
jq: opts.jq
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
verbose: opts.verbose,
|
|
229
|
+
async getClient() {
|
|
230
|
+
if (clientInstance) return clientInstance;
|
|
231
|
+
const token = resolveToken(opts);
|
|
232
|
+
if (!token) {
|
|
233
|
+
if (opts.profile) throw new Error(`No API token found for profile "${opts.profile}". Run \`fluid login\` for that profile or provide --token <token>`);
|
|
234
|
+
throw new Error("No API token found. Run `fluid login` or provide --token <token>");
|
|
235
|
+
}
|
|
236
|
+
clientInstance = createFetchClient({
|
|
237
|
+
baseUrl: opts.baseUrl ?? process.env["FLUID_API_BASE"] ?? "https://api.fluid.app",
|
|
238
|
+
getAuthToken: () => token
|
|
239
|
+
});
|
|
240
|
+
return clientInstance;
|
|
241
|
+
},
|
|
242
|
+
output(data) {
|
|
243
|
+
const formatted = formatOutput(data, outputOptions);
|
|
244
|
+
process.stdout.write(formatted + "\n");
|
|
245
|
+
},
|
|
246
|
+
parseBody(raw) {
|
|
247
|
+
try {
|
|
248
|
+
return JSON.parse(raw);
|
|
249
|
+
} catch {
|
|
250
|
+
throw new Error(`Invalid JSON body: ${raw}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function resolveToken(opts) {
|
|
256
|
+
if (opts.token) return opts.token;
|
|
257
|
+
if (opts.profile) return getAuthToken(opts.profile);
|
|
258
|
+
const envToken = process.env["FLUID_TOKEN"] ?? process.env["FLUID_API_TOKEN"];
|
|
259
|
+
if (envToken) return envToken;
|
|
260
|
+
return getAuthToken();
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/domain/command.ts
|
|
264
|
+
function createDomainCommand(name, description, register) {
|
|
265
|
+
const cmd = new Command(name).description(description).enablePositionalOptions(false).option("--token <token>", "API authentication token").option("--base-url <url>", "API base URL").option("--profile <name>", "Config profile name").option("--format <format>", "Output format (json|table)", "json").option("--compact", "Compact JSON output", false).option("--jq <path>", "Extract value at JSON path").option("--verbose", "Print request details to stderr", false);
|
|
266
|
+
let resolvedCtx = null;
|
|
267
|
+
function getCtx() {
|
|
268
|
+
if (!resolvedCtx) {
|
|
269
|
+
const opts = cmd.opts();
|
|
270
|
+
resolvedCtx = createCommandContext({
|
|
271
|
+
token: opts.token,
|
|
272
|
+
baseUrl: opts.baseUrl,
|
|
273
|
+
profile: opts.profile,
|
|
274
|
+
format: opts.format,
|
|
275
|
+
compact: opts.compact,
|
|
276
|
+
jq: opts.jq,
|
|
277
|
+
verbose: opts.verbose
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return resolvedCtx;
|
|
281
|
+
}
|
|
282
|
+
register(cmd, {
|
|
283
|
+
get verbose() {
|
|
284
|
+
return getCtx().verbose;
|
|
285
|
+
},
|
|
286
|
+
getClient() {
|
|
287
|
+
return getCtx().getClient();
|
|
288
|
+
},
|
|
289
|
+
output(data) {
|
|
290
|
+
return getCtx().output(data);
|
|
291
|
+
},
|
|
292
|
+
parseBody(raw) {
|
|
293
|
+
return getCtx().parseBody(raw);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
return cmd;
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
export { confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
|
|
300
|
+
|
|
301
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../platform/api-client-core/src/fetch-client.ts","../src/domain/output.ts","../src/domain/context.ts","../src/domain/command.ts"],"sourcesContent":["/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n public readonly data: unknown;\n\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): { name: string; message: string; status: number; data: unknown } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n };\n }\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {} } = config;\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]\n value.forEach((item) => queryString.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n );\n }\n const msg = (data.message || data.error_message) as string | undefined;\n throw new ApiError(\n msg || `${method} request failed`,\n response.status,\n data.errors || data,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n try {\n const data = await response.json();\n return data as TResponse;\n } catch {\n try {\n // API declared JSON content-type but body isn't valid JSON\n const text = await response.text();\n return text as TResponse;\n } catch {\n return null as TResponse;\n }\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetch(url, fetchOptions);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const { method = \"POST\", headers: customHeaders, signal } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (signal) fetchOptions.signal = signal;\n response = await fetch(url, fetchOptions);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","import type { OutputOptions } from \"./types.js\";\n\nexport function formatOutput(data: unknown, options: OutputOptions): string {\n let result = data;\n\n if (options.jq) {\n result = extractPath(result, options.jq);\n }\n\n if (options.format === \"table\") {\n return formatTable(result);\n }\n\n if (options.compact) {\n return JSON.stringify(result);\n }\n return JSON.stringify(result, null, 2);\n}\n\nfunction extractPath(data: unknown, path: string): unknown {\n const parts = path\n .replace(/^\\.*/, \"\")\n .split(/\\.|\\[/)\n .filter(Boolean)\n .map((p) => p.replace(/\\]$/, \"\"));\n\n let current: any = data;\n for (const part of parts) {\n if (current == null) return undefined;\n current = current[part];\n }\n return current;\n}\n\nfunction formatTable(data: unknown): string {\n if (Array.isArray(data)) {\n if (data.length === 0) return \"(empty)\";\n\n if (typeof data[0] === \"object\" && data[0] !== null) {\n const keys = Object.keys(data[0]);\n const widths = keys.map((k) =>\n Math.max(k.length, ...data.map((row) => String(row[k] ?? \"\").length)),\n );\n\n const header = keys\n .map((k, i) => k.toUpperCase().padEnd(widths[i]!))\n .join(\" \");\n const separator = widths.map((w) => \"-\".repeat(w)).join(\" \");\n const rows = data.map((row) =>\n keys.map((k, i) => String(row[k] ?? \"\").padEnd(widths[i]!)).join(\" \"),\n );\n\n return [header, separator, ...rows].join(\"\\n\");\n }\n\n return data.map(String).join(\"\\n\");\n }\n\n if (typeof data === \"object\" && data !== null) {\n const entries = Object.entries(data);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([k, v]) => `${k.padEnd(maxKeyLen)} ${JSON.stringify(v)}`)\n .join(\"\\n\");\n }\n\n return String(data);\n}\n","import { createFetchClient } from \"@fluid-app/api-client-core\";\nimport type { FetchClient } from \"@fluid-app/api-client-core\";\n\nimport { getAuthToken } from \"../auth/token.js\";\nimport { formatOutput } from \"./output.js\";\nimport type { CommandContext, OutputOptions } from \"./types.js\";\n\nexport function createCommandContext(opts: {\n token?: string;\n baseUrl?: string;\n profile?: string;\n format: \"json\" | \"table\";\n compact: boolean;\n jq?: string;\n verbose: boolean;\n}): CommandContext {\n let clientInstance: FetchClient | null = null;\n\n const outputOptions: OutputOptions = {\n format: opts.format,\n compact: opts.compact,\n jq: opts.jq,\n };\n\n return {\n verbose: opts.verbose,\n\n async getClient(): Promise<FetchClient> {\n if (clientInstance) return clientInstance;\n\n const token = resolveToken(opts);\n if (!token) {\n if (opts.profile) {\n throw new Error(\n `No API token found for profile \"${opts.profile}\". Run \\`fluid login\\` for that profile or provide --token <token>`,\n );\n }\n\n throw new Error(\n \"No API token found. Run `fluid login` or provide --token <token>\",\n );\n }\n\n const baseUrl =\n opts.baseUrl ??\n process.env[\"FLUID_API_BASE\"] ??\n \"https://api.fluid.app\";\n\n clientInstance = createFetchClient({\n baseUrl,\n getAuthToken: () => token,\n });\n\n return clientInstance;\n },\n\n output(data: unknown): void {\n const formatted = formatOutput(data, outputOptions);\n process.stdout.write(formatted + \"\\n\");\n },\n\n parseBody(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON body: ${raw}`);\n }\n },\n };\n}\n\nfunction resolveToken(opts: {\n token?: string;\n profile?: string;\n}): string | null {\n if (opts.token) return opts.token;\n\n if (opts.profile) return getAuthToken(opts.profile);\n\n const envToken = process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"];\n if (envToken) return envToken;\n\n return getAuthToken();\n}\n","import { Command } from \"commander\";\n\nimport { createCommandContext } from \"./context.js\";\nimport type { CommandContext } from \"./types.js\";\n\ntype RegisterFn = (parent: Command, ctx: CommandContext) => void;\n\nexport function createDomainCommand(\n name: string,\n description: string,\n register: RegisterFn,\n): Command {\n const cmd = new Command(name)\n .description(description)\n // Keep global domain flags usable before or after nested subcommands.\n .enablePositionalOptions(false)\n .option(\"--token <token>\", \"API authentication token\")\n .option(\"--base-url <url>\", \"API base URL\")\n .option(\"--profile <name>\", \"Config profile name\")\n .option(\"--format <format>\", \"Output format (json|table)\", \"json\")\n .option(\"--compact\", \"Compact JSON output\", false)\n .option(\"--jq <path>\", \"Extract value at JSON path\")\n .option(\"--verbose\", \"Print request details to stderr\", false);\n\n let resolvedCtx: CommandContext | null = null;\n\n function getCtx(): CommandContext {\n if (!resolvedCtx) {\n const opts = cmd.opts();\n resolvedCtx = createCommandContext({\n token: opts.token,\n baseUrl: opts.baseUrl,\n profile: opts.profile,\n format: opts.format as \"json\" | \"table\",\n compact: opts.compact,\n jq: opts.jq,\n verbose: opts.verbose,\n });\n }\n return resolvedCtx;\n }\n\n const lazyCtx: CommandContext = {\n get verbose() {\n return getCtx().verbose;\n },\n getClient() {\n return getCtx().getClient();\n },\n output(data: unknown) {\n return getCtx().output(data);\n },\n parseBody(raw: string) {\n return getCtx().parseBody(raw);\n },\n };\n\n register(cmd, lazyCtx);\n\n return cmd;\n}\n"],"mappings":"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"}
|