@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.
- package/LICENSE +21 -0
- package/README.md +324 -0
- package/dist/api/http/BrowserStackHttpClient.d.ts +30 -0
- package/dist/api/http/BrowserStackHttpClient.js +111 -0
- package/dist/api/http/BrowserStackHttpClient.js.map +1 -0
- package/dist/api/http/errors.d.ts +11 -0
- package/dist/api/http/errors.js +31 -0
- package/dist/api/http/errors.js.map +1 -0
- package/dist/api/http/multipart.d.ts +13 -0
- package/dist/api/http/multipart.js +28 -0
- package/dist/api/http/multipart.js.map +1 -0
- package/dist/api/normalizers/common.d.ts +7 -0
- package/dist/api/normalizers/common.js +106 -0
- package/dist/api/normalizers/common.js.map +1 -0
- package/dist/api/registry/EndpointRegistry.d.ts +10 -0
- package/dist/api/registry/EndpointRegistry.js +34 -0
- package/dist/api/registry/EndpointRegistry.js.map +1 -0
- package/dist/api/registry/definitions.d.ts +2 -0
- package/dist/api/registry/definitions.js +510 -0
- package/dist/api/registry/definitions.js.map +1 -0
- package/dist/api/registry/types.d.ts +23 -0
- package/dist/api/registry/types.js +2 -0
- package/dist/api/registry/types.js.map +1 -0
- package/dist/auth/AuthService.d.ts +24 -0
- package/dist/auth/AuthService.js +100 -0
- package/dist/auth/AuthService.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli/context.d.ts +39 -0
- package/dist/cli/context.js +117 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/output.d.ts +29 -0
- package/dist/cli/output.js +143 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/program.d.ts +4 -0
- package/dist/cli/program.js +60 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/runCli.d.ts +1 -0
- package/dist/cli/runCli.js +31 -0
- package/dist/cli/runCli.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.js +73 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/explorer.d.ts +3 -0
- package/dist/commands/explorer.js +86 -0
- package/dist/commands/explorer.js.map +1 -0
- package/dist/commands/framework.d.ts +3 -0
- package/dist/commands/framework.js +474 -0
- package/dist/commands/framework.js.map +1 -0
- package/dist/config/paths.d.ts +5 -0
- package/dist/config/paths.js +22 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/menus/interactiveMenu.d.ts +2 -0
- package/dist/menus/interactiveMenu.js +1106 -0
- package/dist/menus/interactiveMenu.js.map +1 -0
- package/dist/menus/screenModel.d.ts +12 -0
- package/dist/menus/screenModel.js +152 -0
- package/dist/menus/screenModel.js.map +1 -0
- package/dist/prompts/authPrompts.d.ts +10 -0
- package/dist/prompts/authPrompts.js +46 -0
- package/dist/prompts/authPrompts.js.map +1 -0
- package/dist/services/ResourceService.d.ts +25 -0
- package/dist/services/ResourceService.js +80 -0
- package/dist/services/ResourceService.js.map +1 -0
- package/dist/services/frameworkConfigs.d.ts +16 -0
- package/dist/services/frameworkConfigs.js +108 -0
- package/dist/services/frameworkConfigs.js.map +1 -0
- package/dist/storage/CredentialStore.d.ts +11 -0
- package/dist/storage/CredentialStore.js +2 -0
- package/dist/storage/CredentialStore.js.map +1 -0
- package/dist/storage/FileCredentialStores.d.ts +18 -0
- package/dist/storage/FileCredentialStores.js +74 -0
- package/dist/storage/FileCredentialStores.js.map +1 -0
- package/dist/storage/FileStorage.d.ts +3 -0
- package/dist/storage/FileStorage.js +18 -0
- package/dist/storage/FileStorage.js.map +1 -0
- package/dist/storage/KeytarCredentialStore.d.ts +9 -0
- package/dist/storage/KeytarCredentialStore.js +40 -0
- package/dist/storage/KeytarCredentialStore.js.map +1 -0
- package/dist/storage/SessionRepository.d.ts +28 -0
- package/dist/storage/SessionRepository.js +151 -0
- package/dist/storage/SessionRepository.js.map +1 -0
- package/dist/types/domain.d.ts +85 -0
- package/dist/types/domain.js +2 -0
- package/dist/types/domain.js.map +1 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +11 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/errors.js +12 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/files.d.ts +4 -0
- package/dist/utils/files.js +28 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/json.d.ts +2 -0
- package/dist/utils/json.js +10 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/mask.d.ts +2 -0
- package/dist/utils/mask.js +10 -0
- package/dist/utils/mask.js.map +1 -0
- package/dist/utils/query.d.ts +1 -0
- package/dist/utils/query.js +17 -0
- package/dist/utils/query.js.map +1 -0
- 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
|
+
[](https://github.com/kryshtop/bstack/actions/workflows/build.yml)
|
|
6
|
+
[](https://github.com/kryshtop/bstack/actions/workflows/unit-tests.yml)
|
|
7
|
+
[](https://github.com/kryshtop/bstack/blob/main/package.json)
|
|
8
|
+
[](https://github.com/kryshtop/bstack/stargazers)
|
|
9
|
+
[](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
|
+
[](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[];
|