diff --git a/.talismanrc b/.talismanrc index 9cd8393df5..a493619a0c 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,8 +1,8 @@ fileignoreconfig: - filename: package-lock.json - checksum: fa02e2883b15c973134368ce26c78187e247d3761ef14f603afc0d5ea83102a6 + checksum: bfb6604685378147fb5aa8533f328ce0981cd23703442a8eea0482ca2573a903 - filename: pnpm-lock.yaml - checksum: d4174feb2ba68c22cc45987afdaf4d89997df084e7e7eb9edc7e309b5a24aca5 + checksum: c2ba78077fa3dd8100d2697b0e87b29f9c8c64d7c258be654c3843dbd07de750 - filename: packages/contentstack-import-setup/test/unit/backup-handler.test.ts checksum: 0582d62b88834554cf12951c8690a73ef3ddbb78b82d2804d994cf4148e1ef93 - filename: packages/contentstack-import-setup/test/config.json @@ -64,9 +64,9 @@ fileignoreconfig: - filename: packages/contentstack-import/src/utils/setup-branch.ts checksum: a4a968a20d5ab7cbc08c266819907541bbf793cc098521a5e810ada3cbacbee6 - filename: packages/contentstack-bulk-publish/src/producer/publish-unpublished-env.js - checksum: 96fd15e027f38b156c69f10943ea1d5a70e580fa8a5efeb3286cd7132145c72d + checksum: 44dbc966df086f835fdca11cb305d0a5f448ca0be811c14b894e0024f9491385 - filename: packages/contentstack-import/src/import/modules/entries.ts - checksum: 2fd4e8ecf75e077632a6408d09997f0921d2a3508f9f2cb8f47fe79a28592300 + checksum: 0fa92065747da2ea3b02c666da5a0c6d3e74552c804a15578d9da0bfeb082615 - filename: packages/contentstack-utilities/src/logger/logger.ts checksum: 76429bc87e279624b386f00e7eb3f4ec25621ace7056289f812b9a076d6e184e - filename: packages/contentstack-bootstrap/src/bootstrap/utils.ts @@ -130,7 +130,7 @@ fileignoreconfig: - filename: packages/contentstack-import/test/unit/import/modules/environments.test.ts checksum: 58165d06d92f55be8abb04c4ecc47df775a1a47f1cee529f1be5277187700f97 - filename: packages/contentstack-import/test/unit/import/modules/locales.test.ts - checksum: 011ec3efd7a29ed274f073f8678229eaef46f33e272e7e1db1206fa1a20383f0 + checksum: 354827729c24456de3f38f70aed09ae65a15461e9ec7227aa20bb3878ff22add - filename: packages/contentstack-export/test/unit/export/modules/environments.test.ts checksum: 530573c4c92387b755ca1b4eef88ae8bb2ae076be9a726bba7b67a525cba23e9 - filename: packages/contentstack-export/test/unit/export/modules/extensions.test.ts @@ -215,4 +215,56 @@ fileignoreconfig: checksum: 86b11c2a2dd8c0b14aa558e4e52d6d721cd7707422c26a68e96cc5b55b9fefd8 - filename: packages/contentstack-import-setup/src/utils/login-handler.ts checksum: 3860c96e31677356963e67049762f944aef7c7b22fabb75a70ff5c64cf1ac274 + - filename: packages/contentstack-export/src/export/modules/environments.ts + checksum: 2777e15f32d61fcdc0fd395cedf4413cc5b7494a99cfb6c1b68fffa2269908ab + - filename: packages/contentstack-import/src/import/modules/environments.ts + checksum: 0e49cf0fb017e39c5d0eead3e388c323559f9057dd961dea61740915395deab3 + - filename: packages/contentstack-migration/examples/06-update-environment.js + checksum: 4a7d2c2f1ee6bf76066932661ed9674c6aff7d959b26ea14d79949ab5dda43d9 + - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/publish.js + checksum: 0a0e6ddd4aa0de09b3a66bf53c6ea079bb51726a64f96606b117a6e990b90d92 + - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/publish-non-localized-fields.js + checksum: f854cb2ddfafe1c250ec66a326fe620661142ea24282ec3c7b0f105156cbcc1a + - filename: packages/contentstack-export/src/utils/export-config-handler.ts + checksum: 2b7fc04762752729d33f77ebb35a12dd12a65f4fee893a04f96ba19bca521040 + - filename: packages/contentstack-variants/src/import/events.ts + checksum: c38a91e2d89b872287c178efc067dff89a061aad38c402d6485b85bd46784c33 + - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/unpublish.js + checksum: 9efdf1cbc372858d771feef2bbef1a6828418497d4a3c31e99760b33ef9dd4a4 + - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/update-and-publish.js + checksum: 3e13e34f25eac722f2bcf841057bec7471fd7ff89098d0477ccf143e5487e423 + - filename: packages/contentstack-export/src/export/modules/custom-roles.ts + checksum: 19b46d3bf4edeeb10e2f6c81bc0caaac4e1f8c3b4e4f91db2592c2a005ed08a5 + - filename: packages/contentstack-bulk-publish/src/commands/cm/bulk-publish/cross-publish.js + checksum: 0cc612eb2b9be5308682f0e24f8f1c1a5b81d4516d84a4a06d62286e97596cea + - filename: packages/contentstack-export/src/export/modules/composable-studio.ts + checksum: 1ad61df3110dd99f14cef6382d7820dd32538a8d85ffac4bfd631761a1bdf7af + - filename: packages/contentstack-import/test/unit/utils/extension-helper.test.ts + checksum: 8cbcb6f192edb034a01a405437276f5dafb6cb235fa9c7f8e5b936f006f451ea + - filename: packages/contentstack-export/src/export/modules/labels.ts + checksum: c3060a5ae784e886505e1b736d89f054a92131bfef4b00268500326643edb43d + - filename: packages/contentstack-export/src/export/modules/locales.ts + checksum: 6e400596635741a7013ad1317f29307777dd70855d5c2f16fe0388f667254a17 + - filename: packages/contentstack-auth/src/utils/tokens-validation.ts + checksum: 3aab82beb51b1dc01e644b4c07a965a49b80cc3282c8e914fbd2671440e9b28d + - filename: packages/contentstack-config/src/commands/config/remove/base-branch.ts + checksum: 6a6dc3ff9088b4d219d6e93717b5907b0679720228474cb37f32f05d96d4634b + - filename: packages/contentstack-bulk-publish/src/commands/cm/assets/unpublish.js + checksum: 0f8f2ea8107725a68fc8de19273247c632c9d5902b03067a4915e4ae1c8d3d66 + - filename: packages/contentstack-bulk-publish/src/commands/cm/assets/publish.js + checksum: 63f2f414738fbdd109ff5bb56aff26a93a8f76798112ad6830358f743a7749af + - filename: packages/contentstack-auth/src/commands/auth/tokens/add.ts + checksum: 0b10b5a80431160b57052930bba8431ecc5635aff9a5309af1b59b4beea8a0bb + - filename: packages/contentstack-export/src/export/modules/extensions.ts + checksum: d02dbdf57f2009111d5b56e646fc33fd349f2f247e6eec96dfce34280b55a220 + - filename: packages/contentstack-bulk-publish/src/commands/cm/stacks/publish-configure.js + checksum: 900fe4c398e181e1f035d410b510d8ec4a7afa0a5cac7b009bce4c3edaa691f1 + - filename: packages/contentstack-bulk-publish/src/commands/cm/stacks/unpublish.js + checksum: e7cfb7b35f1425359e6e064a77afa6ba54685dda055880af9f02995b00911041 + - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/publish-modified.js + checksum: 512fad49bf40dc16907bfbeb836ff71a13eb9f67918ae280e1cf243b7e9aff0e + - filename: packages/contentstack-variants/src/import/audiences.ts + checksum: e0380352bb945cc694c7988574d3a3682e7ed71b5d3aa07e01007f6fe0137ce0 + - filename: packages/contentstack-import/src/import/modules/composable-studio.ts + checksum: 9b83875b8d82086f13e0b7ab44ff7fe95486fced95b0c22d5c73fa69fbe35d4a version: '1.0' diff --git a/packages/contentstack-audit/src/audit-base-command.ts b/packages/contentstack-audit/src/audit-base-command.ts index caeb54f03e..d16236c213 100644 --- a/packages/contentstack-audit/src/audit-base-command.ts +++ b/packages/contentstack-audit/src/audit-base-command.ts @@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid'; import isEmpty from 'lodash/isEmpty'; import { join, resolve } from 'path'; import cloneDeep from 'lodash/cloneDeep'; -import { cliux, sanitizePath, TableFlags, TableHeader, log, configHandler } from '@contentstack/cli-utilities'; +import { cliux, sanitizePath, TableFlags, TableHeader, log, configHandler, createLogContext } from '@contentstack/cli-utilities'; import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs'; import config from './config'; import { print } from './util/log'; @@ -50,18 +50,6 @@ export abstract class AuditBaseCommand extends BaseCommand { this.currentCommand = command; - // Initialize audit context - this.auditContext = this.createAuditContext(); + // Initialize audit context (reused, no need to call again in scanAndFix) + createLogContext(this.context?.info?.command, '', configHandler.get('authenticationMethod')); + this.auditContext = { module: 'audit' }; log.debug(`Starting audit command: ${command}`, this.auditContext); log.info(`Starting audit command: ${command}`, this.auditContext); @@ -224,7 +213,7 @@ export abstract class AuditBaseCommand extends BaseCommand = Interfaces.InferredArgs; @@ -16,7 +16,9 @@ export abstract class BaseCommand extends Command { */ public async init(): Promise { await super.init(); - this.contextDetails = { ...this.createExportContext() }; + // this.contextDetails = { ...this.createExportContext() }; + this.contextDetails = { ...createLogContext(this.context?.info?.command || 'auth', '',) }; + } /** diff --git a/packages/contentstack-clone/src/commands/cm/stacks/clone.js b/packages/contentstack-clone/src/commands/cm/stacks/clone.js index ec54dd99c5..cb7aedf5d5 100644 --- a/packages/contentstack-clone/src/commands/cm/stacks/clone.js +++ b/packages/contentstack-clone/src/commands/cm/stacks/clone.js @@ -73,7 +73,12 @@ class StackCloneCommand extends Command { sourceManagementTokenAlias, destinationManagementTokenAlias, ); - const cloneContext = this.createCloneContext(authenticationMethod); + createLogContext( + this.context?.info?.command || 'cm:stacks:clone', + sourceStackApiKey, + authenticationMethod + ); + cloneContext = { module: 'clone' }; log.debug('Starting clone operation setup', cloneContext); if (externalConfigPath) { diff --git a/packages/contentstack-export/src/commands/cm/stacks/export.ts b/packages/contentstack-export/src/commands/cm/stacks/export.ts index b80e6040cc..2dc8eb1725 100644 --- a/packages/contentstack-export/src/commands/cm/stacks/export.ts +++ b/packages/contentstack-export/src/commands/cm/stacks/export.ts @@ -13,10 +13,11 @@ import { log, handleAndLogError, getLogPath, + createLogContext, } from '@contentstack/cli-utilities'; import { ModuleExporter } from '../../../export'; -import { Context, ExportConfig } from '../../../types'; +import { ExportConfig } from '../../../types'; import { setupExportConfig, writeExportMetaFile } from '../../../utils'; export default class ExportCommand extends Command { @@ -120,9 +121,16 @@ export default class ExportCommand extends Command { try { const { flags } = await this.parse(ExportCommand); const exportConfig = await setupExportConfig(flags); - // Prepare the context object - const context = this.createExportContext(exportConfig.apiKey, exportConfig.authenticationMethod); - exportConfig.context = { ...context }; + + // Store apiKey in configHandler for session.json (return value not needed) + createLogContext( + this.context?.info?.command || 'cm:stacks:export', + exportConfig.apiKey, + exportConfig.authenticationMethod + ); + + // For log entries, only pass module (other fields are in session.json) + exportConfig.context = { module: '' }; //log.info(`Using Cli Version: ${this.context?.cliVersion}`, exportConfig.context); // Assign exportConfig variables @@ -137,7 +145,6 @@ export default class ExportCommand extends Command { } log.success( `The content of the stack ${exportConfig.apiKey} has been exported successfully!`, - exportConfig.context, ); log.info(`The exported content has been stored at '${exportDir}'.`, exportConfig.context); log.success(`The log has been stored at '${getLogPath()}'.`, exportConfig.context); @@ -147,19 +154,6 @@ export default class ExportCommand extends Command { } } - // Create export context object - private createExportContext(apiKey: string, authenticationMethod?: string): Context { - return { - command: this.context?.info?.command || 'cm:stacks:export', - module: '', - userId: configHandler.get('userUid') || '', - email: configHandler.get('email') || '', - sessionId: this.context?.sessionId || '', - apiKey: apiKey || '', - orgId: configHandler.get('oauthOrgUid') || '', - authenticationMethod: authenticationMethod || 'Basic Auth', - }; - } // Assign values to exportConfig private assignExportConfig(exportConfig: ExportConfig): void { diff --git a/packages/contentstack-export/src/types/index.ts b/packages/contentstack-export/src/types/index.ts index 2327d423ae..ec21185109 100644 --- a/packages/contentstack-export/src/types/index.ts +++ b/packages/contentstack-export/src/types/index.ts @@ -159,15 +159,7 @@ export interface ComposableStudioProject { uid: string; } export interface Context { - command: string; module: string; - userId: string | undefined; - email: string | undefined; - sessionId: string | undefined; - clientId?: string | undefined; - apiKey: string; - orgId: string; - authenticationMethod?: string; } export { default as DefaultConfig } from './default-config'; diff --git a/packages/contentstack-export/test/unit/export/modules/assets.test.ts b/packages/contentstack-export/test/unit/export/modules/assets.test.ts index d865cd4c13..56ef04ef72 100644 --- a/packages/contentstack-export/test/unit/export/modules/assets.test.ts +++ b/packages/contentstack-export/test/unit/export/modules/assets.test.ts @@ -213,6 +213,12 @@ describe('ExportAssets', () => { dirName: 'attributes', fileName: 'attributes.json', invalidKeys: [] + }, + 'composable-studio': { + dirName: 'composable_studio', + fileName: 'composable_studio.json', + apiBaseUrl: 'https://api.contentstack.io', + apiVersion: 'v3' } } } as ExportConfig; diff --git a/packages/contentstack-export/test/unit/export/modules/base-class.test.ts b/packages/contentstack-export/test/unit/export/modules/base-class.test.ts index 426ffe8292..0ffe4187f1 100644 --- a/packages/contentstack-export/test/unit/export/modules/base-class.test.ts +++ b/packages/contentstack-export/test/unit/export/modules/base-class.test.ts @@ -231,6 +231,12 @@ describe('BaseClass', () => { dirName: 'attributes', fileName: 'attributes.json', invalidKeys: [] + }, + 'composable-studio': { + dirName: 'composable_studio', + fileName: 'composable_studio.json', + apiBaseUrl: 'https://api.contentstack.io', + apiVersion: 'v3' } } } as ExportConfig; diff --git a/packages/contentstack-import-setup/src/commands/cm/stacks/import-setup.ts b/packages/contentstack-import-setup/src/commands/cm/stacks/import-setup.ts index 52d0b0c95f..9f02c58192 100644 --- a/packages/contentstack-import-setup/src/commands/cm/stacks/import-setup.ts +++ b/packages/contentstack-import-setup/src/commands/cm/stacks/import-setup.ts @@ -12,6 +12,7 @@ import { log, handleAndLogError, configHandler, + createLogContext, } from '@contentstack/cli-utilities'; import { ImportConfig, Context } from '../../../types'; @@ -71,8 +72,13 @@ export default class ImportSetupCommand extends Command { const { flags } = await this.parse(ImportSetupCommand); let importSetupConfig = await setupImportConfig(flags); // Prepare the context object - const context = this.createImportSetupContext(importSetupConfig.apiKey, (importSetupConfig as any).authenticationMethod); - importSetupConfig.context = { ...context }; + createLogContext( + this.context?.info?.command || 'cm:stacks:import-setup', + importSetupConfig.apiKey, + configHandler.get('authenticationMethod') + ); + + importSetupConfig.context = { module: '' }; // Note setting host to create cma client importSetupConfig.host = this.cmaHost; @@ -94,17 +100,4 @@ export default class ImportSetupCommand extends Command { } } - // Create import setup context object - private createImportSetupContext(apiKey: string, authenticationMethod?: string, module?: string): Context { - return { - command: this.context?.info?.command || 'cm:stacks:import-setup', - module: module || '', - userId: configHandler.get('userUid') || undefined, - email: configHandler.get('email') || undefined, - sessionId: this.context?.sessionId, - apiKey: apiKey || '', - orgId: configHandler.get('oauthOrgUid') || '', - authenticationMethod: authenticationMethod || 'Basic Auth', - }; - } } diff --git a/packages/contentstack-import-setup/src/types/index.ts b/packages/contentstack-import-setup/src/types/index.ts index df9c0b0bda..0c9fc523b7 100644 --- a/packages/contentstack-import-setup/src/types/index.ts +++ b/packages/contentstack-import-setup/src/types/index.ts @@ -154,12 +154,5 @@ export type TaxonomyQueryParams = { }; export interface Context { - command: string; module: string; - userId: string | undefined; - email: string | undefined; - sessionId: string | undefined; - apiKey: string; - orgId: string; - authenticationMethod?: string; } diff --git a/packages/contentstack-import/src/commands/cm/stacks/import.ts b/packages/contentstack-import/src/commands/cm/stacks/import.ts index 65d7d4f741..74217cf456 100644 --- a/packages/contentstack-import/src/commands/cm/stacks/import.ts +++ b/packages/contentstack-import/src/commands/cm/stacks/import.ts @@ -11,6 +11,7 @@ import { handleAndLogError, configHandler, getLogPath, + createLogContext, } from '@contentstack/cli-utilities'; import { Context, ImportConfig } from '../../../types'; @@ -155,9 +156,14 @@ export default class ImportCommand extends Command { const { flags } = await this.parse(ImportCommand); importConfig = await setupImportConfig(flags); // Prepare the context object - const context = this.createImportContext(importConfig.apiKey, importConfig.authenticationMethod); - importConfig.context = { ...context }; - // log.info(`Using CLI version: ${this.context?.cliVersion}`, importConfig.context); + createLogContext( + this.context?.info?.command || 'cm:stacks:export', + importConfig.apiKey, + importConfig.authenticationMethod + ); + + importConfig.context = { module: '' }; + //log.info(`Using Cli Version: ${this.context?.cliVersion}`, importConfig.context); // Note setting host to create cma client importConfig.host = this.cmaHost; @@ -191,18 +197,4 @@ export default class ImportCommand extends Command { } } } - - // Create export context object - private createImportContext(apiKey: string, authenticationMethod?: string): Context { - return { - command: this.context?.info?.command || 'cm:stacks:import', - module: '', - userId: configHandler.get('userUid') || '', - email: configHandler.get('email') || '', - sessionId: this.context?.sessionId, - apiKey: apiKey || '', - orgId: configHandler.get('oauthOrgUid') || '', - authenticationMethod: authenticationMethod || 'Basic Auth', - }; - } } diff --git a/packages/contentstack-import/src/types/index.ts b/packages/contentstack-import/src/types/index.ts index eb76582363..ee9062465c 100644 --- a/packages/contentstack-import/src/types/index.ts +++ b/packages/contentstack-import/src/types/index.ts @@ -136,15 +136,7 @@ export interface ComposableStudioProject { } export interface Context { - command: string; module: string; - userId: string | undefined; - email: string | undefined; - sessionId: string | undefined; - clientId?: string | undefined; - apiKey: string; - orgId: string; - authenticationMethod?: string; } export { default as DefaultConfig } from './default-config'; @@ -158,15 +150,3 @@ export type ExtensionType = { scope: Record; title: string; }; - -export interface Context { - command: string; - module: string; - userId: string | undefined; - email: string | undefined; - sessionId: string | undefined; - clientId?: string | undefined; - apiKey: string; - orgId: string; - authenticationMethod?: string; -} diff --git a/packages/contentstack-import/test/unit/commands/cm/stacks/import.test.ts b/packages/contentstack-import/test/unit/commands/cm/stacks/import.test.ts index fc0829a963..4782a70249 100644 --- a/packages/contentstack-import/test/unit/commands/cm/stacks/import.test.ts +++ b/packages/contentstack-import/test/unit/commands/cm/stacks/import.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { fancy } from 'fancy-test'; import sinon from 'sinon'; -import { managementSDKClient, configHandler, log, handleAndLogError, getLogPath } from '@contentstack/cli-utilities'; +import { managementSDKClient, configHandler, log, handleAndLogError, getLogPath, createLogContext } from '@contentstack/cli-utilities'; import ImportCommand from '../../../../../src/commands/cm/stacks/import'; import { ModuleImporter } from '../../../../../src/import'; import { ImportConfig } from '../../../../../src/types'; @@ -201,18 +201,27 @@ describe('ImportCommand', () => { }); }); - describe('createImportContext', () => { + describe('createLogContext', () => { let configHandlerStub: sinon.SinonStub; + let configHandlerSetStub: sinon.SinonStub; beforeEach(() => { configHandlerStub = sinon.stub(configHandler, 'get'); - configHandlerStub.withArgs('userUid').returns('user-123'); + configHandlerSetStub = sinon.stub(configHandler, 'set'); + configHandlerStub.withArgs('clientId').returns('user-123'); configHandlerStub.withArgs('email').returns('test@example.com'); + configHandlerStub.withArgs('sessionId').returns('test-session-123'); configHandlerStub.withArgs('oauthOrgUid').returns('org-123'); + configHandlerStub.withArgs('authorisationType').returns('BASIC'); + }); + + afterEach(() => { + configHandlerStub.restore(); + configHandlerSetStub.restore(); }); it('should create context with all required properties', () => { - const context = command['createImportContext']('test', 'Basic Auth'); + const context = createLogContext('cm:stacks:import', 'test', 'Basic Auth'); expect(context).to.have.property('command', 'cm:stacks:import'); expect(context).to.have.property('module', ''); @@ -222,10 +231,11 @@ describe('ImportCommand', () => { expect(context).to.have.property('apiKey', 'test'); expect(context).to.have.property('orgId', 'org-123'); expect(context).to.have.property('authenticationMethod', 'Basic Auth'); + expect(configHandlerSetStub.calledWith('apiKey', 'test')).to.be.true; }); it('should use default authentication method when not provided', () => { - const context = command['createImportContext']('test'); + const context = createLogContext('cm:stacks:import', 'test'); expect(context.authenticationMethod).to.equal('Basic Auth'); }); @@ -234,7 +244,7 @@ describe('ImportCommand', () => { configHandlerStub.reset(); configHandlerStub.returns(undefined); - const context = command['createImportContext']('test', 'Management Token'); + const context = createLogContext('cm:stacks:import', 'test', 'Management Token'); expect(context.userId).to.equal(''); expect(context.email).to.equal(''); @@ -242,14 +252,14 @@ describe('ImportCommand', () => { expect(context.authenticationMethod).to.equal('Management Token'); }); - it('should use context command when available', () => { - const context = command['createImportContext']('test'); + it('should use provided command', () => { + const context = createLogContext('cm:stacks:import', 'test'); expect(context.command).to.equal('cm:stacks:import'); }); it('should handle empty apiKey', () => { - const context = command['createImportContext'](''); + const context = createLogContext('cm:stacks:import', ''); expect(context.apiKey).to.equal(''); }); @@ -503,35 +513,43 @@ describe('ImportCommand', () => { configHandlerStub = sinon.stub(configHandler, 'get'); }); - it('should handle undefined context', () => { - (command as any).context = undefined; + afterEach(() => { + configHandlerStub.restore(); + }); + + it('should handle command string directly', () => { + configHandlerStub.returns(undefined); - const context = command['createImportContext']('test'); + const context = createLogContext('cm:stacks:import', 'test'); expect(context.command).to.equal('cm:stacks:import'); }); - it('should handle context without info', () => { - (command as any).context = { sessionId: 'test-session' }; + it('should use command string when provided', () => { + configHandlerStub.withArgs('clientId').returns('user-123'); + configHandlerStub.withArgs('email').returns('test@example.com'); + configHandlerStub.withArgs('sessionId').returns('test-session'); + configHandlerStub.withArgs('oauthOrgUid').returns('org-123'); + configHandlerStub.withArgs('authorisationType').returns('BASIC'); - const context = command['createImportContext']('test'); + const context = createLogContext('cm:stacks:import', 'test'); expect(context.command).to.equal('cm:stacks:import'); + expect(context.sessionId).to.equal('test-session'); }); - it('should handle context without sessionId', () => { - (command as any).context = { info: { command: 'test' } }; + it('should handle missing sessionId from configHandler', () => { + configHandlerStub.returns(undefined); - const context = command['createImportContext']('test'); + const context = createLogContext('cm:stacks:import', 'test'); - expect(context.sessionId).to.be.undefined; + expect(context.sessionId).to.equal(''); }); - it('should handle configHandler throwing errors', () => { - configHandlerStub.reset(); + it('should handle configHandler returning undefined values', () => { configHandlerStub.returns(undefined); - const context = command['createImportContext']('test'); + const context = createLogContext('cm:stacks:import', 'test'); expect(context.userId).to.equal(''); expect(context.email).to.equal(''); diff --git a/packages/contentstack-utilities/src/helpers.ts b/packages/contentstack-utilities/src/helpers.ts index 7156f99935..316a6a4138 100644 --- a/packages/contentstack-utilities/src/helpers.ts +++ b/packages/contentstack-utilities/src/helpers.ts @@ -245,3 +245,62 @@ const sensitiveKeys = [ /management[-._]?token/i, /delivery[-._]?token/i, ]; + +/** + * Get authentication method from config + * @returns Authentication method string ('OAuth', 'Basic Auth', or empty string) + */ +export function getAuthenticationMethod(): string { + const authType = configHandler.get('authorisationType'); + if (authType === 'OAUTH') { + return 'OAuth'; + } else if (authType === 'BASIC') { + return 'Basic Auth'; + } + // Management token detection is command-specific and not stored globally + // Return empty string if unknown + return ''; +} + +/** + * Creates a standardized context object for logging + * This context contains all session-level metadata that should be in session.json + * The apiKey is stored in configHandler so it's available for session.json generation + * + * @param commandId - The command ID (e.g., 'cm:stacks:export') + * @param apiKey - The API key for the stack (will be stored in configHandler for session.json) + * @param authenticationMethod - Optional authentication method + * @returns Context object with all session-level metadata + */ +export function createLogContext( + commandId: string, + apiKey: string, + authenticationMethod?: string +): { + command: string; + module: string; + userId: string; + email: string; + sessionId: string; + apiKey: string; + orgId: string; + authenticationMethod: string; +} { + // Store apiKey in configHandler so it's available for session.json + if (apiKey) { + configHandler.set('apiKey', apiKey); + } + + const authMethod = authenticationMethod || getAuthenticationMethod(); + + return { + command: commandId, + module: '', + userId: configHandler.get('clientId') || '', + email: configHandler.get('email') || '', + sessionId: configHandler.get('sessionId') || '', + apiKey: apiKey || '', + orgId: configHandler.get('oauthOrgUid') || '', + authenticationMethod: authMethod, + }; +} diff --git a/packages/contentstack-utilities/src/index.ts b/packages/contentstack-utilities/src/index.ts index be28362868..daca328264 100644 --- a/packages/contentstack-utilities/src/index.ts +++ b/packages/contentstack-utilities/src/index.ts @@ -27,6 +27,7 @@ export * from './fs-utility'; export { default as NodeCrypto } from './encrypter'; export { Args as args, Flags as flags, Command } from './cli-ux'; export * from './helpers'; +export { createLogContext } from './helpers'; export * from './interfaces'; export * from './date-time'; export * from './add-locale'; diff --git a/packages/contentstack-utilities/src/logger/session-path.ts b/packages/contentstack-utilities/src/logger/session-path.ts index 2d5de07efd..89aaa7f68b 100644 --- a/packages/contentstack-utilities/src/logger/session-path.ts +++ b/packages/contentstack-utilities/src/logger/session-path.ts @@ -1,8 +1,65 @@ import * as fs from 'fs'; +import * as os from 'os'; import * as path from 'path'; -import { configHandler, formatDate, formatTime } from '..'; +import { configHandler, formatDate, formatTime, createLogContext } from '..'; import { getLogPath } from './log'; +/** + * Extract module name from command ID + * Example: "cm:stacks:audit" -> "audit" + */ +function extractModule(commandId: string): string { + if (!commandId || commandId === 'unknown') { + return ''; + } + // Split by colon and get the last part + const parts = commandId.split(':'); + return parts[parts.length - 1] || ''; +} + +/** + * Generate session metadata object for session.json + * Uses createLogContext() to get base context, then adds session-specific metadata + */ +function generateSessionMetadata( + commandId: string, + sessionId: string, + startTimestamp: Date, +): Record { + const originalCommandId = configHandler.get('currentCommandId') || commandId; + const module = extractModule(originalCommandId); + const apiKey = configHandler.get('apiKey') || ''; + + const baseContext = createLogContext(originalCommandId, apiKey); + + return { + ...baseContext, + module: module, + sessionId: sessionId, + startTimestamp: startTimestamp.toISOString(), + MachineEnvironment: { + nodeVersion: process.version, + os: os.platform(), + hostname: os.hostname(), + CLI_VERSION: configHandler.get('CLI_VERSION') || '', + }, + }; +} + +/** + * Create session.json metadata file in the session directory + */ +function createSessionMetadataFile(sessionPath: string, metadata: Record): void { + const metadataPath = path.join(sessionPath, 'session.json'); + try { + fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), 'utf8'); + } catch (error) { + // Silently fail if metadata file cannot be created + // Logging here would cause circular dependency + // The session folder and logs will still be created + } +} + /** * Get the session-based log path for date-organized logging * Structure: {basePath}/{YYYY-MM-DD}/{command}-{YYYYMMDD-HHMMSS}-{sessionId}/ @@ -42,10 +99,22 @@ export function getSessionLogPath(): string { const sessionPath = path.join(basePath, dateStr, sessionFolderName); // Ensure directory exists - if (!fs.existsSync(sessionPath)) { + const isNewSession = !fs.existsSync(sessionPath); + if (isNewSession) { fs.mkdirSync(sessionPath, { recursive: true }); } + // Create session.json metadata file for new sessions + // This ensures metadata is created before any logs are written + if (isNewSession) { + const metadata = generateSessionMetadata( + configHandler.get('currentCommandId') || commandId, + sessionId, + now, + ); + createSessionMetadataFile(sessionPath, metadata); + } + return sessionPath; } diff --git a/packages/contentstack-utilities/test/unit/logger.test.ts b/packages/contentstack-utilities/test/unit/logger.test.ts index 502d9d5d94..997c4fcf46 100644 --- a/packages/contentstack-utilities/test/unit/logger.test.ts +++ b/packages/contentstack-utilities/test/unit/logger.test.ts @@ -450,5 +450,251 @@ describe('Session Log Path', () => { const dateFolder = path.dirname(logDir); expect(fs.existsSync(dateFolder)).to.be.true; expect(dateFolder).to.match(/\d{4}-\d{2}-\d{2}/); + + // Verify the log file exists (winston writes asynchronously, so wait a bit) + // Wait for winston to flush the file with timeout + return new Promise((resolve, reject) => { + const maxAttempts = 20; // 20 * 50ms = 1 second max wait + let attempts = 0; + const checkFile = () => { + attempts++; + if (fs.existsSync(actualLogFile)) { + resolve(); + } else if (attempts >= maxAttempts) { + reject(new Error(`Log file ${actualLogFile} was not created within timeout`)); + } else { + setTimeout(checkFile, 50); + } + }; + checkFile(); + }).then(() => { + expect(fs.existsSync(actualLogFile)).to.be.true; + }); + }); + + describe('Session Metadata (session.json)', () => { + let tempDir: string; + + beforeEach(() => { + tempDir = path.join(os.tmpdir(), `cli-test-${Date.now()}-${Math.random().toString(36).substring(7)}`); + configHandler.delete('currentCommandId'); + configHandler.delete('sessionId'); + configHandler.delete('email'); + configHandler.delete('authorisationType'); + }); + + afterEach(() => { + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + configHandler.delete('currentCommandId'); + configHandler.delete('sessionId'); + configHandler.delete('email'); + configHandler.delete('authorisationType'); + }); + + fancy + .stub(configHandler, 'get', (...args: any[]) => { + const key = args[0]; + if (key === 'log.path') return tempDir; + if (key === 'currentCommandId') return 'cm:stacks:audit'; + if (key === 'sessionId') return 'a3f8c9'; + if (key === 'email') return 'user@example.com'; + if (key === 'authorisationType') return 'OAUTH'; + return undefined; + }) + .it('should create session.json with correct metadata structure', () => { + const sessionPath = getSessionLogPath(); + const metadataPath = path.join(sessionPath, 'session.json'); + + // Verify session.json exists + expect(fs.existsSync(metadataPath)).to.be.true; + + // Read and parse session.json + const metadataContent = fs.readFileSync(metadataPath, 'utf8'); + const metadata = JSON.parse(metadataContent); + + // Verify required fields + expect(metadata).to.have.property('command'); + expect(metadata).to.have.property('module'); + expect(metadata).to.have.property('sessionId'); + expect(metadata).to.have.property('startTimestamp'); + expect(metadata).to.have.property('authenticationMethod'); + expect(metadata).to.have.property('email'); + expect(metadata).to.have.property('MachineEnvironment'); + + // Verify values + expect(metadata.command).to.equal('cm:stacks:audit'); + expect(metadata.module).to.equal('audit'); + expect(metadata.sessionId).to.equal('a3f8c9'); + expect(metadata.authenticationMethod).to.equal('OAuth'); + expect(metadata.email).to.equal('user@example.com'); + + // Verify MachineEnvironment object + expect(metadata.MachineEnvironment).to.have.property('nodeVersion'); + expect(metadata.MachineEnvironment).to.have.property('os'); + expect(metadata.MachineEnvironment).to.have.property('hostname'); + expect(metadata.MachineEnvironment.nodeVersion).to.equal(process.version); + expect(metadata.MachineEnvironment.os).to.equal(process.platform); + expect(metadata.MachineEnvironment.hostname).to.equal(os.hostname()); + + // Verify timestamp is ISO format + expect(metadata.startTimestamp).to.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/); + }); + + fancy + .stub(configHandler, 'get', (...args: any[]) => { + const key = args[0]; + if (key === 'log.path') return tempDir; + if (key === 'currentCommandId') return 'cm:stacks:export'; + if (key === 'sessionId') return 'test-session-123'; + if (key === 'email') return undefined; + if (key === 'authorisationType') return 'BASIC'; + return undefined; + }) + .it('should create session.json with Basic Auth authentication method', () => { + const sessionPath = getSessionLogPath(); + const metadataPath = path.join(sessionPath, 'session.json'); + + const metadataContent = fs.readFileSync(metadataPath, 'utf8'); + const metadata = JSON.parse(metadataContent); + + expect(metadata.authenticationMethod).to.equal('Basic Auth'); + expect(metadata.email).to.equal(''); + expect(metadata.module).to.equal('export'); + }); + + fancy + .stub(configHandler, 'get', (...args: any[]) => { + const key = args[0]; + if (key === 'log.path') return tempDir; + if (key === 'currentCommandId') return 'cm:stacks:import'; + if (key === 'sessionId') return 'test-session-456'; + if (key === 'email') return 'test@example.com'; + if (key === 'authorisationType') return undefined; + return undefined; + }) + .it('should create session.json with empty authentication method when not set', () => { + const sessionPath = getSessionLogPath(); + const metadataPath = path.join(sessionPath, 'session.json'); + + const metadataContent = fs.readFileSync(metadataPath, 'utf8'); + const metadata = JSON.parse(metadataContent); + + expect(metadata.authenticationMethod).to.equal(''); + expect(metadata.email).to.equal('test@example.com'); + expect(metadata.module).to.equal('import'); + }); + + fancy + .stub(configHandler, 'get', (...args: any[]) => { + const key = args[0]; + if (key === 'log.path') return tempDir; + if (key === 'currentCommandId') return 'unknown'; + if (key === 'sessionId') return 'test-session-789'; + if (key === 'email') return undefined; + if (key === 'authorisationType') return undefined; + return undefined; + }) + .it('should create session.json with empty module when command is unknown', () => { + const sessionPath = getSessionLogPath(); + const metadataPath = path.join(sessionPath, 'session.json'); + + const metadataContent = fs.readFileSync(metadataPath, 'utf8'); + const metadata = JSON.parse(metadataContent); + + expect(metadata.command).to.equal('unknown'); + expect(metadata.module).to.equal(''); + }); + + fancy + .stub(configHandler, 'get', (...args: any[]) => { + const key = args[0]; + if (key === 'log.path') return tempDir; + if (key === 'currentCommandId') return 'cm:stacks:audit'; + if (key === 'sessionId') return 'a3f8c9'; + if (key === 'email') return 'user@example.com'; + if (key === 'authorisationType') return 'OAUTH'; + return undefined; + }) + .it('should create session.json only once per session folder', () => { + // First call creates the session folder and metadata + const sessionPath1 = getSessionLogPath(); + const metadataPath = path.join(sessionPath1, 'session.json'); + + // Read first metadata + const metadataContent1 = fs.readFileSync(metadataPath, 'utf8'); + const metadata1 = JSON.parse(metadataContent1); + const firstTimestamp = metadata1.startTimestamp; + + // Second call should return same path (session already exists) + const sessionPath2 = getSessionLogPath(); + expect(sessionPath1).to.equal(sessionPath2); + + // Verify metadata file still exists and wasn't overwritten + expect(fs.existsSync(metadataPath)).to.be.true; + const metadataContent2 = fs.readFileSync(metadataPath, 'utf8'); + const metadata2 = JSON.parse(metadataContent2); + + // Timestamp should be the same (created only once) + expect(metadata2.startTimestamp).to.equal(firstTimestamp); + }); + + fancy + .stub(configHandler, 'get', (...args: any[]) => { + const key = args[0]; + if (key === 'log.path') return tempDir; + if (key === 'currentCommandId') return 'cm:stacks:clone'; + if (key === 'sessionId') return 'clone-session'; + if (key === 'email') return 'clone@example.com'; + if (key === 'authorisationType') return 'BASIC'; + return undefined; + }) + .it('should create session.json before any logs are written', () => { + const sessionPath = getSessionLogPath(); + const metadataPath = path.join(sessionPath, 'session.json'); + + // Verify session.json exists immediately after getSessionLogPath + expect(fs.existsSync(metadataPath)).to.be.true; + + // Now create logger and write a log + const logger = new Logger({ + basePath: tempDir, + consoleLogLevel: 'info', + logLevel: 'info', + }); + + const winLogger = logger.getLoggerInstance('error'); + winLogger.error('Test log entry'); + + // Verify session.json still exists and wasn't overwritten + expect(fs.existsSync(metadataPath)).to.be.true; + + // Verify log file exists (winston writes asynchronously, so wait a bit) + const logFilePath = path.join(sessionPath, 'error.log'); + return new Promise((resolve, reject) => { + const maxAttempts = 20; // 20 * 50ms = 1 second max wait + let attempts = 0; + const checkFile = () => { + attempts++; + if (fs.existsSync(logFilePath)) { + resolve(); + } else if (attempts >= maxAttempts) { + reject(new Error(`Log file ${logFilePath} was not created within timeout`)); + } else { + setTimeout(checkFile, 50); + } + }; + checkFile(); + }).then(() => { + expect(fs.existsSync(logFilePath)).to.be.true; + + // Verify metadata is valid JSON + const metadataContent = fs.readFileSync(metadataPath, 'utf8'); + const metadata = JSON.parse(metadataContent); + expect(metadata.command).to.equal('cm:stacks:clone'); + expect(metadata.module).to.equal('clone'); + }); + }); }); }); diff --git a/packages/contentstack-variants/src/types/utils.ts b/packages/contentstack-variants/src/types/utils.ts index 888b835d7f..1faf91eed0 100644 --- a/packages/contentstack-variants/src/types/utils.ts +++ b/packages/contentstack-variants/src/types/utils.ts @@ -8,13 +8,5 @@ export interface LogType { } export interface Context { - command: string; module: string; - userId: string | undefined; - email: string | undefined; - sessionId: string | undefined; - clientId?: string | undefined; - apiKey: string; - orgId: string; - authMethod?: string; } \ No newline at end of file diff --git a/packages/contentstack/src/hooks/prerun/latest-version-warning.ts b/packages/contentstack/src/hooks/prerun/latest-version-warning.ts index 9136b5c370..581a695b0e 100644 --- a/packages/contentstack/src/hooks/prerun/latest-version-warning.ts +++ b/packages/contentstack/src/hooks/prerun/latest-version-warning.ts @@ -3,7 +3,7 @@ import * as semver from 'semver'; import { IVersionUpgradeCache, IVersionUpgradeWarningFrequency } from '../../interfaces'; const versionUpgradeWarningFrequency: IVersionUpgradeWarningFrequency = { - versionSyncDuration: 3 * 24 * 60 * 60 * 1000, + versionSyncDuration: 3 * 24 * 60 * 60 * 1000, // 3 days }; export default async function (_opts): Promise { const now = Date.now(); @@ -11,6 +11,11 @@ export default async function (_opts): Promise { const logger: LoggerService = new LoggerService(process.env.CS_CLI_LOG_PATH || process.cwd(), 'cli-log'); let cache: IVersionUpgradeCache = { lastChecked: 0, lastWarnedDate: '', latestVersion: '' }; + // if CLI_VERSION is not set or is not the same as the current version, set it + if (!configHandler.get('CLI_VERSION') || configHandler.get('CLI_VERSION') !== this.config.version) { + configHandler.set('CLI_VERSION', this.config.version); // set current version in configHandler + } + if (!configHandler.get('versionUpgradeWarningFrequency')) { configHandler.set('versionUpgradeWarningFrequency', versionUpgradeWarningFrequency); }