@settlemint/dalp-cli 3.1.33-main.43433008600 → 3.1.33-release.43440124932

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.
@@ -1,102 +0,0 @@
1
- const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
2
-
3
- export interface StatusEvent {
4
- readonly phase: string;
5
- readonly message: string;
6
- }
7
-
8
- export interface CliStatusReporter {
9
- readonly update: (event: StatusEvent) => StatusEvent | undefined;
10
- readonly succeed: (message: string) => StatusEvent | undefined;
11
- readonly fail: (message: string) => StatusEvent | undefined;
12
- readonly stop: () => void;
13
- }
14
-
15
- export interface CreateCliStatusOptions {
16
- readonly agent: boolean;
17
- readonly stream?: { write: (chunk: string) => unknown; isTTY?: boolean };
18
- readonly isTty?: boolean;
19
- }
20
-
21
- export function createCliStatus(options: CreateCliStatusOptions): CliStatusReporter {
22
- const stream = options.stream ?? process.stderr;
23
- const isTty = options.isTty ?? Boolean("isTTY" in stream && stream.isTTY);
24
- const human = options.agent !== true;
25
- let frame = 0;
26
- let currentMessage = "";
27
- let timer: ReturnType<typeof setInterval> | undefined;
28
- let spinning = false;
29
-
30
- const clearLine = (): void => {
31
- if (human && isTty) {
32
- stream.write("\r\u001B[K");
33
- }
34
- };
35
-
36
- const paint = (): void => {
37
- if (!human || !isTty || !spinning) {
38
- return;
39
- }
40
- const glyph = SPINNER_FRAMES[frame % SPINNER_FRAMES.length]!;
41
- stream.write(`\r\u001B[K${glyph} ${currentMessage}`);
42
- frame += 1;
43
- };
44
-
45
- const startSpinner = (): void => {
46
- if (!human || !isTty || spinning) {
47
- return;
48
- }
49
- spinning = true;
50
- timer = setInterval(paint, 80);
51
- paint();
52
- };
53
-
54
- const stopSpinner = (): void => {
55
- if (timer !== undefined) {
56
- clearInterval(timer);
57
- timer = undefined;
58
- }
59
- if (spinning) {
60
- clearLine();
61
- spinning = false;
62
- }
63
- };
64
-
65
- const emitHuman = (prefix: string, message: string): void => {
66
- stopSpinner();
67
- if (!human) {
68
- return;
69
- }
70
- if (isTty) {
71
- stream.write(`${prefix} ${message}\n`);
72
- } else {
73
- stream.write(`${message}\n`);
74
- }
75
- };
76
-
77
- return {
78
- update(event) {
79
- currentMessage = event.message;
80
- if (!options.agent) {
81
- if (isTty) {
82
- startSpinner();
83
- paint();
84
- } else {
85
- stream.write(`${event.message}\n`);
86
- }
87
- }
88
- return options.agent ? event : undefined;
89
- },
90
- succeed(message) {
91
- emitHuman("✔", message);
92
- return options.agent ? { phase: "done", message } : undefined;
93
- },
94
- fail(message) {
95
- emitHuman("✖", message);
96
- return options.agent ? { phase: "error", message } : undefined;
97
- },
98
- stop() {
99
- stopSpinner();
100
- },
101
- };
102
- }
@@ -1,41 +0,0 @@
1
- export interface CoverageOperation {
2
- readonly name: string;
3
- readonly hintKey: string;
4
- }
5
-
6
- export function findSurfaceGaps(
7
- sdkOperations: ReadonlyArray<string>,
8
- hasSurface: (name: string) => boolean
9
- ): Array<string> {
10
- return sdkOperations.filter((name) => !hasSurface(name));
11
- }
12
-
13
- const MANIFEST_HINT_KEY = /\b[A-Z]+ \/\S+/g;
14
-
15
- export function findMountGaps(operations: ReadonlyArray<CoverageOperation>, manifest: string): Array<string> {
16
- const mounted = new Set(manifest.match(MANIFEST_HINT_KEY));
17
- return operations.filter((operation) => !mounted.has(operation.hintKey)).map((operation) => operation.name);
18
- }
19
-
20
- export interface ExemptionGaps {
21
- readonly stale: ReadonlyArray<string>;
22
- readonly reasonless: ReadonlyArray<string>;
23
- }
24
-
25
- export function findExemptionGaps(
26
- exemptions: ReadonlyMap<string, string>,
27
- sdkOperations: ReadonlyArray<string>
28
- ): ExemptionGaps {
29
- const live = new Set(sdkOperations);
30
- const stale: Array<string> = [];
31
- const reasonless: Array<string> = [];
32
- for (const [name, reason] of exemptions) {
33
- if (!live.has(name)) {
34
- stale.push(name);
35
- }
36
- if (reason.trim().length === 0) {
37
- reasonless.push(name);
38
- }
39
- }
40
- return { stale, reasonless };
41
- }
@@ -1,256 +0,0 @@
1
- import { chmod, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { z } from "zod";
4
- import { getConfigDir } from "./cli-config.ts";
5
- import { makeCliError, isCliFailure } from "./errors.ts";
6
-
7
- const KEYCHAIN_ENTRY_SEPARATOR_PATTERN = /\n(?=keychain:)/u;
8
- const ACCOUNT_PATTERN = /"acct"<blob>="([^"]*)"/u;
9
-
10
- const SERVICE_NAME = "dalp-cli";
11
- const SECURITY_INTERACTIVE_MAX_LINE_BYTES = 4094;
12
- const SECURITY_QUOTED_ESCAPE_PATTERN = /["\\]/gu;
13
- const SECURITY_LINE_BREAK_PATTERN = /[\n\r\0]/u;
14
-
15
- const StoredCredentialsSchema = z.object({
16
- apiKey: z.string().min(1),
17
- apiKeyId: z.string().min(1),
18
- userEmail: z.string().min(1),
19
- userId: z.string().min(1),
20
- activeOrgId: z.string().min(1).optional(),
21
- apiUrl: z.url(),
22
- });
23
-
24
- export type StoredCredentials = z.infer<typeof StoredCredentialsSchema>;
25
-
26
- export interface CredentialStoreOptions {
27
- readonly configDir?: string;
28
- readonly platform?: NodeJS.Platform;
29
- readonly runSecurity?: (
30
- args: ReadonlyArray<string>,
31
- stdin?: string
32
- ) => Promise<{ readonly exitCode: number; readonly stdout: string }>;
33
- }
34
-
35
- const defaultRunSecurity = async (
36
- args: ReadonlyArray<string>,
37
- stdin?: string
38
- ): Promise<{ readonly exitCode: number; readonly stdout: string }> => {
39
- const proc = Bun.spawn(["security", ...args], {
40
- stdin: stdin === undefined ? "ignore" : new TextEncoder().encode(stdin),
41
- stdout: "pipe",
42
- stderr: "pipe",
43
- });
44
- const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
45
- return { exitCode, stdout };
46
- };
47
-
48
- function credentialsFile(configDir: string): string {
49
- return path.join(configDir, "credentials.json");
50
- }
51
-
52
- async function keychainAccounts(
53
- runSecurity: NonNullable<CredentialStoreOptions["runSecurity"]>
54
- ): Promise<Array<string>> {
55
- const dump = await runSecurity(["dump-keychain"]);
56
- if (dump.exitCode !== 0) {
57
- throw makeCliError({
58
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
59
- message: `Keychain dump failed with exit code ${dump.exitCode}`,
60
- });
61
- }
62
- const accounts = new Set<string>();
63
- for (const entry of dump.stdout.split(KEYCHAIN_ENTRY_SEPARATOR_PATTERN)) {
64
- if (!entry.includes(`"svce"<blob>="${SERVICE_NAME}"`)) {
65
- continue;
66
- }
67
- const account = entry.match(ACCOUNT_PATTERN)?.[1];
68
- if (account !== undefined) {
69
- accounts.add(account);
70
- }
71
- }
72
- return [...accounts];
73
- }
74
-
75
- async function deleteKeychainAccount(
76
- runSecurity: NonNullable<CredentialStoreOptions["runSecurity"]>,
77
- account: string
78
- ): Promise<void> {
79
- const result = await runSecurity(["delete-generic-password", "-s", SERVICE_NAME, "-a", account]);
80
- if (result.exitCode !== 0) {
81
- throw makeCliError({
82
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
83
- message: `Keychain delete failed for ${account} with exit code ${result.exitCode}`,
84
- });
85
- }
86
- }
87
-
88
- function quoteSecurityArgument(value: string): string {
89
- return `"${value.replaceAll(SECURITY_QUOTED_ESCAPE_PATTERN, (character) => `\\${character}`)}"`;
90
- }
91
-
92
- async function addKeychainPassword(
93
- runSecurity: NonNullable<CredentialStoreOptions["runSecurity"]>,
94
- account: string,
95
- password: string
96
- ): Promise<void> {
97
- const command = [
98
- "add-generic-password",
99
- "-s",
100
- quoteSecurityArgument(SERVICE_NAME),
101
- "-a",
102
- quoteSecurityArgument(account),
103
- "-X",
104
- Buffer.from(password, "utf8").toString("hex"),
105
- "-U",
106
- ].join(" ");
107
- if (
108
- SECURITY_LINE_BREAK_PATTERN.test(account) ||
109
- Buffer.byteLength(command, "utf8") > SECURITY_INTERACTIVE_MAX_LINE_BYTES
110
- ) {
111
- throw makeCliError({
112
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
113
- message: `The ${SERVICE_NAME} keychain item for ${JSON.stringify(account)} cannot be passed to the macOS security tool as one command line.`,
114
- });
115
- }
116
- const result = await runSecurity(["-i"], `${command}\n`);
117
- if (result.exitCode !== 0) {
118
- throw makeCliError({
119
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
120
- message: `Keychain save failed with exit code ${result.exitCode}`,
121
- });
122
- }
123
- }
124
-
125
- function resolvePlatform(options: CredentialStoreOptions): NodeJS.Platform {
126
- if (options.platform !== undefined) {
127
- return options.platform;
128
- }
129
- const override = process.env.DALP_CREDENTIAL_PLATFORM;
130
- if (override === "darwin" || override === "linux" || override === "win32") {
131
- return override;
132
- }
133
- return process.platform;
134
- }
135
-
136
- export async function saveCredentials(
137
- credentials: StoredCredentials,
138
- options: CredentialStoreOptions = {}
139
- ): Promise<void> {
140
- const parsed = StoredCredentialsSchema.parse(credentials);
141
- const configDir = options.configDir ?? getConfigDir();
142
- const platform = resolvePlatform(options);
143
- const runSecurity = options.runSecurity ?? defaultRunSecurity;
144
- const encoded = JSON.stringify(parsed);
145
-
146
- if (platform === "darwin") {
147
- const existing = await keychainAccounts(runSecurity);
148
- await addKeychainPassword(runSecurity, parsed.userEmail, encoded);
149
- try {
150
- for (const account of existing.filter((name) => name !== parsed.userEmail)) {
151
- await deleteKeychainAccount(runSecurity, account);
152
- }
153
- } catch (error) {
154
- if (!existing.includes(parsed.userEmail)) {
155
- await runSecurity(["delete-generic-password", "-s", SERVICE_NAME, "-a", parsed.userEmail]);
156
- }
157
- throw error;
158
- }
159
- return;
160
- }
161
-
162
- await mkdir(configDir, { recursive: true, mode: 0o700 });
163
- const file = credentialsFile(configDir);
164
- await writeFile(file, encoded, { mode: 0o600 });
165
- await chmod(file, 0o600);
166
- }
167
-
168
- export async function loadCredentials(options: CredentialStoreOptions = {}): Promise<StoredCredentials | undefined> {
169
- const configDir = options.configDir ?? getConfigDir();
170
- const platform = resolvePlatform(options);
171
- const runSecurity = options.runSecurity ?? defaultRunSecurity;
172
-
173
- let encoded: string;
174
- if (platform === "darwin") {
175
- const accounts = await keychainAccounts(runSecurity);
176
- if (accounts.length === 0) {
177
- return undefined;
178
- }
179
- if (accounts.length > 1) {
180
- throw makeCliError({
181
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
182
- message: `Multiple ${SERVICE_NAME} keychain accounts found (${accounts.join(", ")}). Remove the stale account or keep exactly one DALP CLI login before retrying.`,
183
- });
184
- }
185
- const result = await runSecurity(["find-generic-password", "-s", SERVICE_NAME, "-a", accounts[0]!, "-w"]);
186
- if (result.exitCode !== 0) {
187
- throw makeCliError({
188
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
189
- message: `The ${SERVICE_NAME} keychain item for ${accounts[0]!} could not be read (exit code ${result.exitCode}). Run 'dalp logout' to remove it, then 'dalp login'.`,
190
- });
191
- }
192
- encoded = result.stdout.trim();
193
- } else {
194
- const file = credentialsFile(configDir);
195
- try {
196
- const stats = await Bun.file(file).stat();
197
- if ((stats.mode & 0o077) !== 0) {
198
- throw makeCliError({
199
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
200
- message: `Credentials file has insecure permissions: ${(stats.mode & 0o777).toString(8)}. Run 'chmod 600 ${file}' to fix.`,
201
- });
202
- }
203
- encoded = await readFile(file, "utf8");
204
- } catch (error) {
205
- if (isCliFailure(error)) {
206
- throw error;
207
- }
208
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
209
- return undefined;
210
- }
211
- throw makeCliError({
212
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
213
- message: error instanceof Error ? error.message : "Failed to load credentials",
214
- cause: error,
215
- });
216
- }
217
- }
218
-
219
- try {
220
- return StoredCredentialsSchema.parse(JSON.parse(encoded));
221
- } catch (error) {
222
- throw makeCliError({
223
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
224
- message: "Stored credentials are invalid or corrupt.",
225
- why: "The credential payload failed Zod validation.",
226
- fix: "Run 'dalp logout' then 'dalp login' to refresh credentials.",
227
- cause: error,
228
- });
229
- }
230
- }
231
-
232
- export async function deleteCredentials(options: CredentialStoreOptions = {}): Promise<void> {
233
- const configDir = options.configDir ?? getConfigDir();
234
- const platform = resolvePlatform(options);
235
- const runSecurity = options.runSecurity ?? defaultRunSecurity;
236
-
237
- if (platform === "darwin") {
238
- for (const account of await keychainAccounts(runSecurity)) {
239
- await deleteKeychainAccount(runSecurity, account);
240
- }
241
- return;
242
- }
243
-
244
- try {
245
- await unlink(credentialsFile(configDir));
246
- } catch (error) {
247
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
248
- return;
249
- }
250
- throw makeCliError({
251
- code: "kit.cli.CREDENTIAL_STORE_FAILED",
252
- message: error instanceof Error ? error.message : "Failed to delete credentials",
253
- cause: error,
254
- });
255
- }
256
- }
package/src/lib/errors.ts DELETED
@@ -1,207 +0,0 @@
1
- import { findDalpSdkError, isDalpApiError } from "@settlemint/dalp-sdk";
2
- import { buildErrorCta, extractResourceLinks, type CliCtaBlock, type ResourceLinks } from "./cli-cta.ts";
3
- import type { GatedFeature } from "./feature-gated-operations.ts";
4
-
5
- export interface CliErrorContext {
6
- readonly code: string;
7
- readonly fallbackMessage: string;
8
- readonly fallbackWhy: string;
9
- readonly fallbackFix: string;
10
- readonly gatedFeature?: GatedFeature;
11
- }
12
-
13
- export interface RenderedCliError {
14
- readonly code: string;
15
- readonly message: string;
16
- readonly retryable: boolean;
17
- readonly cta: CliCtaBlock;
18
- }
19
-
20
- export class CliError extends Error {
21
- override readonly name = "CliError";
22
- readonly code: string;
23
- readonly retryable: boolean;
24
- readonly why: string;
25
- readonly fix: string;
26
- readonly links?: ResourceLinks;
27
- readonly category?: string;
28
-
29
- constructor(options: {
30
- readonly message: string;
31
- readonly code: string;
32
- readonly retryable?: boolean;
33
- readonly why?: string;
34
- readonly fix?: string;
35
- readonly cause?: unknown;
36
- readonly links?: ResourceLinks;
37
- readonly category?: string;
38
- }) {
39
- super(options.message, options.cause === undefined ? undefined : { cause: options.cause });
40
- this.code = options.code;
41
- this.retryable = options.retryable ?? false;
42
- this.why = options.why ?? "The CLI operation failed.";
43
- this.fix = options.fix ?? "Correct the input or environment, then retry.";
44
- if (options.links !== undefined) {
45
- this.links = options.links;
46
- }
47
- if (options.category !== undefined) {
48
- this.category = options.category;
49
- }
50
- }
51
- }
52
-
53
- export function makeCliError(options: ConstructorParameters<typeof CliError>[0]): CliError {
54
- return new CliError(options);
55
- }
56
-
57
- export function isCliFailure(error: unknown): error is CliError {
58
- return (
59
- typeof error === "object" &&
60
- error !== null &&
61
- Reflect.get(error, "name") === "CliError" &&
62
- typeof Reflect.get(error, "code") === "string" &&
63
- typeof Reflect.get(error, "message") === "string"
64
- );
65
- }
66
-
67
- export interface NormalizedFailure {
68
- readonly code: string;
69
- readonly message: string;
70
- readonly why: string;
71
- readonly fix: string;
72
- readonly retryable: boolean;
73
- readonly category?: string;
74
- readonly links?: ResourceLinks;
75
- }
76
-
77
- function isRecord(value: unknown): value is Record<string, unknown> {
78
- return typeof value === "object" && value !== null && !Array.isArray(value);
79
- }
80
-
81
- function extractWireFailure(error: unknown): NormalizedFailure | undefined {
82
- if (!isRecord(error)) {
83
- return undefined;
84
- }
85
- const message = typeof error.message === "string" ? error.message : undefined;
86
- const code = typeof error.code === "string" ? error.code : undefined;
87
- const data = isRecord(error.data) ? error.data : undefined;
88
-
89
- const whyFromData = data !== undefined && typeof data.why === "string" ? data.why : undefined;
90
- const fixFromData = data !== undefined && typeof data.fix === "string" ? data.fix : undefined;
91
- const whyTop = typeof error.why === "string" ? error.why : undefined;
92
- const fixTop = typeof error.fix === "string" ? error.fix : undefined;
93
- const why = whyFromData ?? whyTop;
94
- const fix = fixFromData ?? fixTop;
95
-
96
- if (message === undefined && why === undefined && code === undefined) {
97
- return undefined;
98
- }
99
- if (code === undefined && why === undefined) {
100
- return undefined;
101
- }
102
-
103
- const retryableFromData = data !== undefined && typeof data.retryable === "boolean" ? data.retryable : undefined;
104
- const retryableTop = typeof error.retryable === "boolean" ? error.retryable : undefined;
105
- const categoryFromData = data !== undefined && typeof data.category === "string" ? data.category : undefined;
106
- const categoryTop = typeof error.category === "string" ? error.category : undefined;
107
- const idFromData = data !== undefined && typeof data.id === "string" ? data.id : undefined;
108
-
109
- const links = extractResourceLinks(error) ?? (data !== undefined ? extractResourceLinks(data) : undefined);
110
-
111
- return {
112
- code: code ?? idFromData ?? "API_ERROR",
113
- message: message ?? code ?? "Request failed",
114
- why: why ?? "The DALP API rejected the request.",
115
- fix: fix ?? "Correct the input or environment, then retry.",
116
- retryable: retryableFromData ?? retryableTop ?? false,
117
- ...(categoryFromData !== undefined || categoryTop !== undefined
118
- ? { category: categoryFromData ?? categoryTop }
119
- : {}),
120
- ...(links === undefined ? {} : { links }),
121
- };
122
- }
123
-
124
- function normalizeFailure(error: unknown, context: CliErrorContext): NormalizedFailure {
125
- if (isDalpApiError(error)) {
126
- return {
127
- code: error.code,
128
- message: error.message,
129
- why: error.why,
130
- fix: error.fix,
131
- retryable: error.retryable,
132
- category: error.category,
133
- ...(extractResourceLinks(error) === undefined ? {} : { links: extractResourceLinks(error) }),
134
- };
135
- }
136
-
137
- const sdkError = findDalpSdkError(error);
138
- if (sdkError !== undefined) {
139
- return {
140
- code: sdkError.code,
141
- message: sdkError.message,
142
- why: sdkError.why,
143
- fix: sdkError.fix,
144
- retryable: sdkError.retryable,
145
- category: sdkError.category,
146
- };
147
- }
148
-
149
- if (isCliFailure(error)) {
150
- return {
151
- code: error.code,
152
- message: error.message,
153
- why: error.why,
154
- fix: error.fix,
155
- retryable: error.retryable,
156
- ...(error.category === undefined ? {} : { category: error.category }),
157
- ...(error.links === undefined ? {} : { links: error.links }),
158
- };
159
- }
160
-
161
- const wire = extractWireFailure(error);
162
- if (wire !== undefined) {
163
- return wire;
164
- }
165
-
166
- const message = error instanceof Error ? error.message : context.fallbackMessage;
167
- return {
168
- code: context.code,
169
- message,
170
- why: context.fallbackWhy,
171
- fix: context.fallbackFix,
172
- retryable: false,
173
- };
174
- }
175
-
176
- const ROUTE_NOT_FOUND_CODE = "ROUTE_NOT_FOUND";
177
-
178
- function featureNotEnabledFailure(feature: GatedFeature): NormalizedFailure {
179
- return {
180
- code: "FEATURE_NOT_ENABLED",
181
- message: `${feature.name} is not enabled on this deployment.`,
182
- why: `The platform answered with an unknown route because ${feature.name} is switched off (${feature.setting}), so it does not serve this command.`,
183
- fix: `Ask the operator of this deployment to switch ${feature.name} on, or use a deployment that has it enabled.`,
184
- retryable: false,
185
- category: "domain",
186
- };
187
- }
188
-
189
- export function renderSdkErrorForCli(error: unknown, context: CliErrorContext): RenderedCliError {
190
- const normalized = normalizeFailure(error, context);
191
- const failure =
192
- context.gatedFeature !== undefined && normalized.code === ROUTE_NOT_FOUND_CODE
193
- ? featureNotEnabledFailure(context.gatedFeature)
194
- : normalized;
195
- return {
196
- code: failure.code,
197
- message: failure.message,
198
- retryable: failure.retryable,
199
- cta: buildErrorCta({
200
- why: failure.why,
201
- fix: failure.fix,
202
- code: failure.code,
203
- ...(failure.category === undefined ? {} : { category: failure.category }),
204
- ...(failure.links === undefined ? {} : { links: failure.links }),
205
- }),
206
- };
207
- }
@@ -1,18 +0,0 @@
1
- export interface GatedFeature {
2
- readonly name: string;
3
- readonly setting: string;
4
- }
5
-
6
- const FEATURE_GATED_OPERATION_GROUPS: Readonly<Record<string, GatedFeature>> = {
7
- cantonV2: { name: "Canton", setting: "features.canton" },
8
- };
9
-
10
- export const gatedFeatureOfOperation = (operationName: string): GatedFeature | undefined => {
11
- const group = operationName.split(".", 1)[0] ?? "";
12
- return Object.hasOwn(FEATURE_GATED_OPERATION_GROUPS, group) ? FEATURE_GATED_OPERATION_GROUPS[group] : undefined;
13
- };
14
-
15
- export const gatedFeatureContext = (operationName: string): { readonly gatedFeature?: GatedFeature } => {
16
- const gatedFeature = gatedFeatureOfOperation(operationName);
17
- return gatedFeature === undefined ? {} : { gatedFeature };
18
- };
@@ -1,41 +0,0 @@
1
- export interface SurfaceParam {
2
- readonly name: string;
3
- readonly in: "path" | "query" | "header";
4
- readonly required: boolean;
5
- readonly description: string | null;
6
- readonly type: string | null;
7
- readonly enum: ReadonlyArray<string> | null;
8
- readonly pattern: string | null;
9
- readonly examples: ReadonlyArray<string>;
10
- readonly default: unknown;
11
- readonly fields: ReadonlyArray<SurfaceParam>;
12
- }
13
-
14
- export interface SurfaceOperation {
15
- readonly name: string;
16
- readonly method: string;
17
- readonly url: string;
18
- readonly summary: string | null;
19
- readonly description: string | null;
20
- readonly help: string;
21
- readonly tags: ReadonlyArray<string>;
22
- readonly pathParams: ReadonlyArray<SurfaceParam>;
23
- readonly queryParams: ReadonlyArray<SurfaceParam>;
24
- readonly headerParams: ReadonlyArray<SurfaceParam>;
25
- readonly requestBodyDescription: string | null;
26
- readonly hasRequestBody: boolean;
27
- readonly bodyIsComplex: boolean;
28
- readonly bodyFields: ReadonlyArray<SurfaceParam>;
29
- readonly examples: ReadonlyArray<{
30
- readonly description: string;
31
- readonly path?: Record<string, string>;
32
- readonly query?: Record<string, string>;
33
- }>;
34
- }
35
-
36
- export interface OpenApiSurface {
37
- readonly openapiSource: string;
38
- readonly sdkSource: string;
39
- readonly operations: Record<string, SurfaceOperation>;
40
- readonly tags: Record<string, string>;
41
- }
@@ -1,17 +0,0 @@
1
- import path from "node:path";
2
- import surfaceAssetPath from "../generated/openapi-surface.json" with { type: "file" };
3
- import type { OpenApiSurface, SurfaceOperation } from "./openapi-surface-types.ts";
4
-
5
- export type { OpenApiSurface, SurfaceOperation, SurfaceParam } from "./openapi-surface-types.ts";
6
-
7
- const surfaceFile = path.resolve(import.meta.dir, surfaceAssetPath);
8
- const openApiSurface = (await Bun.file(surfaceFile).json()) as OpenApiSurface;
9
-
10
- export function surfaceFor(operationName: string): SurfaceOperation | undefined {
11
- return openApiSurface.operations[operationName];
12
- }
13
-
14
- export function tagDescription(tag: string): string | undefined {
15
- const value = openApiSurface.tags[tag];
16
- return value !== undefined && value.trim() !== "" ? value : undefined;
17
- }