@smithy/signature-v4 5.6.3 → 5.6.5

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/dist-cjs/index.js CHANGED
@@ -20,27 +20,27 @@ class HeaderFormatter {
20
20
  formatHeaderValue(header) {
21
21
  switch (header.type) {
22
22
  case "boolean":
23
- return Uint8Array.from([header.value ? 0 : 1]);
23
+ return Uint8Array.from([header.value ? HEADER_VALUE_TYPE.boolTrue : HEADER_VALUE_TYPE.boolFalse]);
24
24
  case "byte":
25
- return Uint8Array.from([2, header.value]);
25
+ return Uint8Array.from([HEADER_VALUE_TYPE.byte, header.value]);
26
26
  case "short":
27
27
  const shortView = new DataView(new ArrayBuffer(3));
28
- shortView.setUint8(0, 3);
28
+ shortView.setUint8(0, HEADER_VALUE_TYPE.short);
29
29
  shortView.setInt16(1, header.value, false);
30
30
  return new Uint8Array(shortView.buffer);
31
31
  case "integer":
32
32
  const intView = new DataView(new ArrayBuffer(5));
33
- intView.setUint8(0, 4);
33
+ intView.setUint8(0, HEADER_VALUE_TYPE.integer);
34
34
  intView.setInt32(1, header.value, false);
35
35
  return new Uint8Array(intView.buffer);
36
36
  case "long":
37
37
  const longBytes = new Uint8Array(9);
38
- longBytes[0] = 5;
38
+ longBytes[0] = HEADER_VALUE_TYPE.long;
39
39
  longBytes.set(header.value.bytes, 1);
40
40
  return longBytes;
41
41
  case "binary":
42
42
  const binView = new DataView(new ArrayBuffer(3 + header.value.byteLength));
43
- binView.setUint8(0, 6);
43
+ binView.setUint8(0, HEADER_VALUE_TYPE.byteArray);
44
44
  binView.setUint16(1, header.value.byteLength, false);
45
45
  const binBytes = new Uint8Array(binView.buffer);
46
46
  binBytes.set(header.value, 3);
@@ -48,14 +48,14 @@ class HeaderFormatter {
48
48
  case "string":
49
49
  const utf8Bytes = fromUtf8(header.value);
50
50
  const strView = new DataView(new ArrayBuffer(3 + utf8Bytes.byteLength));
51
- strView.setUint8(0, 7);
51
+ strView.setUint8(0, HEADER_VALUE_TYPE.string);
52
52
  strView.setUint16(1, utf8Bytes.byteLength, false);
53
53
  const strBytes = new Uint8Array(strView.buffer);
54
54
  strBytes.set(utf8Bytes, 3);
55
55
  return strBytes;
56
56
  case "timestamp":
57
57
  const tsBytes = new Uint8Array(9);
58
- tsBytes[0] = 8;
58
+ tsBytes[0] = HEADER_VALUE_TYPE.timestamp;
59
59
  tsBytes.set(Int64.fromNumber(header.value.valueOf()).bytes, 1);
60
60
  return tsBytes;
61
61
  case "uuid":
@@ -63,8 +63,8 @@ class HeaderFormatter {
63
63
  throw new Error(`Invalid UUID received: ${header.value}`);
64
64
  }
65
65
  const uuidBytes = new Uint8Array(17);
66
- uuidBytes[0] = 9;
67
- uuidBytes.set(fromHex(header.value.replace(/\-/g, "")), 1);
66
+ uuidBytes[0] = HEADER_VALUE_TYPE.uuid;
67
+ uuidBytes.set(fromHex(header.value.replace(/-/g, "")), 1);
68
68
  return uuidBytes;
69
69
  }
70
70
  }
@@ -278,7 +278,7 @@ ${toHex(hashedRequest)}`;
278
278
  }
279
279
  }
280
280
  formatDate(now) {
281
- const longDate = iso8601(now).replace(/[\-:]/g, "");
281
+ const longDate = iso8601(now).replace(/[-:]/g, "");
282
282
  return {
283
283
  longDate,
284
284
  shortDate: longDate.slice(0, 8),
@@ -17,27 +17,27 @@ export class HeaderFormatter {
17
17
  formatHeaderValue(header) {
18
18
  switch (header.type) {
19
19
  case "boolean":
20
- return Uint8Array.from([header.value ? 0 : 1]);
20
+ return Uint8Array.from([header.value ? HEADER_VALUE_TYPE.boolTrue : HEADER_VALUE_TYPE.boolFalse]);
21
21
  case "byte":
22
- return Uint8Array.from([2, header.value]);
22
+ return Uint8Array.from([HEADER_VALUE_TYPE.byte, header.value]);
23
23
  case "short":
24
24
  const shortView = new DataView(new ArrayBuffer(3));
25
- shortView.setUint8(0, 3);
25
+ shortView.setUint8(0, HEADER_VALUE_TYPE.short);
26
26
  shortView.setInt16(1, header.value, false);
27
27
  return new Uint8Array(shortView.buffer);
28
28
  case "integer":
29
29
  const intView = new DataView(new ArrayBuffer(5));
30
- intView.setUint8(0, 4);
30
+ intView.setUint8(0, HEADER_VALUE_TYPE.integer);
31
31
  intView.setInt32(1, header.value, false);
32
32
  return new Uint8Array(intView.buffer);
33
33
  case "long":
34
34
  const longBytes = new Uint8Array(9);
35
- longBytes[0] = 5;
35
+ longBytes[0] = HEADER_VALUE_TYPE.long;
36
36
  longBytes.set(header.value.bytes, 1);
37
37
  return longBytes;
38
38
  case "binary":
39
39
  const binView = new DataView(new ArrayBuffer(3 + header.value.byteLength));
40
- binView.setUint8(0, 6);
40
+ binView.setUint8(0, HEADER_VALUE_TYPE.byteArray);
41
41
  binView.setUint16(1, header.value.byteLength, false);
42
42
  const binBytes = new Uint8Array(binView.buffer);
43
43
  binBytes.set(header.value, 3);
@@ -45,14 +45,14 @@ export class HeaderFormatter {
45
45
  case "string":
46
46
  const utf8Bytes = fromUtf8(header.value);
47
47
  const strView = new DataView(new ArrayBuffer(3 + utf8Bytes.byteLength));
48
- strView.setUint8(0, 7);
48
+ strView.setUint8(0, HEADER_VALUE_TYPE.string);
49
49
  strView.setUint16(1, utf8Bytes.byteLength, false);
50
50
  const strBytes = new Uint8Array(strView.buffer);
51
51
  strBytes.set(utf8Bytes, 3);
52
52
  return strBytes;
53
53
  case "timestamp":
54
54
  const tsBytes = new Uint8Array(9);
55
- tsBytes[0] = 8;
55
+ tsBytes[0] = HEADER_VALUE_TYPE.timestamp;
56
56
  tsBytes.set(Int64.fromNumber(header.value.valueOf()).bytes, 1);
57
57
  return tsBytes;
58
58
  case "uuid":
@@ -60,8 +60,8 @@ export class HeaderFormatter {
60
60
  throw new Error(`Invalid UUID received: ${header.value}`);
61
61
  }
62
62
  const uuidBytes = new Uint8Array(17);
63
- uuidBytes[0] = 9;
64
- uuidBytes.set(fromHex(header.value.replace(/\-/g, "")), 1);
63
+ uuidBytes[0] = HEADER_VALUE_TYPE.uuid;
64
+ uuidBytes.set(fromHex(header.value.replace(/-/g, "")), 1);
65
65
  return uuidBytes;
66
66
  }
67
67
  }
@@ -66,7 +66,7 @@ ${toHex(hashedRequest)}`;
66
66
  }
67
67
  }
68
68
  formatDate(now) {
69
- const longDate = iso8601(now).replace(/[\-:]/g, "");
69
+ const longDate = iso8601(now).replace(/[-:]/g, "");
70
70
  return {
71
71
  longDate,
72
72
  shortDate: longDate.slice(0, 8),
@@ -0,0 +1,25 @@
1
+ import { Int64 as IInt64, MessageHeaders } from "@smithy/types";
2
+ /**
3
+ * TODO: duplicated from @smithy/eventstream-codec to break large dependency.
4
+ * TODO: This should be moved to its own deduped submodule in @smithy/core when submodules are implemented.
5
+ *
6
+ * @internal
7
+ */
8
+ export declare class HeaderFormatter {
9
+ format(headers: MessageHeaders): Uint8Array;
10
+ private formatHeaderValue;
11
+ }
12
+ /**
13
+ * TODO: duplicated from @smithy/eventstream-codec to break large dependency.
14
+ * TODO: This should be moved to its own deduped submodule in @smithy/core when submodules are implemented.
15
+ */
16
+ export declare class Int64 implements IInt64 {
17
+ readonly bytes: Uint8Array;
18
+ constructor(bytes: Uint8Array);
19
+ static fromNumber(number: number): Int64;
20
+ /**
21
+ * Called implicitly by infix arithmetic operators.
22
+ */
23
+ valueOf(): number;
24
+ toString(): string;
25
+ }
@@ -0,0 +1,20 @@
1
+ import { EventSigner, EventSigningArguments, FormattedEvent, HttpRequest, MessageSigner, MessageSigningArguments, RequestPresigner, RequestPresigningArguments, RequestSigner, RequestSigningArguments, SignableMessage, SignedMessage, SigningArguments, StringSigner } from "@smithy/types";
2
+ import { SignatureV4Base, SignatureV4CryptoInit, SignatureV4Init } from "./SignatureV4Base";
3
+ /**
4
+ * @public
5
+ */
6
+ export declare class SignatureV4 extends SignatureV4Base implements RequestPresigner, RequestSigner, StringSigner, EventSigner, MessageSigner {
7
+ private readonly headerFormatter;
8
+ constructor({ applyChecksum, credentials, region, service, sha256, uriEscapePath, }: SignatureV4Init & SignatureV4CryptoInit);
9
+ presign(originalRequest: HttpRequest, options?: RequestPresigningArguments): Promise<HttpRequest>;
10
+ sign(stringToSign: string, options?: SigningArguments): Promise<string>;
11
+ sign(event: FormattedEvent, options: EventSigningArguments): Promise<string>;
12
+ sign(event: SignableMessage, options: MessageSigningArguments): Promise<SignedMessage>;
13
+ sign(requestToSign: HttpRequest, options?: RequestSigningArguments): Promise<HttpRequest>;
14
+ private signEvent;
15
+ signMessage(signableMessage: SignableMessage, { signingDate, signingRegion, signingService, eventStreamCredentials }: MessageSigningArguments): Promise<SignedMessage>;
16
+ private signString;
17
+ private signRequest;
18
+ private getSignature;
19
+ private getSigningKey;
20
+ }
@@ -0,0 +1,69 @@
1
+ import { AwsCredentialIdentity, ChecksumConstructor, DateInput, HashConstructor, HeaderBag, HttpRequest, Provider } from "@smithy/types";
2
+ /**
3
+ * @public
4
+ */
5
+ export interface SignatureV4Init {
6
+ /**
7
+ * The service signing name.
8
+ */
9
+ service: string;
10
+ /**
11
+ * The region name or a function that returns a promise that will be
12
+ * resolved with the region name.
13
+ */
14
+ region: string | Provider<string>;
15
+ /**
16
+ * The credentials with which the request should be signed or a function
17
+ * that returns a promise that will be resolved with credentials.
18
+ */
19
+ credentials: AwsCredentialIdentity | Provider<AwsCredentialIdentity>;
20
+ /**
21
+ * A constructor function for a hash object that will calculate SHA-256 HMAC
22
+ * checksums.
23
+ */
24
+ sha256?: ChecksumConstructor | HashConstructor;
25
+ /**
26
+ * Whether to uri-escape the request URI path as part of computing the
27
+ * canonical request string. This is required for every AWS service, except
28
+ * Amazon S3, as of late 2017.
29
+ *
30
+ * @default [true]
31
+ */
32
+ uriEscapePath?: boolean;
33
+ /**
34
+ * Whether to calculate a checksum of the request body and include it as
35
+ * either a request header (when signing) or as a query string parameter
36
+ * (when presigning). This is required for AWS Glacier and Amazon S3 and optional for
37
+ * every other AWS service as of late 2017.
38
+ *
39
+ * @default [true]
40
+ */
41
+ applyChecksum?: boolean;
42
+ }
43
+ /**
44
+ * @public
45
+ */
46
+ export interface SignatureV4CryptoInit {
47
+ sha256: ChecksumConstructor | HashConstructor;
48
+ }
49
+ /**
50
+ * @internal
51
+ */
52
+ export declare abstract class SignatureV4Base {
53
+ protected readonly service: string;
54
+ protected readonly regionProvider: Provider<string>;
55
+ protected readonly credentialProvider: Provider<AwsCredentialIdentity>;
56
+ protected readonly sha256: ChecksumConstructor | HashConstructor;
57
+ private readonly uriEscapePath;
58
+ protected readonly applyChecksum: boolean;
59
+ protected constructor({ applyChecksum, credentials, region, service, sha256, uriEscapePath, }: SignatureV4Init & SignatureV4CryptoInit);
60
+ protected createCanonicalRequest(request: HttpRequest, canonicalHeaders: HeaderBag, payloadHash: string): string;
61
+ protected createStringToSign(longDate: string, credentialScope: string, canonicalRequest: string, algorithmIdentifier: string): Promise<string>;
62
+ private getCanonicalPath;
63
+ protected validateResolvedCredentials(credentials: unknown): void;
64
+ protected formatDate(now: DateInput): {
65
+ longDate: string;
66
+ shortDate: string;
67
+ };
68
+ protected getCanonicalHeaderList(headers: object): string;
69
+ }
@@ -0,0 +1,43 @@
1
+ export declare const ALGORITHM_QUERY_PARAM = "X-Amz-Algorithm";
2
+ export declare const CREDENTIAL_QUERY_PARAM = "X-Amz-Credential";
3
+ export declare const AMZ_DATE_QUERY_PARAM = "X-Amz-Date";
4
+ export declare const SIGNED_HEADERS_QUERY_PARAM = "X-Amz-SignedHeaders";
5
+ export declare const EXPIRES_QUERY_PARAM = "X-Amz-Expires";
6
+ export declare const SIGNATURE_QUERY_PARAM = "X-Amz-Signature";
7
+ export declare const TOKEN_QUERY_PARAM = "X-Amz-Security-Token";
8
+ export declare const REGION_SET_PARAM = "X-Amz-Region-Set";
9
+ export declare const AUTH_HEADER = "authorization";
10
+ export declare const AMZ_DATE_HEADER: string;
11
+ export declare const DATE_HEADER = "date";
12
+ export declare const GENERATED_HEADERS: string[];
13
+ export declare const SIGNATURE_HEADER: string;
14
+ export declare const SHA256_HEADER = "x-amz-content-sha256";
15
+ export declare const TOKEN_HEADER: string;
16
+ export declare const HOST_HEADER = "host";
17
+ export declare const ALWAYS_UNSIGNABLE_HEADERS: {
18
+ authorization: boolean;
19
+ "cache-control": boolean;
20
+ connection: boolean;
21
+ expect: boolean;
22
+ from: boolean;
23
+ "keep-alive": boolean;
24
+ "max-forwards": boolean;
25
+ pragma: boolean;
26
+ referer: boolean;
27
+ te: boolean;
28
+ trailer: boolean;
29
+ "transfer-encoding": boolean;
30
+ upgrade: boolean;
31
+ "user-agent": boolean;
32
+ "x-amzn-trace-id": boolean;
33
+ };
34
+ export declare const PROXY_HEADER_PATTERN: RegExp;
35
+ export declare const SEC_HEADER_PATTERN: RegExp;
36
+ export declare const UNSIGNABLE_PATTERNS: RegExp[];
37
+ export declare const ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256";
38
+ export declare const ALGORITHM_IDENTIFIER_V4A = "AWS4-ECDSA-P256-SHA256";
39
+ export declare const EVENT_ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256-PAYLOAD";
40
+ export declare const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
41
+ export declare const MAX_CACHE_SIZE = 50;
42
+ export declare const KEY_TYPE_IDENTIFIER = "aws4_request";
43
+ export declare const MAX_PRESIGNED_TTL: number;
@@ -0,0 +1,30 @@
1
+ import { AwsCredentialIdentity, ChecksumConstructor, HashConstructor } from "@smithy/types";
2
+ /**
3
+ * Create a string describing the scope of credentials used to sign a request.
4
+ *
5
+ * @internal
6
+ *
7
+ * @param shortDate - the current calendar date in the form YYYYMMDD.
8
+ * @param region - the AWS region in which the service resides.
9
+ * @param service - the service to which the signed request is being sent.
10
+ */
11
+ export declare const createScope: (shortDate: string, region: string, service: string) => string;
12
+ /**
13
+ * Derive a signing key from its composite parts.
14
+ *
15
+ * @internal
16
+ *
17
+ * @param sha256Constructor - a constructor function that can instantiate SHA-256
18
+ * hash objects.
19
+ * @param credentials - the credentials with which the request will be
20
+ * signed.
21
+ * @param shortDate - the current calendar date in the form YYYYMMDD.
22
+ * @param region - the AWS region in which the service resides.
23
+ * @param service - the service to which the signed request is being
24
+ * sent.
25
+ */
26
+ export declare const getSigningKey: (sha256Constructor: ChecksumConstructor | HashConstructor, credentials: AwsCredentialIdentity, shortDate: string, region: string, service: string) => Promise<Uint8Array>;
27
+ /**
28
+ * @internal
29
+ */
30
+ export declare const clearCredentialCache: () => void;
@@ -0,0 +1,5 @@
1
+ import { HeaderBag, HttpRequest } from "@smithy/types";
2
+ /**
3
+ * @internal
4
+ */
5
+ export declare const getCanonicalHeaders: ({ headers }: HttpRequest, unsignableHeaders?: Set<string>, signableHeaders?: Set<string>) => HeaderBag;
@@ -0,0 +1,5 @@
1
+ import { HttpRequest } from "@smithy/types";
2
+ /**
3
+ * @internal
4
+ */
5
+ export declare const getCanonicalQuery: ({ query }: HttpRequest) => string;
@@ -0,0 +1,5 @@
1
+ import { ChecksumConstructor, HashConstructor, HttpRequest } from "@smithy/types";
2
+ /**
3
+ * @internal
4
+ */
5
+ export declare const getPayloadHash: ({ headers, body }: HttpRequest, hashConstructor: ChecksumConstructor | HashConstructor) => Promise<string>;
@@ -0,0 +1,4 @@
1
+ import { HeaderBag } from "@smithy/types";
2
+ export declare const hasHeader: (soughtHeader: string, headers: HeaderBag) => boolean;
3
+ export declare const getHeaderValue: (soughtHeader: string, headers: HeaderBag) => string | undefined;
4
+ export declare const deleteHeader: (soughtHeader: string, headers: HeaderBag) => void;
@@ -0,0 +1,12 @@
1
+ export { SignatureV4 } from "./SignatureV4";
2
+ export { ALGORITHM_IDENTIFIER, ALGORITHM_IDENTIFIER_V4A, ALGORITHM_QUERY_PARAM, ALWAYS_UNSIGNABLE_HEADERS, AMZ_DATE_HEADER, AMZ_DATE_QUERY_PARAM, AUTH_HEADER, CREDENTIAL_QUERY_PARAM, DATE_HEADER, EVENT_ALGORITHM_IDENTIFIER, EXPIRES_QUERY_PARAM, GENERATED_HEADERS, HOST_HEADER, KEY_TYPE_IDENTIFIER, MAX_CACHE_SIZE, MAX_PRESIGNED_TTL, PROXY_HEADER_PATTERN, REGION_SET_PARAM, SEC_HEADER_PATTERN, SHA256_HEADER, SIGNATURE_HEADER, SIGNATURE_QUERY_PARAM, SIGNED_HEADERS_QUERY_PARAM, TOKEN_HEADER, TOKEN_QUERY_PARAM, UNSIGNABLE_PATTERNS, UNSIGNED_PAYLOAD, } from "./constants";
3
+ export { getCanonicalHeaders } from "./getCanonicalHeaders";
4
+ export { getCanonicalQuery } from "./getCanonicalQuery";
5
+ export { getPayloadHash } from "./getPayloadHash";
6
+ export { moveHeadersToQuery } from "./moveHeadersToQuery";
7
+ export { prepareRequest } from "./prepareRequest";
8
+ export { clearCredentialCache, createScope, getSigningKey } from "./credentialDerivation";
9
+ export { SignatureV4Base, SignatureV4Init, SignatureV4CryptoInit } from "./SignatureV4Base";
10
+ export { hasHeader } from "./headerUtil";
11
+ export { signatureV4aContainer } from "./signature-v4a-container";
12
+ export { OptionalSigV4aSigner } from "./signature-v4a-container";
@@ -0,0 +1,10 @@
1
+ import { HttpRequest as IHttpRequest, QueryParameterBag } from "@smithy/types";
2
+ /**
3
+ * @internal
4
+ */
5
+ export declare const moveHeadersToQuery: (request: IHttpRequest, options?: {
6
+ unhoistableHeaders?: Set<string>;
7
+ hoistableHeaders?: Set<string>;
8
+ }) => IHttpRequest & {
9
+ query: QueryParameterBag;
10
+ };
@@ -0,0 +1,5 @@
1
+ import { HttpRequest as IHttpRequest } from "@smithy/types";
2
+ /**
3
+ * @internal
4
+ */
5
+ export declare const prepareRequest: (request: IHttpRequest) => IHttpRequest;
@@ -0,0 +1,23 @@
1
+ import { RequestSigner } from "@smithy/types";
2
+ /**
3
+ * @public
4
+ */
5
+ export type OptionalSigV4aSigner = {
6
+ /**
7
+ * This constructor is not typed so as not to require a type import
8
+ * from the signature-v4a package.
9
+ *
10
+ * The true type is SignatureV4a from @smithy/signature-v4a.
11
+ */
12
+ new (options: any): RequestSigner;
13
+ };
14
+ /**
15
+ * \@smithy/signature-v4a will install the constructor in this
16
+ * container if it's installed.
17
+ * This avoids a runtime-require being interpreted statically by bundlers.
18
+ *
19
+ * @public
20
+ */
21
+ export declare const signatureV4aContainer: {
22
+ SignatureV4a: null | OptionalSigV4aSigner;
23
+ };
@@ -0,0 +1,2 @@
1
+ export declare const iso8601: (time: number | string | Date) => string;
2
+ export declare const toDate: (time: number | string | Date) => Date;
package/package.json CHANGED
@@ -1,19 +1,17 @@
1
1
  {
2
2
  "name": "@smithy/signature-v4",
3
- "version": "5.6.3",
3
+ "version": "5.6.5",
4
4
  "description": "A standalone implementation of the AWS Signature V4 request signing algorithm",
5
5
  "main": "./dist-cjs/index.js",
6
6
  "module": "./dist-es/index.js",
7
7
  "types": "./dist-types/index.d.ts",
8
8
  "scripts": {
9
9
  "build": "concurrently 'yarn:build:types' 'yarn:build:es:cjs'",
10
- "build:es:cjs": "premove dist-es && yarn g:tsc -p tsconfig.es.json && node ../../scripts/inline",
10
+ "build:es:cjs": "node ../../scripts/compilation/es_cjs.js",
11
11
  "build:types": "premove dist-types && yarn g:tsc -p tsconfig.types.json",
12
12
  "build:types:downlevel": "premove dist-types/ts3.4 && downlevel-dts dist-types dist-types/ts3.4",
13
13
  "clean": "premove dist-cjs dist-es dist-types",
14
14
  "extract:docs": "api-extractor run --local",
15
- "format": "prettier --config ../../prettier.config.js --ignore-path ../../.prettierignore --write \"**/*.{ts,md,json}\"",
16
- "lint": "eslint -c ../../.eslintrc.js \"src/**/*.ts\"",
17
15
  "stage-release": "premove .release && yarn pack && mkdir ./.release && tar zxvf ./package.tgz --directory ./.release && rm ./package.tgz",
18
16
  "test": "yarn g:vitest run",
19
17
  "test:watch": "yarn g:vitest watch"
@@ -25,8 +23,8 @@
25
23
  "license": "Apache-2.0",
26
24
  "sideEffects": false,
27
25
  "dependencies": {
28
- "@smithy/core": "^3.29.2",
29
- "@smithy/types": "^4.16.0",
26
+ "@smithy/core": "^3.29.4",
27
+ "@smithy/types": "^4.16.1",
30
28
  "tslib": "^2.6.2"
31
29
  },
32
30
  "devDependencies": {