@heroku/heroku-fetch 0.1.1-beta.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.txt +206 -0
- package/README.md +525 -0
- package/dist/auth/auth.d.ts +66 -0
- package/dist/auth/auth.js +93 -0
- package/dist/browser.d.ts +13 -0
- package/dist/browser.js +12 -0
- package/dist/cli/cli-login.d.ts +7 -0
- package/dist/cli/cli-login.js +7 -0
- package/dist/cli/cli-two-factor-prompt.d.ts +62 -0
- package/dist/cli/cli-two-factor-prompt.js +93 -0
- package/dist/cli/login/browser-login.d.ts +8 -0
- package/dist/cli/login/browser-login.js +115 -0
- package/dist/cli/login/index.d.ts +34 -0
- package/dist/cli/login/index.js +193 -0
- package/dist/cli/login/interactive-login.d.ts +8 -0
- package/dist/cli/login/interactive-login.js +59 -0
- package/dist/cli/login/netrc-utils.d.ts +22 -0
- package/dist/cli/login/netrc-utils.js +85 -0
- package/dist/cli/login/oauth.d.ts +20 -0
- package/dist/cli/login/oauth.js +142 -0
- package/dist/cli/login/sso-login.d.ts +8 -0
- package/dist/cli/login/sso-login.js +41 -0
- package/dist/cli/login/types.d.ts +42 -0
- package/dist/cli/login/types.js +11 -0
- package/dist/client/browser-environment-defaults.d.ts +22 -0
- package/dist/client/browser-environment-defaults.js +27 -0
- package/dist/client/http-error-handler.d.ts +10 -0
- package/dist/client/http-error-handler.js +44 -0
- package/dist/client/http-request-hooks.d.ts +18 -0
- package/dist/client/http-request-hooks.js +62 -0
- package/dist/client/index.d.ts +57 -0
- package/dist/client/index.js +164 -0
- package/dist/client/node-environment-defaults.d.ts +31 -0
- package/dist/client/node-environment-defaults.js +68 -0
- package/dist/client/service-configurations.d.ts +2 -0
- package/dist/client/service-configurations.js +21 -0
- package/dist/client/two-factor-authentication-handler.d.ts +16 -0
- package/dist/client/two-factor-authentication-handler.js +53 -0
- package/dist/debug-loggers.d.ts +6 -0
- package/dist/debug-loggers.js +6 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +57 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
# heroku-fetch
|
|
2
|
+
|
|
3
|
+
A modern JavaScript/TypeScript API client for Heroku APIs, built on the Fetch API and designed to work seamlessly in both Node.js and browser environments.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✨ **Universal**: Works in Node.js and browsers
|
|
8
|
+
- 🔐 **Authentication**: Support for bearer tokens and dynamic token providers
|
|
9
|
+
- 🛡️ **2FA Support**: Automatic handling of two-factor authentication challenges
|
|
10
|
+
- 🎯 **Multi-API Support**: Pre-configured for Platform API, Data API, and Particleboard
|
|
11
|
+
- 📊 **Streaming**: Native support for streaming responses and Server-Sent Events
|
|
12
|
+
- 🐛 **Debugging**: Built-in debugging with the `debug` package
|
|
13
|
+
- 💪 **TypeScript**: Full TypeScript support with complete type definitions
|
|
14
|
+
- ⚡ **Modern**: Built on `ky` for a clean, promise-based API
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @heroku/heroku-fetch
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Basic Usage
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { HerokuApiClient } from '@heroku/heroku-fetch';
|
|
28
|
+
|
|
29
|
+
// Create a client for the Platform API
|
|
30
|
+
// Automatically uses token from HEROKU_API_KEY env var or ~/.netrc
|
|
31
|
+
const client = new HerokuApiClient({
|
|
32
|
+
service: 'platform',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Make a request
|
|
36
|
+
const response = await client.get('/apps');
|
|
37
|
+
const apps = await response.json();
|
|
38
|
+
console.log(apps);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Service Configuration
|
|
42
|
+
|
|
43
|
+
The client comes with pre-configured settings for different Heroku services:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Platform API (default)
|
|
47
|
+
const platformClient = new HerokuApiClient({
|
|
48
|
+
service: 'platform',
|
|
49
|
+
token: 'your_token',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Data API
|
|
53
|
+
const dataClient = new HerokuApiClient({
|
|
54
|
+
service: 'data',
|
|
55
|
+
token: 'your_token',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Data API with EU region
|
|
59
|
+
const euDataClient = new HerokuApiClient({
|
|
60
|
+
service: 'data',
|
|
61
|
+
region: 'eu',
|
|
62
|
+
token: 'your_token',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Particleboard
|
|
66
|
+
const particleboardClient = new HerokuApiClient({
|
|
67
|
+
service: 'particleboard',
|
|
68
|
+
token: 'your_token',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Custom API
|
|
72
|
+
const customClient = new HerokuApiClient({
|
|
73
|
+
service: 'custom',
|
|
74
|
+
baseUrl: 'https://your-custom-api.heroku.com',
|
|
75
|
+
token: 'your_token',
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Authentication
|
|
80
|
+
|
|
81
|
+
### Automatic Token from Environment or Netrc (Default)
|
|
82
|
+
|
|
83
|
+
By default, the client automatically fetches tokens from `HEROKU_API_KEY` environment variable or `~/.netrc` file. **No manual token management required!**
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { HerokuApiClient } from '@heroku/heroku-fetch';
|
|
87
|
+
|
|
88
|
+
// Token automatically loaded from HEROKU_API_KEY or ~/.netrc
|
|
89
|
+
const client = new HerokuApiClient({
|
|
90
|
+
service: 'platform',
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The token priority is:
|
|
95
|
+
1. `HEROKU_API_KEY` environment variable
|
|
96
|
+
2. `~/.netrc` file (api.heroku.com machine)
|
|
97
|
+
|
|
98
|
+
If you need to manually retrieve the token for other purposes, you can use `getAuthToken()`:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { getAuthToken } from '@heroku/heroku-fetch';
|
|
102
|
+
|
|
103
|
+
const token = getAuthToken(); // Returns string | undefined
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Static Bearer Token
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const client = new HerokuApiClient({
|
|
110
|
+
service: 'platform',
|
|
111
|
+
token: 'your_static_heroku_bearer_token',
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Dynamic Token Provider
|
|
116
|
+
|
|
117
|
+
Use a function to dynamically retrieve or refresh tokens:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { getAuthTokenProvider } from '@heroku/heroku-fetch';
|
|
121
|
+
|
|
122
|
+
// Option 1: Use the built-in provider for dynamic token fetching
|
|
123
|
+
const client = new HerokuApiClient({
|
|
124
|
+
service: 'platform',
|
|
125
|
+
token: getAuthTokenProvider(), // Returns a function that fetches token each time
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Option 2: Provide your own custom token function
|
|
129
|
+
const client = new HerokuApiClient({
|
|
130
|
+
service: 'platform',
|
|
131
|
+
token: async () => {
|
|
132
|
+
// Fetch token from secure store or refresh mechanism
|
|
133
|
+
const token = await fetchFreshHerokuToken();
|
|
134
|
+
return token;
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Two-Factor Authentication
|
|
140
|
+
|
|
141
|
+
Handle 2FA challenges automatically:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const client = new HerokuApiClient({
|
|
145
|
+
service: 'platform',
|
|
146
|
+
token: 'your_token',
|
|
147
|
+
twoFactor: {
|
|
148
|
+
onChallenge: async () => {
|
|
149
|
+
// Prompt user for 2FA code
|
|
150
|
+
const code = await promptUserFor2FA();
|
|
151
|
+
return code;
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## HTTP Methods
|
|
158
|
+
|
|
159
|
+
The client provides convenience methods for common HTTP operations:
|
|
160
|
+
|
|
161
|
+
### GET
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const response = await client.get('/apps');
|
|
165
|
+
const apps = await response.json();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### POST
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const response = await client.post('/apps', {
|
|
172
|
+
name: 'my-new-app',
|
|
173
|
+
region: 'us',
|
|
174
|
+
});
|
|
175
|
+
const app = await response.json();
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### PUT
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const response = await client.put('/apps/my-app', {
|
|
182
|
+
maintenance: true,
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### PATCH
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const response = await client.patch('/apps/my-app', {
|
|
190
|
+
name: 'renamed-app',
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### DELETE
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const response = await client.delete('/apps/my-app');
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Request Options
|
|
201
|
+
|
|
202
|
+
All HTTP methods accept an optional `RequestOptions` object:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const response = await client.get('/apps', {
|
|
206
|
+
headers: {
|
|
207
|
+
'X-Custom-Header': 'value',
|
|
208
|
+
},
|
|
209
|
+
timeout: 5000, // Override default timeout
|
|
210
|
+
searchParams: {
|
|
211
|
+
limit: 10,
|
|
212
|
+
offset: 0,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Cancellation
|
|
218
|
+
|
|
219
|
+
Pass an `AbortSignal` to cancel an in-flight request. Aborting rejects the
|
|
220
|
+
returned promise with an `AbortError`:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const controller = new AbortController();
|
|
224
|
+
|
|
225
|
+
// Cancel after 2 seconds
|
|
226
|
+
const timer = setTimeout(() => controller.abort(), 2000);
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const response = await client.get('/apps', {signal: controller.signal});
|
|
230
|
+
const apps = await response.json();
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
console.log(apps);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if ((error as Error).name === 'AbortError') {
|
|
235
|
+
console.log('Request cancelled');
|
|
236
|
+
} else {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`signal` is also forwarded to `client.stream()`, so the same controller can
|
|
243
|
+
cancel a long-lived streaming response.
|
|
244
|
+
|
|
245
|
+
## Streaming
|
|
246
|
+
|
|
247
|
+
Stream data from endpoints like Logplex:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const response = await client.stream('/apps/my-app/log-sessions/session-id');
|
|
251
|
+
|
|
252
|
+
// Node.js - use the response body as a stream
|
|
253
|
+
if (response.body) {
|
|
254
|
+
const reader = response.body.getReader();
|
|
255
|
+
const decoder = new TextDecoder();
|
|
256
|
+
|
|
257
|
+
while (true) {
|
|
258
|
+
const { done, value } = await reader.read();
|
|
259
|
+
if (done) break;
|
|
260
|
+
|
|
261
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
262
|
+
console.log(chunk);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Server-Sent Events (SSE)
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
const response = await client.stream('/apps/my-app/log-sessions/session-id');
|
|
271
|
+
|
|
272
|
+
// Process SSE events
|
|
273
|
+
const reader = response.body?.getReader();
|
|
274
|
+
const decoder = new TextDecoder();
|
|
275
|
+
|
|
276
|
+
if (reader) {
|
|
277
|
+
while (true) {
|
|
278
|
+
const { done, value } = await reader.read();
|
|
279
|
+
if (done) break;
|
|
280
|
+
|
|
281
|
+
const text = decoder.decode(value);
|
|
282
|
+
// Parse SSE format: "data: {...}\n\n"
|
|
283
|
+
const events = text.split('\n\n').filter(Boolean);
|
|
284
|
+
|
|
285
|
+
for (const event of events) {
|
|
286
|
+
if (event.startsWith('data: ')) {
|
|
287
|
+
const data = event.slice(6);
|
|
288
|
+
console.log('Event:', data);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Error Handling
|
|
296
|
+
|
|
297
|
+
The client throws specific error types for different scenarios:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import {
|
|
301
|
+
HerokuApiError,
|
|
302
|
+
AuthenticationError,
|
|
303
|
+
NotFoundError,
|
|
304
|
+
TwoFactorRequiredError,
|
|
305
|
+
RateLimitError,
|
|
306
|
+
} from '@heroku/heroku-fetch';
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const response = await client.get('/apps/nonexistent');
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (error instanceof NotFoundError) {
|
|
312
|
+
console.error('App not found');
|
|
313
|
+
} else if (error instanceof AuthenticationError) {
|
|
314
|
+
console.error('Invalid credentials');
|
|
315
|
+
} else if (error instanceof RateLimitError) {
|
|
316
|
+
console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
|
|
317
|
+
} else if (error instanceof TwoFactorRequiredError) {
|
|
318
|
+
console.error('2FA required but not configured');
|
|
319
|
+
} else if (error instanceof HerokuApiError) {
|
|
320
|
+
console.error(`API Error: ${error.message}`);
|
|
321
|
+
console.error(`Status: ${error.statusCode}`);
|
|
322
|
+
console.error(`ID: ${error.id}`);
|
|
323
|
+
console.error(`Errors: ${JSON.stringify(error.errors)}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Debugging
|
|
329
|
+
|
|
330
|
+
Enable debugging output using the `DEBUG` environment variable (Node.js) or `localStorage.debug` (browser):
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
# Enable all heroku-fetch debugging
|
|
334
|
+
DEBUG=heroku-fetch:* node your-script.js
|
|
335
|
+
|
|
336
|
+
# Enable specific namespaces
|
|
337
|
+
DEBUG=heroku-fetch:request,heroku-fetch:response node your-script.js
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Available debug namespaces:
|
|
341
|
+
- `heroku-fetch:request` - Outgoing HTTP requests
|
|
342
|
+
- `heroku-fetch:response` - Incoming HTTP responses
|
|
343
|
+
- `heroku-fetch:auth` - Authentication and token management
|
|
344
|
+
- `heroku-fetch:error` - Error details
|
|
345
|
+
|
|
346
|
+
In the browser:
|
|
347
|
+
```javascript
|
|
348
|
+
localStorage.debug = 'heroku-fetch:*';
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Configuration Options
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
interface HerokuApiClientOptions {
|
|
355
|
+
/** Heroku service type */
|
|
356
|
+
service?: 'platform' | 'data' | 'particleboard' | 'custom';
|
|
357
|
+
|
|
358
|
+
/** Static bearer token or function to retrieve token */
|
|
359
|
+
token?: string | (() => string | Promise<string>);
|
|
360
|
+
|
|
361
|
+
/** Two-factor authentication configuration */
|
|
362
|
+
twoFactor?: {
|
|
363
|
+
onChallenge: () => string | Promise<string>;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/** Custom base URL (required for 'custom' service) */
|
|
367
|
+
baseUrl?: string;
|
|
368
|
+
|
|
369
|
+
/** Service region (e.g., 'eu', 'us') */
|
|
370
|
+
region?: string;
|
|
371
|
+
|
|
372
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
373
|
+
timeout?: number;
|
|
374
|
+
|
|
375
|
+
/** Additional custom headers */
|
|
376
|
+
headers?: Record<string, string>;
|
|
377
|
+
|
|
378
|
+
/** Enable debug output */
|
|
379
|
+
debug?: boolean;
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Updating Options
|
|
384
|
+
|
|
385
|
+
You can update client options after instantiation:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
const client = new HerokuApiClient({ service: 'platform' });
|
|
389
|
+
|
|
390
|
+
// Update token
|
|
391
|
+
client.setOption('token', 'new_token');
|
|
392
|
+
|
|
393
|
+
// Update timeout
|
|
394
|
+
client.setOption('timeout', 5000);
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## TypeScript Support
|
|
398
|
+
|
|
399
|
+
The library is written in TypeScript and provides full type definitions:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import type {
|
|
403
|
+
HerokuApiClientOptions,
|
|
404
|
+
RequestOptions,
|
|
405
|
+
HerokuService,
|
|
406
|
+
TokenProvider,
|
|
407
|
+
} from '@heroku/heroku-fetch';
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Examples
|
|
411
|
+
|
|
412
|
+
### Create and Deploy an App
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
const client = new HerokuApiClient({
|
|
416
|
+
service: 'platform',
|
|
417
|
+
token: process.env.HEROKU_TOKEN,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Create app
|
|
421
|
+
const createResponse = await client.post('/apps', {
|
|
422
|
+
name: 'my-awesome-app',
|
|
423
|
+
region: 'us',
|
|
424
|
+
});
|
|
425
|
+
const app = await createResponse.json();
|
|
426
|
+
console.log('Created app:', app.name);
|
|
427
|
+
|
|
428
|
+
// Get app details
|
|
429
|
+
const appResponse = await client.get(`/apps/${app.name}`);
|
|
430
|
+
const appDetails = await appResponse.json();
|
|
431
|
+
console.log('App details:', appDetails);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Stream Logs
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
const client = new HerokuApiClient({
|
|
438
|
+
service: 'platform',
|
|
439
|
+
token: process.env.HEROKU_TOKEN,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Create log session
|
|
443
|
+
const sessionResponse = await client.post('/apps/my-app/log-sessions', {
|
|
444
|
+
dyno: 'web.1',
|
|
445
|
+
lines: 100,
|
|
446
|
+
tail: true,
|
|
447
|
+
});
|
|
448
|
+
const session = await sessionResponse.json();
|
|
449
|
+
|
|
450
|
+
// Stream logs
|
|
451
|
+
const logsResponse = await client.stream(session.logplex_url);
|
|
452
|
+
const reader = logsResponse.body?.getReader();
|
|
453
|
+
const decoder = new TextDecoder();
|
|
454
|
+
|
|
455
|
+
if (reader) {
|
|
456
|
+
while (true) {
|
|
457
|
+
const { done, value } = await reader.read();
|
|
458
|
+
if (done) break;
|
|
459
|
+
|
|
460
|
+
const logs = decoder.decode(value, { stream: true });
|
|
461
|
+
process.stdout.write(logs);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Work with Postgres
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
const client = new HerokuApiClient({
|
|
470
|
+
service: 'data',
|
|
471
|
+
token: process.env.HEROKU_TOKEN,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// List databases
|
|
475
|
+
const response = await client.get('/databases');
|
|
476
|
+
const databases = await response.json();
|
|
477
|
+
console.log('Databases:', databases);
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Oclif CLI Commands
|
|
481
|
+
|
|
482
|
+
Complete examples for building oclif CLI commands are available in `examples/oclif/`:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
import { Command } from '@oclif/core';
|
|
486
|
+
import { HerokuApiClient } from '@heroku/heroku-fetch';
|
|
487
|
+
|
|
488
|
+
export default class AppsList extends Command {
|
|
489
|
+
async run() {
|
|
490
|
+
// Client automatically uses token from env or netrc
|
|
491
|
+
const client = new HerokuApiClient({
|
|
492
|
+
service: 'platform',
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const response = await client.get('/apps');
|
|
496
|
+
const apps = await response.json();
|
|
497
|
+
|
|
498
|
+
// Display apps...
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
See [examples/oclif/README.md](examples/oclif/README.md) for complete working examples including:
|
|
504
|
+
- Listing apps with table formatting
|
|
505
|
+
- Creating apps with validation
|
|
506
|
+
- Streaming logs in real-time
|
|
507
|
+
- Error handling patterns
|
|
508
|
+
- 2FA support
|
|
509
|
+
|
|
510
|
+
## Contributing
|
|
511
|
+
|
|
512
|
+
Contributions are welcome! Please see the repository for contribution guidelines.
|
|
513
|
+
|
|
514
|
+
## License
|
|
515
|
+
|
|
516
|
+
MIT
|
|
517
|
+
|
|
518
|
+
## Related Projects
|
|
519
|
+
|
|
520
|
+
- [ky](https://github.com/sindresorhus/ky) - The underlying HTTP client
|
|
521
|
+
- [debug](https://github.com/debug-js/debug) - Debugging utility
|
|
522
|
+
|
|
523
|
+
## Support
|
|
524
|
+
|
|
525
|
+
For issues and questions, please visit the [GitHub repository](https://github.com/heroku/heroku-fetch).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication utilities for Heroku API
|
|
3
|
+
* Handles token retrieval from environment variables and netrc file
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Get the Heroku API token from environment variable or netrc file
|
|
7
|
+
*
|
|
8
|
+
* Priority:
|
|
9
|
+
* 1. HEROKU_API_KEY environment variable
|
|
10
|
+
* 2. ~/.netrc file (uses api.heroku.com or HEROKU_HOST if set)
|
|
11
|
+
*
|
|
12
|
+
* @returns The API token or undefined if not found
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { getAuthToken, HerokuApiClient } from '@heroku/heroku-fetch';
|
|
17
|
+
*
|
|
18
|
+
* const token = getAuthToken();
|
|
19
|
+
* const client = new HerokuApiClient({
|
|
20
|
+
* service: 'platform',
|
|
21
|
+
* token,
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function getAuthToken(): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Get the API host from environment or default
|
|
28
|
+
*
|
|
29
|
+
* @returns The API host (default: 'api.heroku.com')
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const host = getApiHost();
|
|
34
|
+
* // Returns 'api.heroku.com' or value of HEROKU_HOST env var
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function getApiHost(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Get the full API URL from environment or default
|
|
40
|
+
*
|
|
41
|
+
* @returns The API URL (default: 'https://api.heroku.com')
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const url = getApiUrl();
|
|
46
|
+
* // Returns 'https://api.heroku.com' or https://{HEROKU_HOST}
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function getApiUrl(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Create a token provider function for use with HerokuApiClient
|
|
52
|
+
* This is useful when you want the token to be fetched dynamically
|
|
53
|
+
*
|
|
54
|
+
* @returns A function that returns the current auth token
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import { getAuthTokenProvider, HerokuApiClient } from '@heroku/heroku-fetch';
|
|
59
|
+
*
|
|
60
|
+
* const client = new HerokuApiClient({
|
|
61
|
+
* service: 'platform',
|
|
62
|
+
* token: getAuthTokenProvider(),
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function getAuthTokenProvider(): () => string | undefined;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication utilities for Heroku API
|
|
3
|
+
* Handles token retrieval from environment variables and netrc file
|
|
4
|
+
*/
|
|
5
|
+
import { Netrc } from 'netrc-parser';
|
|
6
|
+
// Defer netrc instantiation to avoid eager .netrc file operations at module load time
|
|
7
|
+
let _netrc;
|
|
8
|
+
function getNetrc() {
|
|
9
|
+
if (!_netrc) {
|
|
10
|
+
_netrc = new Netrc();
|
|
11
|
+
}
|
|
12
|
+
return _netrc;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the Heroku API token from environment variable or netrc file
|
|
16
|
+
*
|
|
17
|
+
* Priority:
|
|
18
|
+
* 1. HEROKU_API_KEY environment variable
|
|
19
|
+
* 2. ~/.netrc file (uses api.heroku.com or HEROKU_HOST if set)
|
|
20
|
+
*
|
|
21
|
+
* @returns The API token or undefined if not found
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { getAuthToken, HerokuApiClient } from '@heroku/heroku-fetch';
|
|
26
|
+
*
|
|
27
|
+
* const token = getAuthToken();
|
|
28
|
+
* const client = new HerokuApiClient({
|
|
29
|
+
* service: 'platform',
|
|
30
|
+
* token,
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function getAuthToken() {
|
|
35
|
+
// Check environment variable first
|
|
36
|
+
let token = process.env.HEROKU_API_KEY;
|
|
37
|
+
if (!token) {
|
|
38
|
+
// Fall back to netrc
|
|
39
|
+
const netrc = getNetrc();
|
|
40
|
+
netrc.loadSync();
|
|
41
|
+
const apiHost = getApiHost();
|
|
42
|
+
token = netrc.machines[apiHost]?.password;
|
|
43
|
+
}
|
|
44
|
+
return token;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the API host from environment or default
|
|
48
|
+
*
|
|
49
|
+
* @returns The API host (default: 'api.heroku.com')
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const host = getApiHost();
|
|
54
|
+
* // Returns 'api.heroku.com' or value of HEROKU_HOST env var
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function getApiHost() {
|
|
58
|
+
return process.env.HEROKU_HOST || 'api.heroku.com';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the full API URL from environment or default
|
|
62
|
+
*
|
|
63
|
+
* @returns The API URL (default: 'https://api.heroku.com')
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* const url = getApiUrl();
|
|
68
|
+
* // Returns 'https://api.heroku.com' or https://{HEROKU_HOST}
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function getApiUrl() {
|
|
72
|
+
const host = getApiHost();
|
|
73
|
+
return `https://${host}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Create a token provider function for use with HerokuApiClient
|
|
77
|
+
* This is useful when you want the token to be fetched dynamically
|
|
78
|
+
*
|
|
79
|
+
* @returns A function that returns the current auth token
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { getAuthTokenProvider, HerokuApiClient } from '@heroku/heroku-fetch';
|
|
84
|
+
*
|
|
85
|
+
* const client = new HerokuApiClient({
|
|
86
|
+
* service: 'platform',
|
|
87
|
+
* token: getAuthTokenProvider(),
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function getAuthTokenProvider() {
|
|
92
|
+
return () => getAuthToken();
|
|
93
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-specific entry point for @heroku/heroku-fetch
|
|
3
|
+
*
|
|
4
|
+
* This entry point excludes Node.js-specific features like:
|
|
5
|
+
* - CLI utilities (login, 2FA prompts)
|
|
6
|
+
* - File system access (netrc)
|
|
7
|
+
* - Process environment variables
|
|
8
|
+
*
|
|
9
|
+
* Use this entry point when bundling for browser environments.
|
|
10
|
+
*/
|
|
11
|
+
export { HerokuApiClient } from './client/index.js';
|
|
12
|
+
export { AuthenticationError, HerokuApiError, NotFoundError, RateLimitError, TwoFactorRequiredError, } from './errors.js';
|
|
13
|
+
export type { HerokuApiClientOptions, HerokuError, HerokuErrorResponse, HerokuService, RequestOptions, ServiceConfig, TokenProvider, TwoFactorOptions, } from './types.js';
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-specific entry point for @heroku/heroku-fetch
|
|
3
|
+
*
|
|
4
|
+
* This entry point excludes Node.js-specific features like:
|
|
5
|
+
* - CLI utilities (login, 2FA prompts)
|
|
6
|
+
* - File system access (netrc)
|
|
7
|
+
* - Process environment variables
|
|
8
|
+
*
|
|
9
|
+
* Use this entry point when bundling for browser environments.
|
|
10
|
+
*/
|
|
11
|
+
export { HerokuApiClient } from './client/index.js';
|
|
12
|
+
export { AuthenticationError, HerokuApiError, NotFoundError, RateLimitError, TwoFactorRequiredError, } from './errors.js';
|