blogwright-core 0.3.3 → 0.4.0-beta.1

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/README.md CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  SigV4 transport, per-service AWS HTTP clients, config parsing, and the S3 state store
4
4
  used by [blogwright](https://www.npmjs.com/package/blogwright). Not useful on its
5
- own install `blogwright` instead.
5
+ own - install `blogwright` instead.
6
6
 
7
7
  Full documentation: https://github.com/antstanley/blogwright
@@ -13,7 +13,7 @@ export interface TerminalStreams {
13
13
  export interface NodeTerminalOptions extends Partial<TerminalStreams> {
14
14
  /**
15
15
  * Force the minimal, machine-friendly presentation (`--plain`): the terminal
16
- * reports non-interactive, so output is plain durable lines no colour, no
16
+ * reports non-interactive, so output is plain durable lines - no colour, no
17
17
  * transient status, no prompts left hanging for automation.
18
18
  */
19
19
  plain?: boolean | undefined;
@@ -22,7 +22,7 @@ export interface NodeTerminalOptions extends Partial<TerminalStreams> {
22
22
  }
23
23
  /**
24
24
  * Build the real Terminal adapter over the process's standard streams. TTY
25
- * state is read once, at construction never at module load or per call.
25
+ * state is read once, at construction - never at module load or per call.
26
26
  * The transient status line exists only on an interactive TTY; `write`/`error`
27
27
  * clear it first so durable lines never interleave with a stale status.
28
28
  */
@@ -4,7 +4,7 @@ import { stripColors } from '../colors.js';
4
4
  const CLEAR_LINE = '\r\u001B[2K';
5
5
  /**
6
6
  * Build the real Terminal adapter over the process's standard streams. TTY
7
- * state is read once, at construction never at module load or per call.
7
+ * state is read once, at construction - never at module load or per call.
8
8
  * The transient status line exists only on an interactive TTY; `write`/`error`
9
9
  * clear it first so durable lines never interleave with a stale status.
10
10
  */
@@ -2,7 +2,7 @@ import type { SigningClient } from './signer.js';
2
2
  import type { ResourceTags } from '../tags.js';
3
3
  /** AWS managed "CachingOptimized" cache policy. */
4
4
  export declare const CACHING_OPTIMIZED = "658327ea-f89d-4fab-a63d-7e88639e58f6";
5
- /** AWS managed "CachingDisabled" cache policy (used for previews no CDN caching). */
5
+ /** AWS managed "CachingDisabled" cache policy (used for previews - no CDN caching). */
6
6
  export declare const CACHING_DISABLED = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad";
7
7
  export interface DistributionSummary {
8
8
  id: string;
@@ -11,7 +11,7 @@ export interface DistributionSummary {
11
11
  status: string;
12
12
  etag: string | undefined;
13
13
  }
14
- /** One entry from ListDistributions enough to identify a distribution for adoption. */
14
+ /** One entry from ListDistributions - enough to identify a distribution for adoption. */
15
15
  export interface DistributionListItem {
16
16
  id: string;
17
17
  arn: string;
@@ -62,7 +62,7 @@ export declare class CloudFrontClient {
62
62
  etag: string;
63
63
  } | undefined>;
64
64
  /**
65
- * Reconcile the distribution's aliases and viewer certificate in place the
65
+ * Reconcile the distribution's aliases and viewer certificate in place - the
66
66
  * path taken when a domain is added (or changed) after the distribution was
67
67
  * first created. Compares semantically (CNAME set + certificate ARN), since
68
68
  * the returned config carries legacy elements our builder never emits.
@@ -72,7 +72,7 @@ export declare class CloudFrontClient {
72
72
  /**
73
73
  * Disable a distribution by flipping its top-level `<Enabled>` to false. The config
74
74
  * also contains `<Enabled>false</Enabled>` inside TrustedSigners/Logging, so the
75
- * distribution-level flag must be matched specifically it is the one immediately
75
+ * distribution-level flag must be matched specifically - it is the one immediately
76
76
  * followed by `<ViewerCertificate>` in the schema.
77
77
  */
78
78
  disableDistribution(id: string): Promise<void>;
@@ -4,7 +4,7 @@ const API = '/2020-05-31';
4
4
  const XMLNS = 'http://cloudfront.amazonaws.com/doc/2020-05-31/';
5
5
  /** AWS managed "CachingOptimized" cache policy. */
6
6
  export const CACHING_OPTIMIZED = '658327ea-f89d-4fab-a63d-7e88639e58f6';
7
- /** AWS managed "CachingDisabled" cache policy (used for previews no CDN caching). */
7
+ /** AWS managed "CachingDisabled" cache policy (used for previews - no CDN caching). */
8
8
  export const CACHING_DISABLED = '4135ea2d-6df8-44a3-9df3-4b5a84be39ad';
9
9
  /** CloudFront client (REST-XML). Global service, signed in us-east-1. */
10
10
  export class CloudFrontClient {
@@ -116,7 +116,7 @@ export class CloudFrontClient {
116
116
  service: 'cloudfront',
117
117
  method: 'POST',
118
118
  path: `${API}/tagging`,
119
- // Operation=Tag is required the tagging path is shared with UntagResource
119
+ // Operation=Tag is required - the tagging path is shared with UntagResource
120
120
  // (Operation=Untag) and CloudFront routes on it; without it: InvalidAction.
121
121
  query: { Operation: 'Tag', Resource: resourceArn },
122
122
  headers: { 'content-type': 'application/xml' },
@@ -140,7 +140,7 @@ export class CloudFrontClient {
140
140
  }
141
141
  }
142
142
  /**
143
- * Reconcile the distribution's aliases and viewer certificate in place the
143
+ * Reconcile the distribution's aliases and viewer certificate in place - the
144
144
  * path taken when a domain is added (or changed) after the distribution was
145
145
  * first created. Compares semantically (CNAME set + certificate ARN), since
146
146
  * the returned config carries legacy elements our builder never emits.
@@ -171,7 +171,7 @@ export class CloudFrontClient {
171
171
  /**
172
172
  * Disable a distribution by flipping its top-level `<Enabled>` to false. The config
173
173
  * also contains `<Enabled>false</Enabled>` inside TrustedSigners/Logging, so the
174
- * distribution-level flag must be matched specifically it is the one immediately
174
+ * distribution-level flag must be matched specifically - it is the one immediately
175
175
  * followed by `<ViewerCertificate>` in the schema.
176
176
  */
177
177
  async disableDistribution(id) {
@@ -240,7 +240,7 @@ export class CloudFrontClient {
240
240
  async describeFunction(name) {
241
241
  // DescribeFunction is GET …/function/{name}/describe (XML summary). The
242
242
  // bare GET …/function/{name} is GetFunction, whose body is the raw code
243
- // bytes no FunctionARN to parse.
243
+ // bytes - no FunctionARN to parse.
244
244
  try {
245
245
  const res = await this.client.send({
246
246
  service: 'cloudfront',
@@ -277,7 +277,7 @@ export class CloudFrontClient {
277
277
  });
278
278
  }
279
279
  // Re-read the current ETag before publishing. Create/update return a fresh ETag, but
280
- // it isn't reliably surfaced from the response headers publishing the DEVELOPMENT
280
+ // it isn't reliably surfaced from the response headers - publishing the DEVELOPMENT
281
281
  // stage with a stale ETag fails the precondition (HTTP 412), which is what broke
282
282
  // reconciling an already-existing function.
283
283
  const current = await this.describeFunction(name);
@@ -10,7 +10,7 @@ export type CredentialProvider = () => Promise<AwsCredentials>;
10
10
  *
11
11
  * When an endpoint override is in play (floci/localstack) and no real credentials
12
12
  * are configured, fall back to the emulator's dummy `test/test` pair so signing
13
- * still succeeds floci does not validate signatures.
13
+ * still succeeds - floci does not validate signatures.
14
14
  */
15
15
  export declare function createCredentialProvider(opts: {
16
16
  override?: boolean | undefined;
@@ -5,7 +5,7 @@ import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
5
5
  *
6
6
  * When an endpoint override is in play (floci/localstack) and no real credentials
7
7
  * are configured, fall back to the emulator's dummy `test/test` pair so signing
8
- * still succeeds floci does not validate signatures.
8
+ * still succeeds - floci does not validate signatures.
9
9
  */
10
10
  export function createCredentialProvider(opts) {
11
11
  const chain = fromNodeProviderChain();
@@ -8,7 +8,7 @@
8
8
  export interface ResolvedEndpoint {
9
9
  protocol: 'http:' | 'https:';
10
10
  host: string;
11
- /** Signing region some services (iam, cloudfront) are global and sign as us-east-1. */
11
+ /** Signing region - some services (iam, cloudfront) are global and sign as us-east-1. */
12
12
  signingRegion: string;
13
13
  /** True when talking to an override origin (floci/localstack); forces S3 path-style. */
14
14
  override: boolean;
@@ -26,4 +26,24 @@ export declare const SIGNING_NAMES: {
26
26
  readonly secretsmanager: "secretsmanager";
27
27
  };
28
28
  export type ServiceKey = keyof typeof SIGNING_NAMES;
29
- export declare function resolveEndpoint(service: ServiceKey, region: string, override: string | undefined): ResolvedEndpoint;
29
+ /**
30
+ * A plugin-supplied AWS service - core does not enumerate it, so the plugin names
31
+ * its own SigV4 signing name and whether it is global (signs in us-east-1).
32
+ */
33
+ export interface ServiceDescriptor {
34
+ service: string;
35
+ signingName: string;
36
+ global?: boolean | undefined;
37
+ }
38
+ /** What every `service`-keyed site actually needs, whichever form `service` arrived in. */
39
+ export interface ResolvedService {
40
+ name: string;
41
+ signingName: string;
42
+ global: boolean;
43
+ }
44
+ /**
45
+ * Turns a core `ServiceKey` or a plugin-supplied `ServiceDescriptor` into one shape.
46
+ * The single resolution helper every `service`-keyed site reads.
47
+ */
48
+ export declare function resolveService(service: ServiceKey | ServiceDescriptor): ResolvedService;
49
+ export declare function resolveEndpoint(service: ServiceKey | ServiceDescriptor, region: string, override: string | undefined): ResolvedEndpoint;
@@ -21,8 +21,27 @@ export const SIGNING_NAMES = {
21
21
  };
22
22
  /** Services that are global; they always sign in us-east-1. */
23
23
  const GLOBAL_SERVICES = new Set(['iam', 'cloudfront', 'route53']);
24
+ /**
25
+ * Turns a core `ServiceKey` or a plugin-supplied `ServiceDescriptor` into one shape.
26
+ * The single resolution helper every `service`-keyed site reads.
27
+ */
28
+ export function resolveService(service) {
29
+ if (typeof service === 'string') {
30
+ return {
31
+ name: service,
32
+ signingName: SIGNING_NAMES[service],
33
+ global: GLOBAL_SERVICES.has(service),
34
+ };
35
+ }
36
+ return {
37
+ name: service.service,
38
+ signingName: service.signingName,
39
+ global: service.global ?? false,
40
+ };
41
+ }
24
42
  export function resolveEndpoint(service, region, override) {
25
- const signingRegion = GLOBAL_SERVICES.has(service) ? 'us-east-1' : region;
43
+ const resolved = resolveService(service);
44
+ const signingRegion = resolved.global ? 'us-east-1' : region;
26
45
  if (override) {
27
46
  const url = new URL(override);
28
47
  return {
@@ -34,7 +53,7 @@ export function resolveEndpoint(service, region, override) {
34
53
  }
35
54
  return {
36
55
  protocol: 'https:',
37
- host: canonicalHost(service, region),
56
+ host: canonicalHost(resolved.name, region),
38
57
  signingRegion,
39
58
  override: false,
40
59
  };
@@ -5,7 +5,7 @@ export class AwsError extends Error {
5
5
  requestId;
6
6
  service;
7
7
  constructor(opts) {
8
- super(`${opts.service}: ${opts.code} ${opts.message} (HTTP ${opts.statusCode})`);
8
+ super(`${opts.service}: ${opts.code} - ${opts.message} (HTTP ${opts.statusCode})`);
9
9
  this.name = 'AwsError';
10
10
  this.service = opts.service;
11
11
  this.code = opts.code;
package/dist/aws/iam.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ResourceTags } from '../tags.js';
2
2
  import type { SigningClient } from './signer.js';
3
- /** IAM client (query protocol) roles with inline policies. */
3
+ /** IAM client (query protocol) - roles with inline policies. */
4
4
  export declare class IamClient {
5
5
  private readonly client;
6
6
  constructor(client: SigningClient);
package/dist/aws/iam.js CHANGED
@@ -2,7 +2,7 @@ import { AwsError } from './errors.js';
2
2
  import { formEncode } from './form.js';
3
3
  import { allTags, textTag } from './xml.js';
4
4
  const VERSION = '2010-05-08';
5
- /** IAM client (query protocol) roles with inline policies. */
5
+ /** IAM client (query protocol) - roles with inline policies. */
6
6
  export class IamClient {
7
7
  client;
8
8
  constructor(client) {
@@ -9,6 +9,26 @@ export interface FilterEventsOptions {
9
9
  startTime?: number | undefined;
10
10
  endTime?: number | undefined;
11
11
  }
12
+ /** The format CloudWatch Logs writes records in for a vended log delivery. */
13
+ export type DeliveryOutputFormat = 'json' | 'plain' | 'w3c' | 'raw' | 'parquet';
14
+ export interface DeliveryDestinationOptions {
15
+ outputFormat?: DeliveryOutputFormat | undefined;
16
+ }
17
+ export interface DeliveryOptions {
18
+ recordFields?: readonly string[] | undefined;
19
+ fieldDelimiter?: string | undefined;
20
+ }
21
+ /**
22
+ * One delivery attached to a delivery source. The destination ARN travels with
23
+ * the id because a delivery source is shared - AWS permits exactly one per
24
+ * distribution - so a caller tearing its own wiring down has to tell its
25
+ * delivery from someone else's, and the destination it feeds is the only thing
26
+ * that distinguishes them. `DescribeDeliveries` already returns the field.
27
+ */
28
+ export interface DeliverySummary {
29
+ id: string;
30
+ deliveryDestinationArn: string;
31
+ }
12
32
  /** CloudWatch Logs client (AWS JSON 1.1). */
13
33
  export declare class LogsClient {
14
34
  private readonly client;
@@ -21,12 +41,12 @@ export declare class LogsClient {
21
41
  /** Fetch events across a log group within an optional time window. */
22
42
  filterEvents(logGroupName: string, opts?: FilterEventsOptions): Promise<LogEvent[]>;
23
43
  putDeliverySource(name: string, resourceArn: string, logType: string, tags?: ResourceTags): Promise<string>;
24
- putDeliveryDestination(name: string, logGroupArn: string): Promise<string>;
25
- createDelivery(deliverySourceName: string, deliveryDestinationArn: string): Promise<void>;
44
+ putDeliveryDestination(name: string, logGroupArn: string, opts?: DeliveryDestinationOptions): Promise<string>;
45
+ createDelivery(deliverySourceName: string, deliveryDestinationArn: string, opts?: DeliveryOptions): Promise<void>;
26
46
  /** The delivery id linking a given source (needed to delete it), or undefined if none. */
27
47
  findDeliveryIdBySource(deliverySourceName: string): Promise<string | undefined>;
28
- /** Ids of every delivery attached to a delivery source. */
29
- deliveriesForSource(sourceName: string): Promise<string[]>;
48
+ /** Every delivery attached to a delivery source, each paired with the destination it feeds. */
49
+ deliveriesForSource(sourceName: string): Promise<DeliverySummary[]>;
30
50
  deleteDelivery(id: string): Promise<void>;
31
51
  deleteDeliverySource(name: string): Promise<void>;
32
52
  deleteDeliveryDestination(name: string): Promise<void>;
package/dist/aws/logs.js CHANGED
@@ -78,13 +78,22 @@ export class LogsClient {
78
78
  });
79
79
  return out.deliverySource?.arn ?? '';
80
80
  }
81
- async putDeliveryDestination(name, logGroupArn) {
82
- const out = await this.call('PutDeliveryDestination', { name, deliveryDestinationConfiguration: { destinationResourceArn: logGroupArn } });
81
+ async putDeliveryDestination(name, logGroupArn, opts = {}) {
82
+ const out = await this.call('PutDeliveryDestination', {
83
+ name,
84
+ deliveryDestinationConfiguration: { destinationResourceArn: logGroupArn },
85
+ ...(opts.outputFormat !== undefined ? { outputFormat: opts.outputFormat } : {}),
86
+ });
83
87
  return out.deliveryDestination?.arn ?? '';
84
88
  }
85
- async createDelivery(deliverySourceName, deliveryDestinationArn) {
89
+ async createDelivery(deliverySourceName, deliveryDestinationArn, opts = {}) {
86
90
  try {
87
- await this.call('CreateDelivery', { deliverySourceName, deliveryDestinationArn });
91
+ await this.call('CreateDelivery', {
92
+ deliverySourceName,
93
+ deliveryDestinationArn,
94
+ ...(opts.recordFields !== undefined ? { recordFields: opts.recordFields } : {}),
95
+ ...(opts.fieldDelimiter !== undefined ? { fieldDelimiter: opts.fieldDelimiter } : {}),
96
+ });
88
97
  }
89
98
  catch (err) {
90
99
  if (err instanceof AwsError && err.isAlreadyExists)
@@ -104,19 +113,20 @@ export class LogsClient {
104
113
  } while (nextToken);
105
114
  return undefined;
106
115
  }
107
- /** Ids of every delivery attached to a delivery source. */
116
+ /** Every delivery attached to a delivery source, each paired with the destination it feeds. */
108
117
  async deliveriesForSource(sourceName) {
109
- const ids = [];
118
+ const deliveries = [];
110
119
  let nextToken;
111
120
  do {
112
121
  const out = await this.call('DescribeDeliveries', nextToken ? { nextToken } : {});
113
122
  for (const d of out.deliveries ?? []) {
114
- if (d.deliverySourceName === sourceName)
115
- ids.push(d.id);
123
+ if (d.deliverySourceName === sourceName) {
124
+ deliveries.push({ id: d.id, deliveryDestinationArn: d.deliveryDestinationArn ?? '' });
125
+ }
116
126
  }
117
127
  nextToken = out.nextToken;
118
128
  } while (nextToken);
119
- return ids;
129
+ return deliveries;
120
130
  }
121
131
  async deleteDelivery(id) {
122
132
  try {
@@ -73,7 +73,7 @@ export declare class MicrovmsClient {
73
73
  *
74
74
  * `maxResults` is always sent: the ListMicrovms operation defines it as a defaulted
75
75
  * query parameter, and the service normalizes a missing default into the request before
76
- * validating the SigV4 signature so omitting it produces an intermittent
76
+ * validating the SigV4 signature - so omitting it produces an intermittent
77
77
  * SignatureDoesNotMatch that GetMicrovm/DeleteMicrovm (no query params) never hit.
78
78
  */
79
79
  listMicrovms(opts?: {
@@ -84,7 +84,7 @@ export class MicrovmsClient {
84
84
  return normalizeImage(await this.call('POST', PATHS.images, body));
85
85
  }
86
86
  async updateImage(id, input) {
87
- // A PUT is idempotent on the image itself, so no clientToken is needed and reusing one
87
+ // A PUT is idempotent on the image itself, so no clientToken is needed - and reusing one
88
88
  // across separate update attempts triggers "clientToken used with different parameters".
89
89
  const { clientToken, ...body } = this.imageBody(input);
90
90
  void clientToken;
@@ -140,7 +140,7 @@ export class MicrovmsClient {
140
140
  *
141
141
  * `maxResults` is always sent: the ListMicrovms operation defines it as a defaulted
142
142
  * query parameter, and the service normalizes a missing default into the request before
143
- * validating the SigV4 signature so omitting it produces an intermittent
143
+ * validating the SigV4 signature - so omitting it produces an intermittent
144
144
  * SignatureDoesNotMatch that GetMicrovm/DeleteMicrovm (no query params) never hit.
145
145
  */
146
146
  async listMicrovms(opts = {}) {
@@ -8,7 +8,7 @@ export interface DnsRecord {
8
8
  /** When set, emit an AliasTarget (no TTL/ResourceRecords) pointing at `value`. */
9
9
  aliasZoneId?: string | undefined;
10
10
  }
11
- /** CloudFront's fixed alias hosted zone id the same for every distribution. */
11
+ /** CloudFront's fixed alias hosted zone id - the same for every distribution. */
12
12
  export declare const CLOUDFRONT_ALIAS_ZONE_ID = "Z2FDTNDATAQYW2";
13
13
  /** Route53 client (REST-XML). Global service, signed in us-east-1. */
14
14
  export declare class Route53Client {
@@ -1,7 +1,7 @@
1
1
  import { AwsError } from './errors.js';
2
2
  import { encodeEntities, textTag } from './xml.js';
3
3
  const API = '/2013-04-01';
4
- /** CloudFront's fixed alias hosted zone id the same for every distribution. */
4
+ /** CloudFront's fixed alias hosted zone id - the same for every distribution. */
5
5
  export const CLOUDFRONT_ALIAS_ZONE_ID = 'Z2FDTNDATAQYW2';
6
6
  /** Route53 client (REST-XML). Global service, signed in us-east-1. */
7
7
  export class Route53Client {
@@ -63,7 +63,7 @@ export class Route53Client {
63
63
  await this.change(zoneId, 'DELETE', record);
64
64
  }
65
65
  catch (err) {
66
- // Route53 rejects a DELETE whose record doesn't exactly match/exist
66
+ // Route53 rejects a DELETE whose record doesn't exactly match/exist -
67
67
  // that (and only that) means "already gone". Throttling, auth, and other
68
68
  // failures must surface, or a teardown leaves the record dangling while
69
69
  // reporting success.
@@ -1,5 +1,5 @@
1
1
  import type { CredentialProvider } from './credentials.js';
2
- import { type ServiceKey } from './endpoint.js';
2
+ import { type ServiceDescriptor, type ServiceKey } from './endpoint.js';
3
3
  export interface RawResponse {
4
4
  statusCode: number;
5
5
  headers: Record<string, string>;
@@ -14,7 +14,7 @@ export type Transport = (req: {
14
14
  body: string | Uint8Array | undefined;
15
15
  }) => Promise<RawResponse>;
16
16
  export interface SendOptions {
17
- service: ServiceKey;
17
+ service: ServiceKey | ServiceDescriptor;
18
18
  method: string;
19
19
  /** Path starting with '/'. For S3 pass an already-percent-encoded path. */
20
20
  path: string;
@@ -1,7 +1,7 @@
1
1
  import { Sha256 } from '@aws-crypto/sha256-js';
2
2
  import { HttpRequest } from '@smithy/protocol-http';
3
3
  import { SignatureV4 } from '@smithy/signature-v4';
4
- import { resolveEndpoint, SIGNING_NAMES } from './endpoint.js';
4
+ import { resolveEndpoint, resolveService, } from './endpoint.js';
5
5
  import { AwsError, isRetryable } from './errors.js';
6
6
  import { withRetry } from '../util.js';
7
7
  import { textTag } from './xml.js';
@@ -51,6 +51,7 @@ export class SigningClient {
51
51
  this.transport = opts.transport ?? fetchTransport;
52
52
  }
53
53
  async send(opts) {
54
+ const resolved = resolveService(opts.service);
54
55
  const ep = resolveEndpoint(opts.service, this.region, this.endpointOverride);
55
56
  const [hostname, portStr] = ep.host.split(':');
56
57
  const port = portStr ? Number(portStr) : undefined;
@@ -76,7 +77,7 @@ export class SigningClient {
76
77
  ...(opts.body !== undefined ? { body: opts.body } : {}),
77
78
  });
78
79
  const signer = new SignatureV4({
79
- service: SIGNING_NAMES[opts.service],
80
+ service: resolved.signingName,
80
81
  region: ep.signingRegion,
81
82
  credentials: async () => {
82
83
  const c = await this.credentials();
@@ -87,7 +88,7 @@ export class SigningClient {
87
88
  };
88
89
  },
89
90
  sha256: Sha256,
90
- uriEscapePath: opts.service !== 's3',
91
+ uriEscapePath: resolved.name !== 's3',
91
92
  });
92
93
  const signed = await signer.sign(request);
93
94
  const qs = Object.keys(query)
@@ -96,7 +97,7 @@ export class SigningClient {
96
97
  const url = `${ep.protocol}//${ep.host}${opts.path}${qs ? `?${qs}` : ''}`;
97
98
  // Retry transient failures with backoff. For idempotent methods, retry on network
98
99
  // errors and 5xx/429. For non-idempotent POSTs, retry ONLY on network errors (the
99
- // request never reached the server) never on a 5xx, which may mean the mutation
100
+ // request never reached the server) - never on a 5xx, which may mean the mutation
100
101
  // was applied and a retry would double-execute (e.g. launch a second MicroVM).
101
102
  const idempotent = ['GET', 'HEAD', 'PUT', 'DELETE'].includes(opts.method);
102
103
  const retryable = idempotent ? isRetryable : (err) => err instanceof TypeError;
@@ -109,10 +110,10 @@ export class SigningClient {
109
110
  });
110
111
  // Anything outside 2xx is a failure. 3xx matters: S3's region-mismatch
111
112
  // PermanentRedirect is a 301 with no Location header, which fetch
112
- // returns as final treating it as success would report writes as
113
+ // returns as final - treating it as success would report writes as
113
114
  // stored and listings as empty against the wrong endpoint.
114
115
  if (response.statusCode >= 300)
115
- throw parseError(opts.service, response);
116
+ throw parseError(resolved.name, response);
116
117
  return response;
117
118
  }, { retryable });
118
119
  }
package/dist/aws/sts.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { SigningClient } from './signer.js';
2
- /** STS client only GetCallerIdentity, used to derive the account id. */
2
+ /** STS client - only GetCallerIdentity, used to derive the account id. */
3
3
  export declare class StsClient {
4
4
  private readonly client;
5
5
  constructor(client: SigningClient);
package/dist/aws/sts.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { textTag } from './xml.js';
2
- /** STS client only GetCallerIdentity, used to derive the account id. */
2
+ /** STS client - only GetCallerIdentity, used to derive the account id. */
3
3
  export class StsClient {
4
4
  client;
5
5
  constructor(client) {
package/dist/aws/xml.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Minimal XML helpers for the handful of AWS REST-XML / query responses this CLI
3
- * reads (S3, STS, IAM, CloudFront). Not a general XML parser it extracts tag
3
+ * reads (S3, STS, IAM, CloudFront). Not a general XML parser - it extracts tag
4
4
  * contents by name, which is sufficient for the flat response shapes we consume.
5
5
  */
6
6
  export declare function decodeEntities(value: string): string;
@@ -10,7 +10,7 @@ export declare function firstTag(xml: string, name: string): string | undefined;
10
10
  /** Return the decoded text value of the first `<name>` element. */
11
11
  export declare function textTag(xml: string, name: string): string | undefined;
12
12
  /**
13
- * Like {@link textTag} but verbatim no trimming. For values where whitespace
13
+ * Like {@link textTag} but verbatim - no trimming. For values where whitespace
14
14
  * is data, like S3 object keys (a trimmed key would target a different object).
15
15
  */
16
16
  export declare function rawTextTag(xml: string, name: string): string | undefined;
package/dist/aws/xml.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Minimal XML helpers for the handful of AWS REST-XML / query responses this CLI
3
- * reads (S3, STS, IAM, CloudFront). Not a general XML parser it extracts tag
3
+ * reads (S3, STS, IAM, CloudFront). Not a general XML parser - it extracts tag
4
4
  * contents by name, which is sufficient for the flat response shapes we consume.
5
5
  */
6
6
  const ENTITIES = {
@@ -41,7 +41,7 @@ export function textTag(xml, name) {
41
41
  return inner === undefined ? undefined : decodeEntities(inner.trim());
42
42
  }
43
43
  /**
44
- * Like {@link textTag} but verbatim no trimming. For values where whitespace
44
+ * Like {@link textTag} but verbatim - no trimming. For values where whitespace
45
45
  * is data, like S3 object keys (a trimmed key would target a different object).
46
46
  */
47
47
  export function rawTextTag(xml, name) {
package/dist/clients.d.ts CHANGED
@@ -19,12 +19,22 @@ export interface ClientBundleOptions {
19
19
  export interface AwsClients {
20
20
  region: string;
21
21
  signing: SigningClient;
22
+ /**
23
+ * The us-east-1 signer, exposed so a plugin can build clients for services
24
+ * core does not enumerate over the credentials, endpoint override and
25
+ * transport the host already resolved - a `SigningClient`'s region is fixed
26
+ * at construction and both of those are private, so a hand-built one would
27
+ * re-resolve credentials and ignore a transport a test injected. Core builds
28
+ * no additional client from it: `logsUsEast1`, `acm`, `cloudfront` and
29
+ * `route53` below are the only ones it signs for.
30
+ */
31
+ signingUsEast1: SigningClient;
22
32
  s3: S3Client;
23
33
  sts: StsClient;
24
34
  iam: IamClient;
25
35
  logs: LogsClient;
26
36
  /**
27
- * Logs client pinned to us-east-1 for CloudFront vended log delivery
37
+ * Logs client pinned to us-east-1 for CloudFront vended log delivery -
28
38
  * PutDeliverySource with the CloudFront LogType exists only there, the same
29
39
  * global-service quirk as CloudFront's ACM certificates.
30
40
  */
package/dist/clients.js CHANGED
@@ -19,11 +19,13 @@ export function createClients(opts) {
19
19
  ...(opts.transport ? { transport: opts.transport } : {}),
20
20
  };
21
21
  const signing = new SigningClient({ region: opts.region, ...base });
22
- // ACM for CloudFront must be us-east-1 regardless of the primary region.
22
+ // ACM for CloudFront must be us-east-1 regardless of the primary region; the
23
+ // same signer is handed out as `signingUsEast1` for plugin-supplied services.
23
24
  const usEast1 = new SigningClient({ region: 'us-east-1', ...base });
24
25
  return {
25
26
  region: opts.region,
26
27
  signing,
28
+ signingUsEast1: usEast1,
27
29
  s3: new S3Client(signing),
28
30
  sts: new StsClient(signing),
29
31
  iam: new IamClient(signing),
package/dist/colors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * ANSI colour helpers shared by the CLI and feature packages. Pure string
3
- * composition writing (and stripping for non-interactive sessions) is the
3
+ * composition - writing (and stripping for non-interactive sessions) is the
4
4
  * logger's job.
5
5
  */
6
6
  const ESCAPE = '\u001B';