@openstax/ts-utils 1.31.1 → 1.32.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/dist/cjs/services/apiGateway/index.d.ts +1 -1
- package/dist/cjs/services/authProvider/browser.d.ts +3 -3
- package/dist/cjs/services/authProvider/decryption.d.ts +5 -1
- package/dist/cjs/services/authProvider/decryption.js +28 -18
- package/dist/cjs/services/authProvider/index.d.ts +6 -6
- package/dist/cjs/services/authProvider/subrequest.d.ts +1 -1
- package/dist/cjs/services/authProvider/subrequest.js +2 -7
- package/dist/cjs/services/authProvider/utils/userSubrequest.d.ts +3 -0
- package/dist/cjs/services/authProvider/utils/userSubrequest.js +13 -0
- package/dist/cjs/services/fileServer/index.d.ts +0 -11
- package/dist/cjs/services/fileServer/localFileServer.js +1 -48
- package/dist/cjs/services/fileServer/s3FileServer.js +0 -70
- package/dist/cjs/services/searchProvider/openSearch.js +1 -0
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/services/apiGateway/index.d.ts +1 -1
- package/dist/esm/services/authProvider/browser.d.ts +3 -3
- package/dist/esm/services/authProvider/decryption.d.ts +5 -1
- package/dist/esm/services/authProvider/decryption.js +28 -18
- package/dist/esm/services/authProvider/index.d.ts +6 -6
- package/dist/esm/services/authProvider/subrequest.d.ts +1 -1
- package/dist/esm/services/authProvider/subrequest.js +2 -4
- package/dist/esm/services/authProvider/utils/userSubrequest.d.ts +3 -0
- package/dist/esm/services/authProvider/utils/userSubrequest.js +6 -0
- package/dist/esm/services/fileServer/index.d.ts +0 -11
- package/dist/esm/services/fileServer/localFileServer.js +2 -49
- package/dist/esm/services/fileServer/s3FileServer.js +1 -68
- package/dist/esm/services/searchProvider/openSearch.js +1 -0
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +3 -4
- package/script/bin/.init-params-script.bash.swp +0 -0
|
@@ -38,7 +38,7 @@ export type ApiClientResponse<Ro> = Ro extends any ? {
|
|
|
38
38
|
} : never;
|
|
39
39
|
export type ExpandRoute<T> = T extends ((...args: infer A) => infer R) & {
|
|
40
40
|
renderUrl: (...args: infer Ar) => Promise<string>;
|
|
41
|
-
} ? (
|
|
41
|
+
} ? (...args: A) => R & {
|
|
42
42
|
renderUrl: (...args: Ar) => Promise<string>;
|
|
43
43
|
} : never;
|
|
44
44
|
export type MapRoutesToClient<Ru> = [Ru] extends [AnyRoute<Ru>] ? {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConfigProviderForConfig } from '../../config';
|
|
2
2
|
import { FetchConfig, GenericFetch } from '../../fetch';
|
|
3
|
-
import { User } from '.';
|
|
3
|
+
import { ApiUser, User } from '.';
|
|
4
4
|
type Config = {
|
|
5
5
|
accountsBase: string;
|
|
6
6
|
};
|
|
@@ -26,7 +26,7 @@ export interface Window {
|
|
|
26
26
|
addEventListener: (event: 'message', callback: EventHandler) => void;
|
|
27
27
|
removeEventListener: (event: 'message', callback: EventHandler) => void;
|
|
28
28
|
}
|
|
29
|
-
export type UpdatableUserFields = Partial<Pick<
|
|
29
|
+
export type UpdatableUserFields = Partial<Pick<ApiUser, 'consent_preferences' | 'first_name' | 'last_name'>>;
|
|
30
30
|
export declare const browserAuthProvider: <C extends string = "auth">({ window, configSpace }: Initializer<C>) => (configProvider: { [_key in C]: ConfigProviderForConfig<Config>; }) => {
|
|
31
31
|
/**
|
|
32
32
|
* gets the authentication token
|
|
@@ -65,7 +65,7 @@ export declare const browserAuthProvider: <C extends string = "auth">({ window,
|
|
|
65
65
|
* updates user settings, for example the cookie consent preferences
|
|
66
66
|
*/
|
|
67
67
|
updateUser: (updates: UpdatableUserFields) => Promise<{
|
|
68
|
-
user:
|
|
68
|
+
user: ApiUser;
|
|
69
69
|
token: string | null;
|
|
70
70
|
}>;
|
|
71
71
|
};
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import type { ConfigProviderForConfig } from '../../config';
|
|
2
|
-
import {
|
|
2
|
+
import { GenericFetch } from '../../fetch';
|
|
3
|
+
import { ApiUser, AuthProvider, CookieAuthProvider } from '.';
|
|
3
4
|
type Config = {
|
|
5
|
+
accountsBase: string;
|
|
4
6
|
cookieName: string;
|
|
5
7
|
encryptionPrivateKey: string;
|
|
6
8
|
signaturePublicKey: string;
|
|
7
9
|
};
|
|
8
10
|
interface Initializer<C> {
|
|
9
11
|
configSpace?: C;
|
|
12
|
+
fetch: GenericFetch;
|
|
10
13
|
}
|
|
11
14
|
export type DecryptionAuthProvider = AuthProvider & {
|
|
12
15
|
getTokenExpiration: (tokenString?: string) => Promise<number | null | undefined>;
|
|
16
|
+
loadUserData: () => Promise<ApiUser | undefined>;
|
|
13
17
|
};
|
|
14
18
|
export declare const decryptionAuthProvider: <C extends string = "decryption">(initializer: Initializer<C>) => (configProvider: { [_key in C]: ConfigProviderForConfig<Config>; }) => CookieAuthProvider<DecryptionAuthProvider>;
|
|
15
19
|
export {};
|
|
@@ -3,14 +3,17 @@ import { SessionExpiredError } from '../../errors';
|
|
|
3
3
|
import { ifDefined } from '../../guards';
|
|
4
4
|
import { once } from '../../misc/helpers';
|
|
5
5
|
import { decryptAndVerify } from './utils/decryptAndVerify';
|
|
6
|
+
import { loadUserData } from './utils/userSubrequest';
|
|
6
7
|
import { getAuthTokenOrCookie } from '.';
|
|
7
8
|
export const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
8
9
|
const config = configProvider[ifDefined(initializer.configSpace, 'decryption')];
|
|
10
|
+
const accountsBase = once(() => resolveConfigValue(config.accountsBase));
|
|
9
11
|
const cookieName = once(() => resolveConfigValue(config.cookieName));
|
|
10
12
|
const encryptionPrivateKey = once(() => resolveConfigValue(config.encryptionPrivateKey));
|
|
11
13
|
const signaturePublicKey = once(() => resolveConfigValue(config.signaturePublicKey));
|
|
12
14
|
return ({ request, logger }) => {
|
|
13
15
|
let user;
|
|
16
|
+
let userData;
|
|
14
17
|
const getAuthToken = async () => getAuthTokenOrCookie(request, await cookieName())[0];
|
|
15
18
|
const getAuthorizedFetchConfig = async () => {
|
|
16
19
|
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
@@ -19,40 +22,47 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
19
22
|
}
|
|
20
23
|
return { headers };
|
|
21
24
|
};
|
|
22
|
-
const
|
|
25
|
+
const getDecryptedPayload = async (tokenString) => {
|
|
23
26
|
const token = tokenString !== null && tokenString !== void 0 ? tokenString : await getAuthToken();
|
|
24
27
|
if (!token) {
|
|
25
28
|
return undefined;
|
|
26
29
|
}
|
|
27
30
|
return decryptAndVerify(token, await encryptionPrivateKey(), await signaturePublicKey());
|
|
28
31
|
};
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const getUser = async () => {
|
|
33
|
+
if (!user) {
|
|
34
|
+
const result = await getDecryptedPayload();
|
|
35
|
+
if (!result) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if ('error' in result && result.error == 'expired token') {
|
|
39
|
+
throw new SessionExpiredError();
|
|
40
|
+
}
|
|
41
|
+
if ('user' in result) {
|
|
42
|
+
logger.setContext({ user: result.user.uuid });
|
|
43
|
+
user = result.user;
|
|
44
|
+
}
|
|
40
45
|
}
|
|
41
|
-
return
|
|
46
|
+
return user;
|
|
42
47
|
};
|
|
43
48
|
return {
|
|
44
49
|
getAuthToken,
|
|
45
50
|
getAuthorizedFetchConfig,
|
|
46
51
|
getTokenExpiration: async (tokenString) => {
|
|
47
52
|
var _a;
|
|
48
|
-
const payload = await
|
|
53
|
+
const payload = await getDecryptedPayload(tokenString);
|
|
49
54
|
return payload ? ((_a = payload.exp) !== null && _a !== void 0 ? _a : null) : undefined;
|
|
50
55
|
},
|
|
51
|
-
getUser
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
getUser,
|
|
57
|
+
loadUserData: async () => {
|
|
58
|
+
if (!userData) {
|
|
59
|
+
const token = await getAuthToken();
|
|
60
|
+
if (!token) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
userData = await loadUserData(initializer.fetch, await accountsBase(), await cookieName(), token);
|
|
54
64
|
}
|
|
55
|
-
return
|
|
65
|
+
return userData;
|
|
56
66
|
},
|
|
57
67
|
};
|
|
58
68
|
};
|
|
@@ -10,14 +10,14 @@ export type ConsentPreferences = {
|
|
|
10
10
|
export type TokenUser = {
|
|
11
11
|
id: number;
|
|
12
12
|
name: string;
|
|
13
|
+
uuid: string;
|
|
14
|
+
faculty_status: string;
|
|
15
|
+
is_admin: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type ApiUser = TokenUser & {
|
|
13
18
|
first_name: string;
|
|
14
19
|
last_name: string;
|
|
15
20
|
full_name: string;
|
|
16
|
-
uuid: string;
|
|
17
|
-
faculty_status: string;
|
|
18
|
-
is_administrator: boolean;
|
|
19
|
-
} & Partial<ConsentPreferences>;
|
|
20
|
-
export interface ApiUser extends TokenUser {
|
|
21
21
|
contact_infos: Array<{
|
|
22
22
|
type: string;
|
|
23
23
|
value: string;
|
|
@@ -34,7 +34,7 @@ export interface ApiUser extends TokenUser {
|
|
|
34
34
|
self_reported_role: string;
|
|
35
35
|
signed_contract_names: string[];
|
|
36
36
|
using_openstax: boolean;
|
|
37
|
-
}
|
|
37
|
+
} & Partial<ConsentPreferences>;
|
|
38
38
|
export type User = TokenUser | ApiUser;
|
|
39
39
|
export type AuthProvider = {
|
|
40
40
|
getAuthToken: () => Promise<string | null>;
|
|
@@ -2,8 +2,8 @@ import { ConfigProviderForConfig } from '../../config';
|
|
|
2
2
|
import { GenericFetch } from '../../fetch';
|
|
3
3
|
import { CookieAuthProvider } from '.';
|
|
4
4
|
type Config = {
|
|
5
|
-
cookieName: string;
|
|
6
5
|
accountsBase: string;
|
|
6
|
+
cookieName: string;
|
|
7
7
|
};
|
|
8
8
|
interface Initializer<C> {
|
|
9
9
|
configSpace?: C;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import cookie from 'cookie';
|
|
2
1
|
import { once } from '../..';
|
|
3
2
|
import { resolveConfigValue } from '../../config';
|
|
4
3
|
import { ifDefined } from '../../guards';
|
|
4
|
+
import { loadUserData } from './utils/userSubrequest';
|
|
5
5
|
import { getAuthTokenOrCookie } from '.';
|
|
6
6
|
export const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
7
7
|
const config = configProvider[ifDefined(initializer.configSpace, 'subrequest')];
|
|
@@ -23,9 +23,7 @@ export const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
|
23
23
|
if (!token) {
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
|
-
const
|
|
27
|
-
const user = await initializer.fetch((await accountsBase()).replace(/\/+$/, '') + '/api/user', { headers })
|
|
28
|
-
.then(response => response.json());
|
|
26
|
+
const user = await loadUserData(initializer.fetch, await accountsBase(), resolvedCookieName, token);
|
|
29
27
|
if (user) {
|
|
30
28
|
logger.setContext({ user: user.uuid });
|
|
31
29
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import cookie from 'cookie';
|
|
2
|
+
export const loadUserData = (fetch, accountsBase, cookieName, token) => {
|
|
3
|
+
const headers = { cookie: cookie.serialize(cookieName, token) };
|
|
4
|
+
return fetch(accountsBase.replace(/\/+$/, '') + '/api/user', { headers })
|
|
5
|
+
.then(response => response.json());
|
|
6
|
+
};
|
|
@@ -14,16 +14,5 @@ export interface FileServerAdapter {
|
|
|
14
14
|
putFileContent: (source: FileValue, content: string) => Promise<FileValue>;
|
|
15
15
|
getSignedViewerUrl: (source: FileValue) => Promise<string>;
|
|
16
16
|
getFileContent: (source: FileValue) => Promise<Buffer>;
|
|
17
|
-
getSignedFileUploadConfig: () => Promise<{
|
|
18
|
-
url: string;
|
|
19
|
-
payload: {
|
|
20
|
-
[key: string]: string;
|
|
21
|
-
};
|
|
22
|
-
}>;
|
|
23
|
-
copyFileTo: (source: FileValue, destinationPath: string) => Promise<FileValue>;
|
|
24
|
-
copyFileToDirectory: (source: FileValue, destinationDirectory: string) => Promise<FileValue>;
|
|
25
|
-
isTemporaryUpload: (source: FileValue) => boolean;
|
|
26
|
-
getFileChecksum: (source: FileValue) => Promise<string>;
|
|
27
|
-
filesEqual: (sourceA: FileValue, sourceB: FileValue) => Promise<boolean>;
|
|
28
17
|
}
|
|
29
18
|
export declare const isFileOrFolder: (thing: any) => thing is FileValue | FolderValue;
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/* cspell:ignore originalname */
|
|
2
|
-
import crypto from 'crypto';
|
|
3
2
|
import fs from 'fs';
|
|
4
3
|
import https from 'https';
|
|
5
4
|
import path from 'path';
|
|
6
5
|
import cors from 'cors';
|
|
7
6
|
import express from 'express';
|
|
8
7
|
import multer from 'multer';
|
|
9
|
-
import { v4 as uuid } from 'uuid';
|
|
10
8
|
import { assertString } from '../../assertions';
|
|
11
9
|
import { resolveConfigValue } from '../../config';
|
|
12
10
|
import { ifDefined } from '../../guards';
|
|
13
|
-
import {
|
|
11
|
+
import { once } from '../../misc/helpers';
|
|
14
12
|
/* istanbul ignore next */
|
|
15
|
-
const startServer =
|
|
13
|
+
const startServer = once((port, uploadDir) => {
|
|
16
14
|
// TODO - re-evaluate the `preservePath` behavior to match whatever s3 does
|
|
17
15
|
const upload = multer({ dest: uploadDir, preservePath: true });
|
|
18
16
|
const fileServerApp = express();
|
|
@@ -68,54 +66,9 @@ export const localFileServer = (initializer) => (configProvider) => {
|
|
|
68
66
|
await fs.promises.writeFile(filePath, content);
|
|
69
67
|
return source;
|
|
70
68
|
};
|
|
71
|
-
const getSignedFileUploadConfig = async () => {
|
|
72
|
-
const prefix = 'uploads/' + uuid();
|
|
73
|
-
return {
|
|
74
|
-
url: `https://${await host}:${await port}/`,
|
|
75
|
-
payload: {
|
|
76
|
-
key: prefix + '/${filename}',
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
const copyFileTo = async (source, destinationPath) => {
|
|
81
|
-
const sourcePath = path.join(await fileDir, source.path);
|
|
82
|
-
const destPath = path.join(await fileDir, destinationPath);
|
|
83
|
-
const destDirectory = path.dirname(destPath);
|
|
84
|
-
await fs.promises.mkdir(destDirectory, { recursive: true });
|
|
85
|
-
await fs.promises.copyFile(sourcePath, destPath);
|
|
86
|
-
return {
|
|
87
|
-
...source,
|
|
88
|
-
path: destinationPath
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
const copyFileToDirectory = async (source, destination) => {
|
|
92
|
-
const destinationPath = path.join(destination, source.label);
|
|
93
|
-
return copyFileTo(source, destinationPath);
|
|
94
|
-
};
|
|
95
|
-
const isTemporaryUpload = (source) => {
|
|
96
|
-
return source.path.indexOf('uploads/') === 0;
|
|
97
|
-
};
|
|
98
|
-
const getFileChecksum = async (source) => {
|
|
99
|
-
const filePath = path.join(await fileDir, source.path);
|
|
100
|
-
const fileContent = await fs.promises.readFile(filePath);
|
|
101
|
-
return crypto.createHash('md5').update(fileContent).digest('hex');
|
|
102
|
-
};
|
|
103
|
-
const filesEqual = async (sourceA, sourceB) => {
|
|
104
|
-
const [aSum, bSum] = await Promise.all([
|
|
105
|
-
getFileChecksum(sourceA),
|
|
106
|
-
getFileChecksum(sourceB)
|
|
107
|
-
]);
|
|
108
|
-
return aSum === bSum;
|
|
109
|
-
};
|
|
110
69
|
return {
|
|
111
70
|
getSignedViewerUrl,
|
|
112
71
|
getFileContent,
|
|
113
72
|
putFileContent,
|
|
114
|
-
getSignedFileUploadConfig,
|
|
115
|
-
copyFileTo,
|
|
116
|
-
copyFileToDirectory,
|
|
117
|
-
isTemporaryUpload,
|
|
118
|
-
getFileChecksum,
|
|
119
|
-
filesEqual,
|
|
120
73
|
};
|
|
121
74
|
};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/* cspell:ignore presigner */
|
|
2
|
-
import {
|
|
3
|
-
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
|
|
2
|
+
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
4
3
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { v4 as uuid } from 'uuid';
|
|
7
4
|
import { once } from '../..';
|
|
8
5
|
import { assertDefined } from '../../assertions';
|
|
9
6
|
import { resolveConfigValue } from '../../config';
|
|
@@ -44,73 +41,9 @@ export const s3FileServer = (initializer) => (configProvider) => {
|
|
|
44
41
|
await (await s3Service()).send(command);
|
|
45
42
|
return source;
|
|
46
43
|
};
|
|
47
|
-
/*
|
|
48
|
-
* https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_presigned_post.html
|
|
49
|
-
* https://docs.aws.amazon.com/AmazonS3/latest/userguide/HTTPPOSTExamples.html
|
|
50
|
-
* https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
|
|
51
|
-
*/
|
|
52
|
-
const getSignedFileUploadConfig = async () => {
|
|
53
|
-
const prefix = 'uploads/' + uuid();
|
|
54
|
-
const bucket = (await bucketName());
|
|
55
|
-
const Conditions = [
|
|
56
|
-
{ acl: 'private' },
|
|
57
|
-
{ bucket },
|
|
58
|
-
['starts-with', '$key', prefix]
|
|
59
|
-
];
|
|
60
|
-
const defaultFields = {
|
|
61
|
-
acl: 'private',
|
|
62
|
-
};
|
|
63
|
-
const { url, fields } = await createPresignedPost(await s3Service(), {
|
|
64
|
-
Bucket: bucket,
|
|
65
|
-
Key: prefix + '/${filename}',
|
|
66
|
-
Conditions,
|
|
67
|
-
Fields: defaultFields,
|
|
68
|
-
Expires: 3600, // 1 hour
|
|
69
|
-
});
|
|
70
|
-
return {
|
|
71
|
-
url, payload: fields
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
const copyFileTo = async (source, destinationPath) => {
|
|
75
|
-
const bucket = (await bucketName());
|
|
76
|
-
const destinationPathWithoutLeadingSlash = destinationPath.replace(/^\//, '');
|
|
77
|
-
const command = new CopyObjectCommand({
|
|
78
|
-
Bucket: bucket,
|
|
79
|
-
Key: destinationPathWithoutLeadingSlash,
|
|
80
|
-
CopySource: path.join(bucket, source.path),
|
|
81
|
-
});
|
|
82
|
-
await (await s3Service()).send(command);
|
|
83
|
-
return {
|
|
84
|
-
...source,
|
|
85
|
-
path: destinationPathWithoutLeadingSlash
|
|
86
|
-
};
|
|
87
|
-
};
|
|
88
|
-
const copyFileToDirectory = async (source, destination) => {
|
|
89
|
-
const destinationPath = path.join(destination, source.label);
|
|
90
|
-
return copyFileTo(source, destinationPath);
|
|
91
|
-
};
|
|
92
|
-
const isTemporaryUpload = (source) => {
|
|
93
|
-
return source.path.indexOf('uploads/') === 0;
|
|
94
|
-
};
|
|
95
|
-
const getFileChecksum = async (source) => {
|
|
96
|
-
const bucket = (await bucketName());
|
|
97
|
-
const command = new HeadObjectCommand({ Bucket: bucket, Key: source.path });
|
|
98
|
-
const response = await (await s3Service()).send(command);
|
|
99
|
-
return assertDefined(response.ETag);
|
|
100
|
-
};
|
|
101
|
-
const filesEqual = async (sourceA, sourceB) => {
|
|
102
|
-
const [aSum, bSum] = await Promise.all([getFileChecksum(sourceA), getFileChecksum(sourceB)]);
|
|
103
|
-
return aSum === bSum;
|
|
104
|
-
};
|
|
105
44
|
return {
|
|
106
45
|
getFileContent,
|
|
107
46
|
putFileContent,
|
|
108
47
|
getSignedViewerUrl,
|
|
109
|
-
getSignedFileUploadConfig,
|
|
110
|
-
copyFileTo,
|
|
111
|
-
copyFileToDirectory,
|
|
112
|
-
isTemporaryUpload,
|
|
113
|
-
getFileChecksum,
|
|
114
|
-
filesEqual,
|
|
115
48
|
};
|
|
116
49
|
};
|
|
@@ -29,6 +29,7 @@ export const openSearchService = (initializer = {}) => (configProvider) => {
|
|
|
29
29
|
sniffOnConnectionFault: true,
|
|
30
30
|
sniffOnStart: true,
|
|
31
31
|
resurrectStrategy: 'ping',
|
|
32
|
+
agent: { keepAlive: false },
|
|
32
33
|
node: await resolveConfigValue(config.node),
|
|
33
34
|
}));
|
|
34
35
|
return (indexConfig) => {
|