@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.
@@ -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.1-beta",
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.4",
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.1",
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 { suggestCorrectedDateFormat } = require('../../../utils');
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
- 'Max duration between startTime and endTime should be 30 minutes. Current duration is 0 hours 45 minutes and 0 seconds.',
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
- // Regular expression to validate the input date format YYYY-MM-DDTHH:MM:SSZ
44
- 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)$/;
45
-
46
- // Validate user provided startTime format
47
- if (!dateTimeRegex.test(flags.startTime)) {
48
- const correctedStartTime = suggestCorrectedDateFormat(flags.startTime);
49
- if (!correctedStartTime) {
50
- this.error('Found invalid date components for startTime. Check and correct the date.');
51
- } else {
52
- this.error(
53
- `Use the format YYYY-MM-DDTHH:MM:SSZ for startTime. Did you mean ${correctedStartTime}?`,
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
- return;
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
- // Validate user provided endTime format
61
- if (!dateTimeRegex.test(flags.endTime)) {
62
- const correctedEndTime = suggestCorrectedDateFormat(flags.endTime);
63
- //check for incorrect date components
64
- if (!correctedEndTime) {
65
- this.error('Found invalid date components for endTime. Check and correct the date.');
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
- this.error(
68
- `Use the format YYYY-MM-DDTHH:MM:SSZ for endTime. Did you mean ${correctedEndTime}?`,
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
- // Require both startTime and endTime
84
- if (!startTime || !endTime) {
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
- // Get the current date and calculate the date 30 days ago, both in UTC
90
- const today = new Date();
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
- required: true,
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
  };