@kryshtop/bstack 1.0.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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +324 -0
  3. package/dist/api/http/BrowserStackHttpClient.d.ts +30 -0
  4. package/dist/api/http/BrowserStackHttpClient.js +111 -0
  5. package/dist/api/http/BrowserStackHttpClient.js.map +1 -0
  6. package/dist/api/http/errors.d.ts +11 -0
  7. package/dist/api/http/errors.js +31 -0
  8. package/dist/api/http/errors.js.map +1 -0
  9. package/dist/api/http/multipart.d.ts +13 -0
  10. package/dist/api/http/multipart.js +28 -0
  11. package/dist/api/http/multipart.js.map +1 -0
  12. package/dist/api/normalizers/common.d.ts +7 -0
  13. package/dist/api/normalizers/common.js +106 -0
  14. package/dist/api/normalizers/common.js.map +1 -0
  15. package/dist/api/registry/EndpointRegistry.d.ts +10 -0
  16. package/dist/api/registry/EndpointRegistry.js +34 -0
  17. package/dist/api/registry/EndpointRegistry.js.map +1 -0
  18. package/dist/api/registry/definitions.d.ts +2 -0
  19. package/dist/api/registry/definitions.js +510 -0
  20. package/dist/api/registry/definitions.js.map +1 -0
  21. package/dist/api/registry/types.d.ts +23 -0
  22. package/dist/api/registry/types.js +2 -0
  23. package/dist/api/registry/types.js.map +1 -0
  24. package/dist/auth/AuthService.d.ts +24 -0
  25. package/dist/auth/AuthService.js +100 -0
  26. package/dist/auth/AuthService.js.map +1 -0
  27. package/dist/bin.d.ts +2 -0
  28. package/dist/bin.js +4 -0
  29. package/dist/bin.js.map +1 -0
  30. package/dist/cli/context.d.ts +39 -0
  31. package/dist/cli/context.js +117 -0
  32. package/dist/cli/context.js.map +1 -0
  33. package/dist/cli/output.d.ts +29 -0
  34. package/dist/cli/output.js +143 -0
  35. package/dist/cli/output.js.map +1 -0
  36. package/dist/cli/program.d.ts +4 -0
  37. package/dist/cli/program.js +60 -0
  38. package/dist/cli/program.js.map +1 -0
  39. package/dist/cli/runCli.d.ts +1 -0
  40. package/dist/cli/runCli.js +31 -0
  41. package/dist/cli/runCli.js.map +1 -0
  42. package/dist/commands/auth.d.ts +3 -0
  43. package/dist/commands/auth.js +73 -0
  44. package/dist/commands/auth.js.map +1 -0
  45. package/dist/commands/explorer.d.ts +3 -0
  46. package/dist/commands/explorer.js +86 -0
  47. package/dist/commands/explorer.js.map +1 -0
  48. package/dist/commands/framework.d.ts +3 -0
  49. package/dist/commands/framework.js +474 -0
  50. package/dist/commands/framework.js.map +1 -0
  51. package/dist/config/paths.d.ts +5 -0
  52. package/dist/config/paths.js +22 -0
  53. package/dist/config/paths.js.map +1 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.js +16 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/menus/interactiveMenu.d.ts +2 -0
  58. package/dist/menus/interactiveMenu.js +1106 -0
  59. package/dist/menus/interactiveMenu.js.map +1 -0
  60. package/dist/menus/screenModel.d.ts +12 -0
  61. package/dist/menus/screenModel.js +152 -0
  62. package/dist/menus/screenModel.js.map +1 -0
  63. package/dist/prompts/authPrompts.d.ts +10 -0
  64. package/dist/prompts/authPrompts.js +46 -0
  65. package/dist/prompts/authPrompts.js.map +1 -0
  66. package/dist/services/ResourceService.d.ts +25 -0
  67. package/dist/services/ResourceService.js +80 -0
  68. package/dist/services/ResourceService.js.map +1 -0
  69. package/dist/services/frameworkConfigs.d.ts +16 -0
  70. package/dist/services/frameworkConfigs.js +108 -0
  71. package/dist/services/frameworkConfigs.js.map +1 -0
  72. package/dist/storage/CredentialStore.d.ts +11 -0
  73. package/dist/storage/CredentialStore.js +2 -0
  74. package/dist/storage/CredentialStore.js.map +1 -0
  75. package/dist/storage/FileCredentialStores.d.ts +18 -0
  76. package/dist/storage/FileCredentialStores.js +74 -0
  77. package/dist/storage/FileCredentialStores.js.map +1 -0
  78. package/dist/storage/FileStorage.d.ts +3 -0
  79. package/dist/storage/FileStorage.js +18 -0
  80. package/dist/storage/FileStorage.js.map +1 -0
  81. package/dist/storage/KeytarCredentialStore.d.ts +9 -0
  82. package/dist/storage/KeytarCredentialStore.js +40 -0
  83. package/dist/storage/KeytarCredentialStore.js.map +1 -0
  84. package/dist/storage/SessionRepository.d.ts +28 -0
  85. package/dist/storage/SessionRepository.js +151 -0
  86. package/dist/storage/SessionRepository.js.map +1 -0
  87. package/dist/types/domain.d.ts +85 -0
  88. package/dist/types/domain.js +2 -0
  89. package/dist/types/domain.js.map +1 -0
  90. package/dist/utils/constants.d.ts +10 -0
  91. package/dist/utils/constants.js +11 -0
  92. package/dist/utils/constants.js.map +1 -0
  93. package/dist/utils/errors.d.ts +2 -0
  94. package/dist/utils/errors.js +12 -0
  95. package/dist/utils/errors.js.map +1 -0
  96. package/dist/utils/files.d.ts +4 -0
  97. package/dist/utils/files.js +28 -0
  98. package/dist/utils/files.js.map +1 -0
  99. package/dist/utils/json.d.ts +2 -0
  100. package/dist/utils/json.js +10 -0
  101. package/dist/utils/json.js.map +1 -0
  102. package/dist/utils/mask.d.ts +2 -0
  103. package/dist/utils/mask.js +10 -0
  104. package/dist/utils/mask.js.map +1 -0
  105. package/dist/utils/query.d.ts +1 -0
  106. package/dist/utils/query.js +17 -0
  107. package/dist/utils/query.js.map +1 -0
  108. package/package.json +92 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Open Source Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,324 @@
1
+ # bstack
2
+
3
+ BrowserStack App Automate SDK and CLI for uploads, builds, sessions, and media workflows.
4
+
5
+ [![Build](https://github.com/kryshtop/bstack/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/kryshtop/bstack/actions/workflows/build.yml)
6
+ [![Tests](https://github.com/kryshtop/bstack/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/kryshtop/bstack/actions/workflows/unit-tests.yml)
7
+ [![Version](https://img.shields.io/badge/version-1.0.0-blue?style=flat-square)](https://github.com/kryshtop/bstack/blob/main/package.json)
8
+ [![GitHub Stars](https://img.shields.io/badge/stars-view%20on%20GitHub-181717?style=flat-square&logo=github)](https://github.com/kryshtop/bstack/stargazers)
9
+ [![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](https://github.com/kryshtop/bstack/blob/main/LICENSE)
10
+
11
+ `@kryshtop/bstack` is a CLI-first BrowserStack App Automate package that ships both:
12
+
13
+ - a CLI binary: `bstack`
14
+ - a TypeScript/JavaScript SDK for registry-driven BrowserStack App Automate API access
15
+
16
+ It is designed for local operator workflows and CI scripting. In most cases you should install it globally, or as a project-local `devDependency` when you want a pinned CLI version inside a repository. The package also exposes an SDK surface for advanced programmatic use, but it is primarily intended as an operator utility rather than a runtime dependency of application packages.
17
+
18
+ ## Features
19
+
20
+ - Interactive terminal UI for common BrowserStack App Automate workflows
21
+ - Scriptable command mode for CI and shell automation
22
+ - SDK exports for HTTP client, registry, services, normalizers, and types
23
+ - Local credential persistence with OS keychain preference
24
+
25
+ ## Requirements
26
+
27
+ - Node.js `>=22.20.0`
28
+ - npm `>=10`
29
+
30
+ ## Installation
31
+
32
+ ### Recommended: global CLI usage
33
+
34
+ ```bash
35
+ npm install -g @kryshtop/bstack
36
+ bstack --help
37
+ ```
38
+
39
+ ### Project-local CLI usage
40
+
41
+ ```bash
42
+ npm install -D @kryshtop/bstack
43
+ npx bstack --help
44
+ ```
45
+
46
+ ### From a local tarball
47
+
48
+ ```bash
49
+ npm pack
50
+ npm install -D ./kryshtop-bstack-1.0.0.tgz
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ### CLI
56
+
57
+ ```bash
58
+ bstack auth login
59
+ bstack auth status
60
+ bstack appium apps list
61
+ ```
62
+
63
+ If installed locally instead of globally:
64
+
65
+ ```bash
66
+ npx @kryshtop/bstack --help
67
+ ```
68
+
69
+ ### SDK
70
+
71
+ If you intentionally want to use the package programmatically, install it in the way that matches your project policy. For CLI-only use, prefer the global or `devDependency` installs above.
72
+
73
+ ```ts
74
+ import {
75
+ BrowserStackHttpClient,
76
+ EndpointRegistry,
77
+ ResourceService,
78
+ endpointDefinitions,
79
+ } from '@kryshtop/bstack';
80
+
81
+ const registry = new EndpointRegistry(endpointDefinitions);
82
+ const http = new BrowserStackHttpClient({
83
+ username: process.env.BSTACK_USERNAME!,
84
+ accessKey: process.env.BSTACK_ACCESS_KEY!,
85
+ storageStrategy: 'plain-file',
86
+ createdAt: new Date().toISOString(),
87
+ updatedAt: new Date().toISOString(),
88
+ });
89
+
90
+ const service = new ResourceService(registry, http);
91
+ const builds = await service.listBuilds('appium');
92
+ console.log(builds);
93
+ ```
94
+
95
+ ## CLI Usage
96
+
97
+ ### Interactive mode
98
+
99
+ Run with no subcommand:
100
+
101
+ ```bash
102
+ bstack
103
+ ```
104
+
105
+ or explicitly:
106
+
107
+ ```bash
108
+ bstack menu
109
+ ```
110
+
111
+ ### Command mode
112
+
113
+ Global options:
114
+
115
+ - `--json`
116
+ - `--debug-http`
117
+ - `--verbose`
118
+ - `--master-key <key>`
119
+ - `--allow-plain-storage`
120
+ - `--base-url <url>`
121
+
122
+ Examples:
123
+
124
+ ```bash
125
+ bstack auth login
126
+ bstack auth validate
127
+ bstack auth status --json
128
+ bstack appium apps upload ./MyApp.apk --custom-id SampleApp
129
+ bstack appium builds list --status running
130
+ bstack maestro suites upload ./maestro-suite.zip
131
+ bstack xcuitest sessions get <sessionId> --build <buildId>
132
+ bstack media upload ./fixtures/profile.png
133
+ bstack explorer
134
+ ```
135
+
136
+ ## SDK Exports
137
+
138
+ The package root exports:
139
+
140
+ - `AuthService`
141
+ - `BrowserStackHttpClient`
142
+ - `BrowserStackApiError`
143
+ - `buildMultipartPayload`
144
+ - `EndpointRegistry`
145
+ - `endpointDefinitions`
146
+ - `ResourceService`
147
+ - `frameworkDescriptors`
148
+ - `getFrameworkDescriptor`
149
+ - `validateUploadPath`
150
+ - `CommandRuntime`
151
+ - `createProgram`
152
+ - `runCli`
153
+ - config path helpers
154
+ - normalizers
155
+ - public domain types
156
+
157
+ Internal implementation files are not exported through the package root.
158
+
159
+ ## Authentication
160
+
161
+ The CLI uses BrowserStack username and access key.
162
+
163
+ Supported sources:
164
+
165
+ - interactive prompt
166
+ - explicit CLI flags
167
+ - environment variables you provide in your shell or CI
168
+
169
+ ### Login examples
170
+
171
+ Interactive:
172
+
173
+ ```bash
174
+ bstack auth login
175
+ ```
176
+
177
+ Non-interactive:
178
+
179
+ ```bash
180
+ bstack auth login --username "$BSTACK_USERNAME" --access-key "$BSTACK_ACCESS_KEY"
181
+ ```
182
+
183
+ Encrypted local storage:
184
+
185
+ ```bash
186
+ export BSTACK_MASTER_KEY='choose-a-strong-local-master-key'
187
+ bstack auth login \
188
+ --username "$BSTACK_USERNAME" \
189
+ --access-key "$BSTACK_ACCESS_KEY" \
190
+ --storage encrypted-file
191
+ ```
192
+
193
+ Plain-text storage is explicit only:
194
+
195
+ ```bash
196
+ bstack auth login \
197
+ --username "$BSTACK_USERNAME" \
198
+ --access-key "$BSTACK_ACCESS_KEY" \
199
+ --storage plain-file \
200
+ --allow-plain-storage
201
+ ```
202
+
203
+ ## Environment Variables
204
+
205
+ See [.env.example](./.env.example).
206
+
207
+ Supported variables:
208
+
209
+ - `BSTACK_USERNAME`
210
+ - `BSTACK_ACCESS_KEY`
211
+ - `BSTACK_MASTER_KEY`
212
+ - `BSTACK_BASE_URL`
213
+ - `BSTACK_HTTP_TIMEOUT_MS`
214
+
215
+ ## Credential and Session Storage
216
+
217
+ Credentials are stored outside the repository root.
218
+
219
+ Storage order for `--storage auto`:
220
+
221
+ 1. OS keychain via optional `keytar`
222
+ 2. encrypted file using your master key
223
+ 3. plain-text file only if you explicitly allow it
224
+
225
+ Typical user config locations:
226
+
227
+ - macOS: `~/Library/Application Support/bstack`
228
+ - Linux: `~/.config/bstack`
229
+
230
+ Persisted files may include:
231
+
232
+ - `config.json`
233
+ - `session.enc`
234
+ - `session.json`
235
+ - `last-response.json`
236
+
237
+ Secrets are not bundled into the npm package.
238
+
239
+ ## Framework Coverage
240
+
241
+ ### Appium
242
+
243
+ - apps: upload, list, group list, delete
244
+ - builds: list, get
245
+ - sessions: list, get, update status
246
+ - plan and usage validation
247
+
248
+ ### Maestro
249
+
250
+ - apps: upload, list, get, delete
251
+ - test suites: upload, list, get, delete
252
+ - builds: run, list, get, stop
253
+ - sessions: get
254
+
255
+ ### Espresso
256
+
257
+ - apps: upload, list, get, delete
258
+ - test suites: upload, list, get, delete
259
+ - builds: run, list, get, stop
260
+ - sessions: get
261
+
262
+ ### Flutter Android
263
+
264
+ - apps: upload, list, get, delete
265
+ - test suites: upload, list, get, delete
266
+ - builds: run, list, get, stop
267
+ - sessions: get
268
+
269
+ ### Flutter iOS
270
+
271
+ - test packages: upload, list, get, delete
272
+ - builds: run, list, get, stop
273
+ - sessions: get
274
+
275
+ ### Detox Android
276
+
277
+ - apps: upload
278
+ - app-client: upload
279
+ - sessions: get
280
+
281
+ ### XCUITest
282
+
283
+ - apps: upload, list, get, delete
284
+ - test suites: upload, list, get, delete
285
+ - builds: run, list, get, stop
286
+ - sessions: get
287
+
288
+ ### Media
289
+
290
+ - upload, list, group list, delete
291
+
292
+ ## Media Notes
293
+
294
+ Media uploads return BrowserStack `media_url` values that you can plug into framework-specific execution payloads or capabilities.
295
+
296
+ The package preserves raw BrowserStack responses instead of forcing a guessed capability abstraction.
297
+
298
+ ## Troubleshooting
299
+
300
+ ### No saved session found
301
+
302
+ ```bash
303
+ bstack auth login
304
+ ```
305
+
306
+ ### Keychain support unavailable
307
+
308
+ Use encrypted-file storage with a master key:
309
+
310
+ ```bash
311
+ bstack auth login --storage encrypted-file --master-key '<key>'
312
+ ```
313
+
314
+ ### Encrypted session cannot be decrypted
315
+
316
+ Set the same `BSTACK_MASTER_KEY` used when the session was created, or log in again and choose another storage backend.
317
+
318
+ ### Unsupported operation
319
+
320
+ The requested command may not be available for that framework. Check `bstack help-frameworks` to see the currently supported operations.
321
+
322
+ ## Star History
323
+
324
+ [![Star History Chart](https://api.star-history.com/svg?repos=kryshtop/bstack&type=Date)](https://www.star-history.com/#kryshtop/bstack&Date)
@@ -0,0 +1,30 @@
1
+ import { type Method } from 'axios';
2
+ import type { StoredSession } from '../../types/domain.js';
3
+ import type { MultipartInput } from './multipart.js';
4
+ export interface HttpClientOptions {
5
+ baseUrl?: string;
6
+ timeoutMs?: number;
7
+ retryAttempts?: number;
8
+ debugHttp?: boolean;
9
+ }
10
+ export interface RequestOptions {
11
+ method: Method;
12
+ path: string;
13
+ query?: Record<string, unknown>;
14
+ data?: unknown;
15
+ headers?: Record<string, string>;
16
+ }
17
+ export declare class BrowserStackHttpClient {
18
+ private readonly session;
19
+ private readonly options;
20
+ private readonly client;
21
+ private readonly timeoutMs;
22
+ private readonly retryAttempts;
23
+ constructor(session: StoredSession, options?: HttpClientOptions);
24
+ getAuthHeader(): string;
25
+ requestRaw<T = unknown>(options: RequestOptions): Promise<T>;
26
+ requestJson<T = unknown>(options: RequestOptions): Promise<T>;
27
+ uploadMultipart<T = unknown>(path: string, input: MultipartInput, fields?: Record<string, unknown>): Promise<T>;
28
+ private performRequest;
29
+ private buildPath;
30
+ }
@@ -0,0 +1,111 @@
1
+ import axios from 'axios';
2
+ import { DEFAULT_BASE_URL, DEFAULT_HTTP_TIMEOUT_MS, DEFAULT_RETRY_ATTEMPTS, } from '../../utils/constants.js';
3
+ import { buildQuery } from '../../utils/query.js';
4
+ import { BrowserStackApiError, isRetryableStatus, mapAxiosError } from './errors.js';
5
+ import { buildMultipartPayload } from './multipart.js';
6
+ export class BrowserStackHttpClient {
7
+ session;
8
+ options;
9
+ client;
10
+ timeoutMs;
11
+ retryAttempts;
12
+ constructor(session, options = {}) {
13
+ this.session = session;
14
+ this.options = options;
15
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
16
+ this.retryAttempts = options.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS;
17
+ this.client = axios.create({
18
+ baseURL: options.baseUrl ?? process.env.BSTACK_BASE_URL ?? DEFAULT_BASE_URL,
19
+ timeout: this.timeoutMs,
20
+ auth: {
21
+ username: session.username,
22
+ password: session.accessKey,
23
+ },
24
+ validateStatus: (status) => status >= 200 && status < 300,
25
+ });
26
+ }
27
+ getAuthHeader() {
28
+ return `Basic ${Buffer.from(`${this.session.username}:${this.session.accessKey}`).toString('base64')}`;
29
+ }
30
+ async requestRaw(options) {
31
+ return this.performRequest({
32
+ method: options.method,
33
+ url: this.buildPath(options.path, options.query),
34
+ data: options.data,
35
+ headers: options.headers,
36
+ });
37
+ }
38
+ async requestJson(options) {
39
+ return this.requestRaw(options);
40
+ }
41
+ async uploadMultipart(path, input, fields) {
42
+ const { form, headers } = await buildMultipartPayload({
43
+ ...input,
44
+ fields: normalizeFormFields(fields),
45
+ });
46
+ return this.performRequest({
47
+ method: 'POST',
48
+ url: path,
49
+ data: form,
50
+ headers,
51
+ maxContentLength: Infinity,
52
+ maxBodyLength: Infinity,
53
+ });
54
+ }
55
+ async performRequest(config) {
56
+ let attempt = 0;
57
+ while (true) {
58
+ try {
59
+ if (this.options.debugHttp) {
60
+ const method = String(config.method ?? 'GET').toUpperCase();
61
+ process.stderr.write(`[http] ${method} ${config.url}\n`);
62
+ }
63
+ const response = await this.client.request(config);
64
+ return response.data;
65
+ }
66
+ catch (error) {
67
+ attempt += 1;
68
+ if (axios.isAxiosError(error)) {
69
+ const mapped = mapAxiosError(error);
70
+ if (attempt < this.retryAttempts && mapped.retryable) {
71
+ await sleep(backoffMs(attempt, mapped.statusCode));
72
+ continue;
73
+ }
74
+ throw mapped;
75
+ }
76
+ const fallback = new BrowserStackApiError({
77
+ code: 'UNKNOWN_HTTP_ERROR',
78
+ message: error instanceof Error ? error.message : 'Unknown HTTP error',
79
+ retryable: false,
80
+ });
81
+ if (attempt < this.retryAttempts && isRetryableStatus(fallback.statusCode)) {
82
+ await sleep(backoffMs(attempt, fallback.statusCode));
83
+ continue;
84
+ }
85
+ throw fallback;
86
+ }
87
+ }
88
+ }
89
+ buildPath(path, query) {
90
+ const params = buildQuery(query ?? {});
91
+ const queryString = params.toString();
92
+ return queryString ? `${path}?${queryString}` : path;
93
+ }
94
+ }
95
+ function sleep(ms) {
96
+ return new Promise((resolve) => setTimeout(resolve, ms));
97
+ }
98
+ function backoffMs(attempt, statusCode) {
99
+ const base = statusCode === 429 ? 1_500 : 700;
100
+ return base * 2 ** (attempt - 1);
101
+ }
102
+ function normalizeFormFields(input) {
103
+ const normalized = {};
104
+ for (const [key, value] of Object.entries(input ?? {})) {
105
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
106
+ normalized[key] = value;
107
+ }
108
+ }
109
+ return normalized;
110
+ }
111
+ //# sourceMappingURL=BrowserStackHttpClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserStackHttpClient.js","sourceRoot":"","sources":["../../../src/api/http/BrowserStackHttpClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { AxiosError } from 'axios';
2
+ import type { ApiErrorShape } from '../../types/domain.js';
3
+ export declare class BrowserStackApiError extends Error {
4
+ readonly statusCode?: number;
5
+ readonly code: string;
6
+ readonly details?: unknown;
7
+ readonly retryable: boolean;
8
+ constructor(shape: ApiErrorShape);
9
+ }
10
+ export declare function isRetryableStatus(statusCode?: number): boolean;
11
+ export declare function mapAxiosError(error: AxiosError): BrowserStackApiError;
@@ -0,0 +1,31 @@
1
+ export class BrowserStackApiError extends Error {
2
+ statusCode;
3
+ code;
4
+ details;
5
+ retryable;
6
+ constructor(shape) {
7
+ super(shape.message);
8
+ this.name = 'BrowserStackApiError';
9
+ this.statusCode = shape.statusCode;
10
+ this.code = shape.code;
11
+ this.details = shape.details;
12
+ this.retryable = shape.retryable;
13
+ }
14
+ }
15
+ export function isRetryableStatus(statusCode) {
16
+ return statusCode === 429 || Boolean(statusCode && statusCode >= 500);
17
+ }
18
+ export function mapAxiosError(error) {
19
+ const statusCode = error.response?.status;
20
+ const message = typeof error.response?.data === 'string'
21
+ ? error.response.data
22
+ : error.message || 'BrowserStack API request failed.';
23
+ return new BrowserStackApiError({
24
+ statusCode,
25
+ code: error.code ?? 'HTTP_ERROR',
26
+ message,
27
+ details: error.response?.data,
28
+ retryable: isRetryableStatus(statusCode) || error.code === 'ECONNABORTED',
29
+ });
30
+ }
31
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/api/http/errors.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7B,UAAU,CAAU;IACpB,IAAI,CAAS;IACb,OAAO,CAAW;IAClB,SAAS,CAAU;IAEnC,YAAmB,KAAoB;QACrC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IACnC,CAAC;CACF;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAmB;IACnD,OAAO,UAAU,KAAK,GAAG,IAAI,OAAO,CAAC,UAAU,IAAI,UAAU,IAAI,GAAG,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1C,MAAM,OAAO,GACX,OAAO,KAAK,CAAC,QAAQ,EAAE,IAAI,KAAK,QAAQ;QACtC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI;QACrB,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,kCAAkC,CAAC;IAE1D,OAAO,IAAI,oBAAoB,CAAC;QAC9B,UAAU;QACV,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,YAAY;QAChC,OAAO;QACP,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI;QAC7B,SAAS,EAAE,iBAAiB,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;KAC1E,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,13 @@
1
+ import FormData from 'form-data';
2
+ export interface MultipartInput {
3
+ filePath?: string;
4
+ fileFieldName?: string;
5
+ fileName?: string;
6
+ url?: string;
7
+ urlFieldName?: string;
8
+ fields?: Record<string, string | number | boolean | undefined>;
9
+ }
10
+ export declare function buildMultipartPayload(input: MultipartInput): Promise<{
11
+ form: FormData;
12
+ headers: Record<string, string>;
13
+ }>;
@@ -0,0 +1,28 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import FormData from 'form-data';
3
+ import mime from 'mime-types';
4
+ import { assertReadableFile } from '../../utils/files.js';
5
+ export async function buildMultipartPayload(input) {
6
+ const form = new FormData();
7
+ if (input.filePath) {
8
+ await assertReadableFile(input.filePath);
9
+ form.append(input.fileFieldName ?? 'file', createReadStream(input.filePath), {
10
+ contentType: mime.lookup(input.filePath) || 'application/octet-stream',
11
+ filename: input.fileName,
12
+ });
13
+ }
14
+ if (input.url) {
15
+ form.append(input.urlFieldName ?? 'url', input.url);
16
+ }
17
+ for (const [key, value] of Object.entries(input.fields ?? {})) {
18
+ if (value === undefined) {
19
+ continue;
20
+ }
21
+ form.append(key, String(value));
22
+ }
23
+ return {
24
+ form,
25
+ headers: form.getHeaders(),
26
+ };
27
+ }
28
+ //# sourceMappingURL=multipart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multipart.js","sourceRoot":"","sources":["../../../src/api/http/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,YAAY,CAAC;AAE9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAW1D,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAAqB;IAErB,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAE5B,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,MAAM,kBAAkB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,IAAI,MAAM,EAAE,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE;YAC3E,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,0BAA0B;YACtE,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QAC9D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,IAAI,CAAC,UAAU,EAA4B;KACrD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { BuildSummary, SessionSummary, UploadedArtifact } from '../../types/domain.js';
2
+ export declare function normalizeArtifact(input: unknown): UploadedArtifact;
3
+ export declare function normalizeBuildSummary(input: unknown): BuildSummary;
4
+ export declare function normalizeSessionSummary(input: unknown): SessionSummary;
5
+ export declare function normalizeArtifactCollection(input: unknown): UploadedArtifact[];
6
+ export declare function normalizeBuildCollection(input: unknown): BuildSummary[];
7
+ export declare function normalizeSessionCollection(input: unknown): SessionSummary[];