@adobe/aio-cli-plugin-api-mesh 5.2.1-beta → 5.2.2-beta
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/oclif.manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"5.2.1-beta","commands":{"PLUGINNAME":{"id":"PLUGINNAME","description":"Your description here","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio PLUGINNAME:some_command"],"flags":{"someflag":{"name":"someflag","type":"option","char":"f","description":"this is some flag"}},"args":[]},"api-mesh:create":{"id":"api-mesh:create","description":"Create a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":false}},"args":[{"name":"file"}]},"api-mesh:delete":{"id":"api-mesh:delete","description":"Delete the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false}},"args":[]},"api-mesh:describe":{"id":"api-mesh:describe","description":"Get details of a mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:get":{"id":"api-mesh:get","description":"Get the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:init":{"id":"api-mesh:init","description":"This command will create a workspace where you can organise your API mesh configuration and other files","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":[{"description":"API mesh workspace init","command":"aio api-mesh init commerce-mesh"},{"description":"API mesh workspace init with flags","command":"aio api-mesh init commerce-mesh --path ./mesh_projects/test_mesh --git y --packageManager yarn"}],"flags":{"path":{"name":"path","type":"option","char":"p","default":"."},"packageManager":{"name":"packageManager","type":"option","char":"m","options":["npm","yarn"]},"git":{"name":"git","type":"option","char":"g","options":["y","n"]}},"args":[{"name":"projectName","description":"Project name","required":true}]},"api-mesh:log-get-bulk":{"id":"api-mesh:log-get-bulk","description":"Download all mesh logs for a selected time period.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"startTime":{"name":"startTime","type":"option","description":"Start time for the logs in UTC","required":true},"endTime":{"name":"endTime","type":"option","description":"End time for the logs in UTC","required":true},"filename":{"name":"filename","type":"option","description":"Path to the output file for logs","required":true}},"args":[]},"api-mesh:log-get":{"id":"api-mesh:log-get","description":"Get the Log of a given mesh by RayId","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"rayId","description":"Fetch a single log by rayID","required":true}]},"api-mesh:log-list":{"id":"api-mesh:log-list","description":"Get recent logs of requests made to the API Mesh.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"filename":{"name":"filename","type":"option","description":"Name of CSV file to export the recent logs to"}},"args":[]},"api-mesh:run":{"id":"api-mesh:run","description":"Run a local development server that builds and compiles a mesh locally","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":[],"flags":{"port":{"name":"port","type":"option","char":"p","description":"Port number for the local dev server"},"debug":{"name":"debug","type":"boolean","description":"Enable debugging mode","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"select":{"name":"select","type":"boolean","description":"Retrieve existing artifacts from the mesh","allowNo":false},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":false}},"args":[{"name":"file","description":"Mesh File"}]},"api-mesh:status":{"id":"api-mesh:status","description":"Get a mesh status with a given meshid.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:update":{"id":"api-mesh:update","description":"Update a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":false}},"args":[{"name":"file"}]},"api-mesh:cache:purge":{"id":"api-mesh:cache:purge","description":"Cache purge for a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"all":{"name":"all","type":"boolean","char":"a","description":"Purge all cache. CLI will purge all cache data.","required":true,"allowNo":false}},"args":[]},"api-mesh:source:discover":{"id":"api-mesh:source:discover","description":"Return the list of avaliable sources","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm install action prompt. CLI will not check ask user to install source.","allowNo":false}},"args":[]},"api-mesh:source:get":{"id":"api-mesh:source:get","description":"Command returns the content of a specific source.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:get -s=<version>@<source_name>","$ aio api-mesh:source:get -s<source_name>","$ aio api-mesh:source:get -m"],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm print action prompt. CLI will not check ask user to print source.","allowNo":false},"source":{"name":"source","type":"option","char":"s","description":"Source name"},"multiple":{"name":"multiple","type":"boolean","char":"m","description":"Select multiple sources","allowNo":false}},"args":[]},"api-mesh:source:install":{"id":"api-mesh:source:install","description":"Command to install the source to your API mesh.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:install <version>@<source_name>","$ aio api-mesh:source:install <source_name> -v <variable_name>=<variable_value>","$ aio api-mesh:source:install <source_name> -f <path_to_variables_file>"],"flags":{"source":{"name":"source","type":"option","char":"s","description":"Source name"},"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm override action prompt. CLI will not check ask user to override source.","allowNo":false},"variable":{"name":"variable","type":"option","char":"v","description":"Variables required for the source"},"variable-file":{"name":"variable-file","type":"option","char":"f","description":"Variables file path"},"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"source"}]}}}
|
|
1
|
+
{"version":"5.2.2-beta","commands":{"PLUGINNAME":{"id":"PLUGINNAME","description":"Your description here","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio PLUGINNAME:some_command"],"flags":{"someflag":{"name":"someflag","type":"option","char":"f","description":"this is some flag"}},"args":[]},"api-mesh:create":{"id":"api-mesh:create","description":"Create a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":false}},"args":[{"name":"file"}]},"api-mesh:delete":{"id":"api-mesh:delete","description":"Delete the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false}},"args":[]},"api-mesh:describe":{"id":"api-mesh:describe","description":"Get details of a mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:get":{"id":"api-mesh:get","description":"Get the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:init":{"id":"api-mesh:init","description":"This command will create a workspace where you can organise your API mesh configuration and other files","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":[{"description":"API mesh workspace init","command":"aio api-mesh init commerce-mesh"},{"description":"API mesh workspace init with flags","command":"aio api-mesh init commerce-mesh --path ./mesh_projects/test_mesh --git y --packageManager yarn"}],"flags":{"path":{"name":"path","type":"option","char":"p","default":"."},"packageManager":{"name":"packageManager","type":"option","char":"m","options":["npm","yarn"]},"git":{"name":"git","type":"option","char":"g","options":["y","n"]}},"args":[{"name":"projectName","description":"Project name","required":true}]},"api-mesh:log-get-bulk":{"id":"api-mesh:log-get-bulk","description":"Download all mesh logs for a selected time period.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"startTime":{"name":"startTime","type":"option","description":"Start time for the logs in UTC"},"endTime":{"name":"endTime","type":"option","description":"End time for the logs in UTC"},"filename":{"name":"filename","type":"option","description":"Path to the output file for logs","required":true},"past":{"name":"past","type":"option","description":"Past time window in mins"},"from":{"name":"from","type":"option","description":"The from time in YYYY-MM-DD:HH:MM:SS format based on your system's time zone. It is used to fetch logs from the past and is the starting time for the past time duration."}},"args":[]},"api-mesh:log-get":{"id":"api-mesh:log-get","description":"Get the Log of a given mesh by RayId","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"rayId","description":"Fetch a single log by rayID","required":true}]},"api-mesh:log-list":{"id":"api-mesh:log-list","description":"Get recent logs of requests made to the API Mesh.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"filename":{"name":"filename","type":"option","description":"Name of CSV file to export the recent logs to"}},"args":[]},"api-mesh:run":{"id":"api-mesh:run","description":"Run a local development server that builds and compiles a mesh locally","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":[],"flags":{"port":{"name":"port","type":"option","char":"p","description":"Port number for the local dev server"},"debug":{"name":"debug","type":"boolean","description":"Enable debugging mode","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"select":{"name":"select","type":"boolean","description":"Retrieve existing artifacts from the mesh","allowNo":false},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":false}},"args":[{"name":"file","description":"Mesh File"}]},"api-mesh:status":{"id":"api-mesh:status","description":"Get a mesh status with a given meshid.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:update":{"id":"api-mesh:update","description":"Update a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":false}},"args":[{"name":"file"}]},"api-mesh:cache:purge":{"id":"api-mesh:cache:purge","description":"Cache purge for a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"all":{"name":"all","type":"boolean","char":"a","description":"Purge all cache. CLI will purge all cache data.","required":true,"allowNo":false}},"args":[]},"api-mesh:source:discover":{"id":"api-mesh:source:discover","description":"Return the list of avaliable sources","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm install action prompt. CLI will not check ask user to install source.","allowNo":false}},"args":[]},"api-mesh:source:get":{"id":"api-mesh:source:get","description":"Command returns the content of a specific source.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:get -s=<version>@<source_name>","$ aio api-mesh:source:get -s<source_name>","$ aio api-mesh:source:get -m"],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm print action prompt. CLI will not check ask user to print source.","allowNo":false},"source":{"name":"source","type":"option","char":"s","description":"Source name"},"multiple":{"name":"multiple","type":"boolean","char":"m","description":"Select multiple sources","allowNo":false}},"args":[]},"api-mesh:source:install":{"id":"api-mesh:source:install","description":"Command to install the source to your API mesh.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:install <version>@<source_name>","$ aio api-mesh:source:install <source_name> -v <variable_name>=<variable_value>","$ aio api-mesh:source:install <source_name> -f <path_to_variables_file>"],"flags":{"source":{"name":"source","type":"option","char":"s","description":"Source name"},"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm override action prompt. CLI will not check ask user to override source.","allowNo":false},"variable":{"name":"variable","type":"option","char":"v","description":"Variables required for the source"},"variable-file":{"name":"variable-file","type":"option","char":"f","description":"Variables file path"},"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"source"}]}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/aio-cli-plugin-api-mesh",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.2-beta",
|
|
4
4
|
"description": "Adobe I/O CLI plugin to develop and manage API mesh sources",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"oclif-plugin"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"version": "oclif-dev readme && git add README.md"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@adobe-apimesh/mesh-builder": "2.1.
|
|
40
|
+
"@adobe-apimesh/mesh-builder": "2.1.5",
|
|
41
41
|
"@adobe/aio-cli-lib-console": "^5.0.0",
|
|
42
42
|
"@adobe/aio-lib-core-config": "^5.0.0",
|
|
43
43
|
"@adobe/aio-lib-core-logging": "^3.0.0",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@adobe/aio-lib-ims": "^7.0.1",
|
|
46
46
|
"@adobe/plugin-hooks": "0.3.3",
|
|
47
47
|
"@adobe/plugin-on-fetch": "0.1.1",
|
|
48
|
-
"@adobe/plugin-source-headers": "^0.0.
|
|
48
|
+
"@adobe/plugin-source-headers": "^0.0.2",
|
|
49
49
|
"@envelop/disable-introspection": "^6.0.0",
|
|
50
50
|
"@graphql-mesh/cli": "0.82.30",
|
|
51
51
|
"@graphql-mesh/graphql": "0.34.13",
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"jsmin": "1.0.1",
|
|
91
91
|
"json-interpolate": "^1.0.3",
|
|
92
92
|
"lru-cache": "^7.14.1",
|
|
93
|
+
"ms": "^2.1.3",
|
|
93
94
|
"node-clipboardy": "^1.0.3",
|
|
94
95
|
"node-fetch": "2.6.1",
|
|
95
96
|
"pino": "^9.5.0",
|
|
@@ -3,7 +3,11 @@ const path = require('path');
|
|
|
3
3
|
const GetBulkLogCommand = require('../log-get-bulk');
|
|
4
4
|
const { initRequestId, initSdk, promptConfirm } = require('../../../helpers');
|
|
5
5
|
const { getMeshId, getPresignedUrls } = require('../../../lib/devConsole');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
suggestCorrectedDateFormat,
|
|
8
|
+
validateDateTimeRange,
|
|
9
|
+
parsePastDuration,
|
|
10
|
+
} = require('../../../utils');
|
|
7
11
|
|
|
8
12
|
jest.mock('fs');
|
|
9
13
|
jest.mock('axios');
|
|
@@ -86,7 +90,7 @@ describe('GetBulkLogCommand', () => {
|
|
|
86
90
|
|
|
87
91
|
const command = new GetBulkLogCommand([], {});
|
|
88
92
|
await expect(command.run()).rejects.toThrow(
|
|
89
|
-
'
|
|
93
|
+
'The maximum duration between startTime and endTime is 30 minutes. The current duration is 0 hours 45 minutes and 0 seconds.',
|
|
90
94
|
);
|
|
91
95
|
});
|
|
92
96
|
|
|
@@ -291,3 +295,267 @@ describe('GetBulkLogCommand startTime and endTime validation', () => {
|
|
|
291
295
|
},
|
|
292
296
|
);
|
|
293
297
|
});
|
|
298
|
+
|
|
299
|
+
describe('GetBulkLogCommand with --past and --from flags', () => {
|
|
300
|
+
let parseSpy;
|
|
301
|
+
|
|
302
|
+
let now;
|
|
303
|
+
let fromDate;
|
|
304
|
+
beforeEach(() => {
|
|
305
|
+
now = new Date();
|
|
306
|
+
fromDate = new Date(now);
|
|
307
|
+
fromDate.setDate(fromDate.getDate() - 29); // Set fromDate to 29 days ago
|
|
308
|
+
parseSpy = jest.spyOn(GetBulkLogCommand.prototype, 'parse').mockResolvedValue({
|
|
309
|
+
flags: {
|
|
310
|
+
past: '20mins',
|
|
311
|
+
from: fromDate.toISOString().slice(0, 10) + ':12:00:00',
|
|
312
|
+
filename: 'test.csv',
|
|
313
|
+
ignoreCache: false,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
initSdk.mockResolvedValue({
|
|
318
|
+
imsOrgId: 'orgId',
|
|
319
|
+
imsOrgCode: 'orgCode',
|
|
320
|
+
projectId: 'projectId',
|
|
321
|
+
workspaceId: 'workspaceId',
|
|
322
|
+
workspaceName: 'workspaceName',
|
|
323
|
+
});
|
|
324
|
+
getMeshId.mockResolvedValue('meshId');
|
|
325
|
+
getPresignedUrls.mockResolvedValue({
|
|
326
|
+
presignedUrls: [{ key: 'log1.csv', url: 'http://example.com/someHash' }],
|
|
327
|
+
totalSize: 2048,
|
|
328
|
+
});
|
|
329
|
+
promptConfirm.mockResolvedValue(true);
|
|
330
|
+
global.requestId = 'dummy_request_id';
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
afterEach(() => {
|
|
334
|
+
jest.clearAllMocks();
|
|
335
|
+
// clear the date objects
|
|
336
|
+
now = null;
|
|
337
|
+
fromDate = null;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('runs with valid --past and --from flags', async () => {
|
|
341
|
+
fs.existsSync.mockReturnValue(true);
|
|
342
|
+
fs.statSync.mockReturnValue({ size: 0 });
|
|
343
|
+
|
|
344
|
+
const mockWriteStream = {
|
|
345
|
+
write: jest.fn(),
|
|
346
|
+
end: jest.fn(),
|
|
347
|
+
on: jest.fn((event, callback) => {
|
|
348
|
+
if (event === 'finish') {
|
|
349
|
+
callback();
|
|
350
|
+
}
|
|
351
|
+
}),
|
|
352
|
+
};
|
|
353
|
+
fs.createWriteStream.mockReturnValue(mockWriteStream);
|
|
354
|
+
|
|
355
|
+
const command = new GetBulkLogCommand([], {});
|
|
356
|
+
await command.run();
|
|
357
|
+
|
|
358
|
+
expect(initRequestId).toHaveBeenCalled();
|
|
359
|
+
expect(initSdk).toHaveBeenCalled();
|
|
360
|
+
expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName');
|
|
361
|
+
expect(getPresignedUrls).toHaveBeenCalledWith(
|
|
362
|
+
'orgCode',
|
|
363
|
+
'projectId',
|
|
364
|
+
'workspaceId',
|
|
365
|
+
'meshId',
|
|
366
|
+
expect.any(String),
|
|
367
|
+
expect.any(String),
|
|
368
|
+
);
|
|
369
|
+
expect(fs.createWriteStream).toHaveBeenCalledWith(path.resolve(process.cwd(), 'test.csv'), {
|
|
370
|
+
flags: 'a',
|
|
371
|
+
});
|
|
372
|
+
expect(mockWriteStream.write).toHaveBeenCalled();
|
|
373
|
+
expect(mockWriteStream.end).toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('throws an error with invalid --from date components', async () => {
|
|
377
|
+
parseSpy.mockResolvedValueOnce({
|
|
378
|
+
flags: {
|
|
379
|
+
past: '20mins',
|
|
380
|
+
from: fromDate.toISOString().slice(0, 10) + ':25:61:61',
|
|
381
|
+
filename: 'test.csv',
|
|
382
|
+
ignoreCache: false,
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const command = new GetBulkLogCommand([], {});
|
|
387
|
+
await expect(command.run()).rejects.toThrow(
|
|
388
|
+
'Invalid date components passed in --from. Correct the date.',
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('throws an error with invalid --from date format', async () => {
|
|
393
|
+
parseSpy.mockResolvedValueOnce({
|
|
394
|
+
flags: {
|
|
395
|
+
past: '15mins',
|
|
396
|
+
from: fromDate.toISOString().slice(0, 10).replace(/-/g, ':') + ':15:00:00',
|
|
397
|
+
filename: 'test.csv',
|
|
398
|
+
ignoreCache: false,
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const command = new GetBulkLogCommand([], {});
|
|
403
|
+
await expect(command.run()).rejects.toThrow(
|
|
404
|
+
'Invalid format. Use the format YYYY-MM-DD:HH:MM:SS for --from.',
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test('runs with valid --past flag without --from', async () => {
|
|
409
|
+
parseSpy.mockResolvedValueOnce({
|
|
410
|
+
flags: {
|
|
411
|
+
past: '15mins',
|
|
412
|
+
filename: 'test.csv',
|
|
413
|
+
ignoreCache: false,
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
fs.existsSync.mockReturnValue(true);
|
|
418
|
+
fs.statSync.mockReturnValue({ size: 0 });
|
|
419
|
+
|
|
420
|
+
const command = new GetBulkLogCommand([], {});
|
|
421
|
+
await command.run();
|
|
422
|
+
|
|
423
|
+
expect(initRequestId).toHaveBeenCalled();
|
|
424
|
+
expect(initSdk).toHaveBeenCalled();
|
|
425
|
+
expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName');
|
|
426
|
+
expect(getPresignedUrls).toHaveBeenCalledWith(
|
|
427
|
+
'orgCode',
|
|
428
|
+
'projectId',
|
|
429
|
+
'workspaceId',
|
|
430
|
+
'meshId',
|
|
431
|
+
expect.any(String),
|
|
432
|
+
expect.any(String),
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test('throws an error with edge case for --past duration', async () => {
|
|
437
|
+
parseSpy.mockResolvedValueOnce({
|
|
438
|
+
flags: {
|
|
439
|
+
past: '0s',
|
|
440
|
+
from: fromDate.toISOString().slice(0, 10) + ':12:00:00',
|
|
441
|
+
filename: 'test.csv',
|
|
442
|
+
ignoreCache: false,
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const command = new GetBulkLogCommand([], {});
|
|
447
|
+
await expect(command.run()).rejects.toThrow(
|
|
448
|
+
'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".',
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test('runs with edge case for --from date', async () => {
|
|
453
|
+
parseSpy.mockResolvedValueOnce({
|
|
454
|
+
flags: {
|
|
455
|
+
past: '15mins',
|
|
456
|
+
from: fromDate.toISOString().slice(0, 10) + ':00:00:00',
|
|
457
|
+
filename: 'test.csv',
|
|
458
|
+
ignoreCache: false,
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
fs.existsSync.mockReturnValue(true);
|
|
463
|
+
fs.statSync.mockReturnValue({ size: 0 });
|
|
464
|
+
|
|
465
|
+
const command = new GetBulkLogCommand([], {});
|
|
466
|
+
await command.run();
|
|
467
|
+
|
|
468
|
+
expect(initRequestId).toHaveBeenCalled();
|
|
469
|
+
expect(initSdk).toHaveBeenCalled();
|
|
470
|
+
expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName');
|
|
471
|
+
expect(getPresignedUrls).toHaveBeenCalledWith(
|
|
472
|
+
'orgCode',
|
|
473
|
+
'projectId',
|
|
474
|
+
'workspaceId',
|
|
475
|
+
'meshId',
|
|
476
|
+
expect.any(String),
|
|
477
|
+
expect.any(String),
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
describe('validateDateTimeRange', () => {
|
|
483
|
+
const testCases = [
|
|
484
|
+
{
|
|
485
|
+
startTime: '2025-03-09T12:00:00Z',
|
|
486
|
+
endTime: '2025-03-09T12:45:00Z',
|
|
487
|
+
error:
|
|
488
|
+
'The maximum duration between startTime and endTime is 30 minutes. The current duration is 0 hours 45 minutes and 0 seconds.',
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
startTime: new Date().toISOString(),
|
|
492
|
+
endTime: new Date(new Date().getTime() + 45 * 60 * 1000).toISOString(),
|
|
493
|
+
error: 'endTime cannot be in the future. Provide a valid endTime.',
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
startTime: new Date(new Date().setUTCDate(new Date().getUTCDate() - 31)).toISOString(),
|
|
497
|
+
endTime: '2025-03-10T12:00:00Z',
|
|
498
|
+
error: 'Cannot get logs more than 30 days old. Adjust your time range.',
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
startTime: '2025-03-09T12:00:00Z',
|
|
502
|
+
endTime: '2025-03-09T12:00:00Z',
|
|
503
|
+
error: 'The minimum duration is 1 minutes. The current duration is 0 minutes.',
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
startTime: '2025-03-09T12:30:00Z',
|
|
507
|
+
endTime: '2025-03-09T12:00:00Z',
|
|
508
|
+
error: 'endTime must be greater than startTime',
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
startTime: '2025-03-09T12:00:00Z',
|
|
512
|
+
endTime: '2025-03-09T12:20:00Z',
|
|
513
|
+
error: null,
|
|
514
|
+
},
|
|
515
|
+
];
|
|
516
|
+
|
|
517
|
+
test.each(testCases)(
|
|
518
|
+
'validates time range for startTime: $startTime and endTime: $endTime',
|
|
519
|
+
({ startTime, endTime, error }) => {
|
|
520
|
+
if (error) {
|
|
521
|
+
expect(() => validateDateTimeRange(startTime, endTime)).toThrow(error);
|
|
522
|
+
} else {
|
|
523
|
+
expect(() => validateDateTimeRange(startTime, endTime)).not.toThrow();
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
describe('parsePastDuration', () => {
|
|
530
|
+
const validDurations = [
|
|
531
|
+
['20m', 20 * 60 * 1000],
|
|
532
|
+
['20 m', 20 * 60 * 1000],
|
|
533
|
+
['20min', 20 * 60 * 1000],
|
|
534
|
+
['20 min', 20 * 60 * 1000],
|
|
535
|
+
['20mins', 20 * 60 * 1000],
|
|
536
|
+
['20 mins', 20 * 60 * 1000],
|
|
537
|
+
['20minute', 20 * 60 * 1000],
|
|
538
|
+
['20 minute', 20 * 60 * 1000],
|
|
539
|
+
['20minutes', 20 * 60 * 1000],
|
|
540
|
+
['20 minutes', 20 * 60 * 1000],
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
test.each(validDurations)(
|
|
544
|
+
'parses valid past duration "%s" correctly',
|
|
545
|
+
(pastDuration, expectedDurationInMs) => {
|
|
546
|
+
const durationInMs = parsePastDuration(pastDuration);
|
|
547
|
+
expect(durationInMs).toBe(expectedDurationInMs);
|
|
548
|
+
},
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const invalidDurations = ['20h', '20 hours', '20s', '20 seconds'];
|
|
552
|
+
|
|
553
|
+
test.each(invalidDurations)(
|
|
554
|
+
'throws an error for invalid past duration format "%s"',
|
|
555
|
+
invalidPastDuration => {
|
|
556
|
+
expect(() => parsePastDuration(invalidPastDuration)).toThrow(
|
|
557
|
+
'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".',
|
|
558
|
+
);
|
|
559
|
+
},
|
|
560
|
+
);
|
|
561
|
+
});
|
|
@@ -10,7 +10,13 @@ const {
|
|
|
10
10
|
startTimeFlag,
|
|
11
11
|
endTimeFlag,
|
|
12
12
|
logFilenameFlag,
|
|
13
|
+
pastFlag,
|
|
14
|
+
fromFlag,
|
|
13
15
|
suggestCorrectedDateFormat,
|
|
16
|
+
parsePastDuration,
|
|
17
|
+
validateDateTimeRange,
|
|
18
|
+
validateDateTimeFormat,
|
|
19
|
+
localToUTCTime,
|
|
14
20
|
} = require('../../utils');
|
|
15
21
|
|
|
16
22
|
require('dotenv').config();
|
|
@@ -21,6 +27,8 @@ class GetBulkLogCommand extends Command {
|
|
|
21
27
|
startTime: startTimeFlag,
|
|
22
28
|
endTime: endTimeFlag,
|
|
23
29
|
filename: logFilenameFlag,
|
|
30
|
+
past: pastFlag,
|
|
31
|
+
from: fromFlag,
|
|
24
32
|
};
|
|
25
33
|
|
|
26
34
|
async run() {
|
|
@@ -35,64 +43,86 @@ class GetBulkLogCommand extends Command {
|
|
|
35
43
|
|
|
36
44
|
const filename = await flags.filename;
|
|
37
45
|
|
|
46
|
+
let calculatedStartTime, calculatedEndTime, formattedStartTime, formattedEndTime;
|
|
47
|
+
|
|
38
48
|
// Only supports files that end with .csv
|
|
39
49
|
if (!filename || path.extname(filename).toLowerCase() !== '.csv') {
|
|
40
50
|
this.error('Invalid file type. Provide a filename with a .csv extension.');
|
|
41
51
|
return;
|
|
42
52
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (!
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
|
|
54
|
+
if (flags.startTime && flags.endTime) {
|
|
55
|
+
// Regular expression to validate the input date format YYYY-MM-DDTHH:MM:SSZ
|
|
56
|
+
const dateTimeRegex = /^(?:(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T(0[0-9]|1[0-9]|2[0-3]):([0-5]\d):([0-5]\d)Z)$/;
|
|
57
|
+
|
|
58
|
+
// Validate user provided startTime format
|
|
59
|
+
if (!dateTimeRegex.test(flags.startTime)) {
|
|
60
|
+
const correctedStartTime = suggestCorrectedDateFormat(flags.startTime);
|
|
61
|
+
if (!correctedStartTime) {
|
|
62
|
+
this.error('Invalid date components in startTime. Correct the date.');
|
|
63
|
+
} else {
|
|
64
|
+
this.error(
|
|
65
|
+
`Use the format YYYY-MM-DDTHH:MM:SSZ for startTime. Did you mean ${correctedStartTime}?`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
55
69
|
}
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
// Validate user provided endTime format
|
|
72
|
+
if (!dateTimeRegex.test(flags.endTime)) {
|
|
73
|
+
const correctedEndTime = suggestCorrectedDateFormat(flags.endTime);
|
|
74
|
+
// Check for incorrect date components
|
|
75
|
+
if (!correctedEndTime) {
|
|
76
|
+
this.error('Found invalid date components for endTime. Check and correct the date.');
|
|
77
|
+
} else {
|
|
78
|
+
this.error(
|
|
79
|
+
`Use the format YYYY-MM-DDTHH:MM:SSZ for endTime. Did you mean ${correctedEndTime}?`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
59
84
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
// Validate the date-time range
|
|
86
|
+
validateDateTimeRange(flags.startTime, flags.endTime);
|
|
87
|
+
|
|
88
|
+
// Properly format startTime and endTime strings before handing it over to SMS
|
|
89
|
+
formattedStartTime = flags.startTime.replace(/-|:|Z/g, '').replace('T', 'T');
|
|
90
|
+
formattedEndTime = flags.endTime.replace(/-|:|Z/g, '').replace('T', 'T');
|
|
91
|
+
} else if (flags.past) {
|
|
92
|
+
const pastTimeWindow = parsePastDuration(flags.past);
|
|
93
|
+
if (flags.from) {
|
|
94
|
+
let convertedTime;
|
|
95
|
+
const dateTimeRegex = /^\d{4}-\d{2}-\d{2}:\d{2}:\d{2}:\d{2}$/;
|
|
96
|
+
if (!dateTimeRegex.test(flags.from)) {
|
|
97
|
+
this.error('Invalid format. Use the format YYYY-MM-DD:HH:MM:SS for --from.');
|
|
98
|
+
} else {
|
|
99
|
+
try {
|
|
100
|
+
convertedTime = await localToUTCTime(flags.from.toString());
|
|
101
|
+
} catch (error) {
|
|
102
|
+
this.error(`Invalid date components passed in --from. Correct the date.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// add the past window to the converted time to get the end time to fetch logs from the past
|
|
106
|
+
calculatedStartTime = new Date(convertedTime);
|
|
107
|
+
calculatedEndTime = new Date(calculatedStartTime.getTime() + pastTimeWindow);
|
|
66
108
|
} else {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
);
|
|
109
|
+
// subtract the past window from the current time to get the start time to fetch recent logs from now
|
|
110
|
+
calculatedEndTime = new Date();
|
|
111
|
+
calculatedStartTime = new Date(calculatedEndTime.getTime() - pastTimeWindow);
|
|
70
112
|
}
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Properly format startTime and endTime strings before handing it over to SMS
|
|
75
|
-
const formattedStartTime = flags.startTime.replace(/-|:|Z/g, '').replace('T', 'T');
|
|
76
|
-
const formattedEndTime = flags.endTime.replace(/-|:|Z/g, '').replace('T', 'T');
|
|
77
|
-
|
|
78
|
-
// Convert formatted times to Date objects for comparison
|
|
79
|
-
const startTime = new Date(flags.startTime);
|
|
80
|
-
const endTime = new Date(flags.endTime);
|
|
81
|
-
const now = new Date(); // Current time
|
|
82
113
|
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
// Validate the calculated start and end times range
|
|
115
|
+
validateDateTimeRange(calculatedStartTime, calculatedEndTime);
|
|
116
|
+
// Properly format startTime and endTime strings before handing it over to SMS i.e remove the milliseconds
|
|
117
|
+
formattedStartTime = validateDateTimeFormat(calculatedStartTime);
|
|
118
|
+
formattedEndTime = validateDateTimeFormat(calculatedEndTime);
|
|
119
|
+
} else if ((flags.startTime && !flags.endTime) || (!flags.startTime && flags.endTime)) {
|
|
85
120
|
this.error('Provide both startTime and endTime.');
|
|
86
121
|
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const thirtyDaysAgo = new Date(today);
|
|
92
|
-
thirtyDaysAgo.setUTCDate(today.getUTCDate() - 30);
|
|
93
|
-
// Validate that logs from beyond 30 days from today are not available
|
|
94
|
-
if (startTime < thirtyDaysAgo || endTime < thirtyDaysAgo) {
|
|
95
|
-
this.error('Cannot get logs more than 30 days old. Adjust your time range.');
|
|
122
|
+
} else {
|
|
123
|
+
this.error(
|
|
124
|
+
'Missing required flags. Provide at least one flag --startTime, --endTime, or --past --from or type `mesh log:get-bulk --help` for more information.',
|
|
125
|
+
);
|
|
96
126
|
return;
|
|
97
127
|
}
|
|
98
128
|
|
|
@@ -115,37 +145,7 @@ class GetBulkLogCommand extends Command {
|
|
|
115
145
|
if (stats.size > 0) {
|
|
116
146
|
throw new Error(`Make sure the file: ${filename} is empty`);
|
|
117
147
|
}
|
|
118
|
-
// truncate milliseconds to ensure comparison is only done up to seconds
|
|
119
|
-
startTime.setMilliseconds(0);
|
|
120
|
-
endTime.setMilliseconds(0);
|
|
121
148
|
|
|
122
|
-
// Validate startTime < endTime
|
|
123
|
-
if (startTime > endTime) {
|
|
124
|
-
this.error('endTime must be greater than startTime');
|
|
125
|
-
}
|
|
126
|
-
// Validate that endTime is not greater than the current time (now)
|
|
127
|
-
if (endTime > now) {
|
|
128
|
-
this.error('endTime cannot be in the future. Provide a valid endTime.');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// 4. Check if the duration between start and end times is greater than 30 minutes (1800 seconds)
|
|
133
|
-
const timeDifferenceInSeconds = (endTime.getTime() - startTime.getTime()) / 1000;
|
|
134
|
-
|
|
135
|
-
if (timeDifferenceInSeconds > 1800) {
|
|
136
|
-
const hours = Math.floor(timeDifferenceInSeconds / 3600); //hours calculation
|
|
137
|
-
const minutes = Math.floor((timeDifferenceInSeconds % 3600) / 60); //minutes calculation
|
|
138
|
-
const seconds = timeDifferenceInSeconds % 60; //seconds calculation
|
|
139
|
-
|
|
140
|
-
this.error(
|
|
141
|
-
`Max duration between startTime and endTime should be 30 minutes. Current duration is ${hours} hour${
|
|
142
|
-
hours !== 1 ? 's' : ''
|
|
143
|
-
} ${minutes} minute${minutes !== 1 ? 's' : ''} and ${seconds} second${
|
|
144
|
-
seconds !== 1 ? 's' : ''
|
|
145
|
-
}.`,
|
|
146
|
-
);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
149
|
logger.info('Calling initSdk...');
|
|
150
150
|
const { imsOrgCode, projectId, workspaceId, workspaceName } = await initSdk({
|
|
151
151
|
ignoreCache,
|
package/src/utils.js
CHANGED
|
@@ -6,6 +6,7 @@ const { readFile } = require('fs/promises');
|
|
|
6
6
|
const { interpolateMesh } = require('./helpers');
|
|
7
7
|
const dotenv = require('dotenv');
|
|
8
8
|
const YAML = require('yaml');
|
|
9
|
+
const ms = require('ms');
|
|
9
10
|
const parseEnv = require('envsub/js/envsub-parser');
|
|
10
11
|
const os = require('os');
|
|
11
12
|
const chalk = require('chalk');
|
|
@@ -79,12 +80,18 @@ const fileNameFlag = Flags.string({
|
|
|
79
80
|
|
|
80
81
|
const startTimeFlag = Flags.string({
|
|
81
82
|
description: 'Start time for the logs in UTC',
|
|
82
|
-
required: true,
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
const endTimeFlag = Flags.string({
|
|
86
86
|
description: 'End time for the logs in UTC',
|
|
87
|
-
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const pastFlag = Flags.string({
|
|
90
|
+
description: 'Past time window in mins',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const fromFlag = Flags.string({
|
|
94
|
+
description: `The from time in YYYY-MM-DD:HH:MM:SS format based on your system's time zone. It is used to fetch logs from the past and is the starting time for the past time duration.`,
|
|
88
95
|
});
|
|
89
96
|
|
|
90
97
|
const logFilenameFlag = Flags.string({
|
|
@@ -605,6 +612,143 @@ function suggestCorrectedDateFormat(inputDate) {
|
|
|
605
612
|
return correctedDate;
|
|
606
613
|
}
|
|
607
614
|
|
|
615
|
+
/**
|
|
616
|
+
* Parses a duration string representing a past time window and converts it to milliseconds.
|
|
617
|
+
*
|
|
618
|
+
* @param {string} pastTimeWindow - The past time duration to parse, e.g., "20 mins", "15 minutes".
|
|
619
|
+
* @returns {number} The duration in milliseconds.
|
|
620
|
+
*/
|
|
621
|
+
function parsePastDuration(pastTimeWindow) {
|
|
622
|
+
// Regular expression to match various formats of minute abbreviations
|
|
623
|
+
const pastDurationRegex = /^(\d+)\s*(m|mins?|minutes?)$/i;
|
|
624
|
+
const match = pastTimeWindow.match(pastDurationRegex);
|
|
625
|
+
|
|
626
|
+
if (!match) {
|
|
627
|
+
throw new Error(
|
|
628
|
+
'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".',
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Convert the matched duration to milliseconds
|
|
633
|
+
const durationInMs = ms(pastTimeWindow);
|
|
634
|
+
|
|
635
|
+
return durationInMs;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Validates the provided startTime and endTime flags.
|
|
640
|
+
*
|
|
641
|
+
* @param {string} startTime - The start time in the format YYYY-MM-DDTHH:MM:SSZ
|
|
642
|
+
* @param {string} endTime - The end time in the format YYYY-MM-DDTHH:MM:SSZ
|
|
643
|
+
*/
|
|
644
|
+
function validateDateTimeRange(startTime, endTime) {
|
|
645
|
+
// Convert formatted times to Date objects for comparison
|
|
646
|
+
const start = new Date(startTime);
|
|
647
|
+
const end = new Date(endTime);
|
|
648
|
+
const now = new Date(); // Current time
|
|
649
|
+
|
|
650
|
+
// Get the current date and calculate the date 30 days ago, both in UTC
|
|
651
|
+
const today = new Date();
|
|
652
|
+
const thirtyDaysAgo = new Date(today);
|
|
653
|
+
thirtyDaysAgo.setUTCDate(today.getUTCDate() - 30);
|
|
654
|
+
|
|
655
|
+
// Validate that logs from beyond 30 days from today are not available
|
|
656
|
+
if (start < thirtyDaysAgo || end < thirtyDaysAgo) {
|
|
657
|
+
throw new Error('Cannot get logs more than 30 days old. Adjust your time range.');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Truncate milliseconds to ensure comparison is only done up to seconds
|
|
661
|
+
start.setMilliseconds(0);
|
|
662
|
+
end.setMilliseconds(0);
|
|
663
|
+
|
|
664
|
+
// Validate startTime < endTime
|
|
665
|
+
if (start > end) {
|
|
666
|
+
throw new Error('endTime must be greater than startTime');
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Validate that endTime is not greater than the current time (now)
|
|
670
|
+
if (end > now) {
|
|
671
|
+
throw new Error('endTime cannot be in the future. Provide a valid endTime.');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (start.getTime() === end.getTime()) {
|
|
675
|
+
throw new Error('The minimum duration is 1 minutes. The current duration is 0 minutes.');
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Check if the duration between start and end times is greater than 30 minutes (1800 seconds)
|
|
679
|
+
const timeDifferenceInSeconds = (end.getTime() - start.getTime()) / 1000;
|
|
680
|
+
|
|
681
|
+
if (timeDifferenceInSeconds > 1800) {
|
|
682
|
+
const hours = Math.floor(timeDifferenceInSeconds / 3600); // Hours calculation
|
|
683
|
+
const minutes = Math.floor((timeDifferenceInSeconds % 3600) / 60); // Minutes calculation
|
|
684
|
+
const seconds = timeDifferenceInSeconds % 60; // Seconds calculation
|
|
685
|
+
|
|
686
|
+
throw new Error(
|
|
687
|
+
`The maximum duration between startTime and endTime is 30 minutes. The current duration is ${hours} hour${
|
|
688
|
+
hours !== 1 ? 's' : ''
|
|
689
|
+
} ${minutes} minute${minutes !== 1 ? 's' : ''} and ${seconds} second${
|
|
690
|
+
seconds !== 1 ? 's' : ''
|
|
691
|
+
}.`,
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Format and validate a given date string
|
|
698
|
+
* @param {string} time - The time string in the format YYYY-MM-DDTHH:MM:SSZ
|
|
699
|
+
* @returns {string|null} The formatted and validated date string or null if invalid
|
|
700
|
+
*/
|
|
701
|
+
function validateDateTimeFormat(time) {
|
|
702
|
+
// Regular expression to validate the input date format YYYY-MM-DDTHH:MM:SSZ
|
|
703
|
+
const dateTimeRegex = /^(?:(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T(0[0-9]|1[0-9]|2[0-3]):([0-5]\d):([0-5]\d)Z)$/;
|
|
704
|
+
|
|
705
|
+
// Convert the Date object to ISO string and remove milliseconds
|
|
706
|
+
let timeString = time.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
707
|
+
|
|
708
|
+
// Validate the formatted time string against the regex
|
|
709
|
+
if (!dateTimeRegex.test(timeString)) {
|
|
710
|
+
const correctedDate = suggestCorrectedDateFormat(timeString);
|
|
711
|
+
if (!correctedDate) {
|
|
712
|
+
throw Error(
|
|
713
|
+
`Invalid date components in ${timeString}. Confirm the date information is correct.`,
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Return the formatted time string without dashes, colons, and 'Z'
|
|
719
|
+
return timeString.replace(/-|:|Z/g, '').replace('T', 'T');
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Convert a given local time string to UTC time string
|
|
724
|
+
* @param {string} timeString - The time string in the format YYYY-MM-DD:HH:MM:SS
|
|
725
|
+
* @returns {string|null} The UTC time in the format YYYY-MM-DDTHH:mm:ss[Z]
|
|
726
|
+
*/
|
|
727
|
+
async function localToUTCTime(timeString) {
|
|
728
|
+
// Split the input time string into components
|
|
729
|
+
let [date, hour, minute, second] = timeString.split(':');
|
|
730
|
+
// Create a properly formatted date-time string
|
|
731
|
+
const formattedTimeString = `${date}T${hour}:${minute}:${second}`;
|
|
732
|
+
try {
|
|
733
|
+
//Get the local timezone
|
|
734
|
+
// takes the timezone where the javascript runtime is running
|
|
735
|
+
// reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions#browser_compatibility:~:text=The%20value%20provided%20for%20this%20property%20in%20the%20options%20argument%2C%20with%20default%20filled%20in%20as%20needed.%20It%20is%20an%20IANA%20time%20zone%20name.%20The%20default%20is%20the%20runtime%27s%20default%20time%20zone.
|
|
736
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
737
|
+
|
|
738
|
+
// Create a Date object from the formatted time string
|
|
739
|
+
const localTime = new Date(formattedTimeString);
|
|
740
|
+
|
|
741
|
+
// Convert to UTC
|
|
742
|
+
const utcTime = new Date(localTime.toUTCString('en-US', { timeZone: timeZone }));
|
|
743
|
+
|
|
744
|
+
// Return the UTC time in ISO format without milliseconds
|
|
745
|
+
return utcTime.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
746
|
+
} catch (error) {
|
|
747
|
+
logger.error(`Error: ${error.message}`);
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
608
752
|
module.exports = {
|
|
609
753
|
ignoreCacheFlag,
|
|
610
754
|
autoConfirmActionFlag,
|
|
@@ -626,6 +770,12 @@ module.exports = {
|
|
|
626
770
|
startTimeFlag,
|
|
627
771
|
endTimeFlag,
|
|
628
772
|
logFilenameFlag,
|
|
773
|
+
pastFlag,
|
|
774
|
+
fromFlag,
|
|
629
775
|
suggestCorrectedDateFormat,
|
|
776
|
+
parsePastDuration,
|
|
777
|
+
validateDateTimeRange,
|
|
778
|
+
validateDateTimeFormat,
|
|
779
|
+
localToUTCTime,
|
|
630
780
|
cachePurgeAllActionFlag,
|
|
631
781
|
};
|