From b71137bed2f2d216e4259b23a1e7e2f866af5017 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Mon, 20 Jul 2026 17:08:28 +0530 Subject: [PATCH 01/13] Fix: Reconcile completed progress bar counts with processed items Co-Authored-By: Claude Opus 4.8 --- .../progress-summary/cli-progress-manager.ts | 13 ++-- .../test/unit/cliProgressManager.test.ts | 59 +++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/packages/contentstack-utilities/src/progress-summary/cli-progress-manager.ts b/packages/contentstack-utilities/src/progress-summary/cli-progress-manager.ts index bf0d843e74..07a9dd25b3 100644 --- a/packages/contentstack-utilities/src/progress-summary/cli-progress-manager.ts +++ b/packages/contentstack-utilities/src/progress-summary/cli-progress-manager.ts @@ -421,14 +421,19 @@ export default class CLIProgressManager { if (!this.showConsoleLogs && process.progressBar) { const totalProcessed = process.current; - const percentage = Math.round((totalProcessed / process.total) * 100); + // Reconcile the bar's total with the number of items actually processed so the + // rendered "value/total" matches the "Complete (success/processed)" summary. A + // process registered with an estimated total larger than the items actually ticked + // would otherwise render mismatched counts, e.g. "37/37 | Complete (27/27)". + process.progressBar.setTotal(totalProcessed); + const percentage = totalProcessed > 0 ? 100 : 0; const formattedPercentage = this.formatPercentage(percentage); const statusText = success - ? getChalk().green(`✓ Complete (${process.successCount}/${process.current})`) - : getChalk().red(`✗ Failed (${process.successCount}/${process.current})`); + ? getChalk().green(`✓ Complete (${process.successCount}/${totalProcessed})`) + : getChalk().red(`✗ Failed (${process.successCount}/${totalProcessed})`); const displayName = this.formatProcessName(processName); const indentedLabel = ` ├─ ${displayName}`.padEnd(25); - process.progressBar.update(process.total, { + process.progressBar.update(totalProcessed, { label: success ? getChalk().green(indentedLabel) : getChalk().red(indentedLabel), status: statusText, percentage: formattedPercentage, diff --git a/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts b/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts index cb0752b48d..3d1db0dd11 100644 --- a/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts +++ b/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts @@ -34,6 +34,7 @@ const mockProgressBar = { stop: sinon.stub(), increment: sinon.stub(), update: sinon.stub(), + setTotal: sinon.stub(), }; const mockMultiBar = { @@ -335,6 +336,64 @@ describe('CLIProgressManager', () => { }); }); + describe('Complete count reconciliation (DX-7521)', () => { + beforeEach(() => { + mockProgressBar.update.resetHistory(); + mockProgressBar.setTotal.resetHistory(); + mockProgressBar.increment.resetHistory(); + progressManager = new CLIProgressManager({ + enableNestedProgress: true, + moduleName: 'RECONCILE_TEST', + showConsoleLogs: false, + }); + }); + + fancy.it('renders the processed count, not the registered estimate, when fewer items were ticked', () => { + progressManager.addProcess('gf-update', 37); + for (let i = 0; i < 27; i++) { + progressManager.tick(true, `item-${i}`, null, 'gf-update'); + } + + progressManager.completeProcess('gf-update', true); + + expect(mockProgressBar.setTotal.calledWith(27)).to.equal(true); + expect(mockProgressBar.update.lastCall.args[0]).to.equal(27); + }); + + fancy.it('renders the full count unchanged when every registered item was ticked', () => { + progressManager.addProcess('entries-create', 59); + for (let i = 0; i < 59; i++) { + progressManager.tick(true, `item-${i}`, null, 'entries-create'); + } + + progressManager.completeProcess('entries-create', true); + + expect(mockProgressBar.update.lastCall.args[0]).to.equal(59); + }); + + fancy.it('renders the registered total when a process completes with no ticks (skip case)', () => { + progressManager.addProcess('skipped', 5); + + progressManager.completeProcess('skipped', true); + + expect(mockProgressBar.update.lastCall.args[0]).to.equal(5); + }); + + fancy.it('reconciles the denominator for a failed process with partial ticks', () => { + progressManager.addProcess('entries', 59); + for (let i = 0; i < 38; i++) { + progressManager.tick(true, `ok-${i}`, null, 'entries'); + } + for (let i = 0; i < 3; i++) { + progressManager.tick(false, `err-${i}`, 'boom', 'entries'); + } + + progressManager.completeProcess('entries', false); + + expect(mockProgressBar.update.lastCall.args[0]).to.equal(41); + }); + }); + describe('Progress Tracking', () => { beforeEach(() => { progressManager = new CLIProgressManager({ From bb5465987658fce7d923f468c6ecd2f9468e14d7 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Mon, 20 Jul 2026 17:20:39 +0530 Subject: [PATCH 02/13] test: make progress reconciliation tests independent of cli-progress mock The module-level cli-progress mock binds by load order and does not take effect in CI, so the reconciliation tests saw the real bar and failed. Inject a stub bar at the rendering seam instead, and drop the ticket number from the describe title. Co-Authored-By: Claude Opus 4.8 --- .../test/unit/cliProgressManager.test.ts | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts b/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts index 3d1db0dd11..ab6f20ab35 100644 --- a/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts +++ b/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts @@ -336,11 +336,17 @@ describe('CLIProgressManager', () => { }); }); - describe('Complete count reconciliation (DX-7521)', () => { + describe('Complete count reconciliation', () => { + let bar: { update: sinon.SinonStub; setTotal: sinon.SinonStub; increment: sinon.SinonStub }; + + // Inject a stub bar at the rendering seam so assertions do not depend on the + // load-order-sensitive cli-progress module mock (which does not bind in CI). + function injectBar(processName: string) { + bar = { update: sinon.stub(), setTotal: sinon.stub(), increment: sinon.stub() }; + (progressManager as any).processes.get(processName).progressBar = bar; + } + beforeEach(() => { - mockProgressBar.update.resetHistory(); - mockProgressBar.setTotal.resetHistory(); - mockProgressBar.increment.resetHistory(); progressManager = new CLIProgressManager({ enableNestedProgress: true, moduleName: 'RECONCILE_TEST', @@ -350,37 +356,41 @@ describe('CLIProgressManager', () => { fancy.it('renders the processed count, not the registered estimate, when fewer items were ticked', () => { progressManager.addProcess('gf-update', 37); + injectBar('gf-update'); for (let i = 0; i < 27; i++) { progressManager.tick(true, `item-${i}`, null, 'gf-update'); } progressManager.completeProcess('gf-update', true); - expect(mockProgressBar.setTotal.calledWith(27)).to.equal(true); - expect(mockProgressBar.update.lastCall.args[0]).to.equal(27); + expect(bar.setTotal.calledWith(27)).to.equal(true); + expect(bar.update.lastCall.args[0]).to.equal(27); }); fancy.it('renders the full count unchanged when every registered item was ticked', () => { progressManager.addProcess('entries-create', 59); + injectBar('entries-create'); for (let i = 0; i < 59; i++) { progressManager.tick(true, `item-${i}`, null, 'entries-create'); } progressManager.completeProcess('entries-create', true); - expect(mockProgressBar.update.lastCall.args[0]).to.equal(59); + expect(bar.update.lastCall.args[0]).to.equal(59); }); fancy.it('renders the registered total when a process completes with no ticks (skip case)', () => { progressManager.addProcess('skipped', 5); + injectBar('skipped'); progressManager.completeProcess('skipped', true); - expect(mockProgressBar.update.lastCall.args[0]).to.equal(5); + expect(bar.update.lastCall.args[0]).to.equal(5); }); fancy.it('reconciles the denominator for a failed process with partial ticks', () => { progressManager.addProcess('entries', 59); + injectBar('entries'); for (let i = 0; i < 38; i++) { progressManager.tick(true, `ok-${i}`, null, 'entries'); } @@ -390,7 +400,7 @@ describe('CLIProgressManager', () => { progressManager.completeProcess('entries', false); - expect(mockProgressBar.update.lastCall.args[0]).to.equal(41); + expect(bar.update.lastCall.args[0]).to.equal(41); }); }); From 6940f8287517e8ffe506d2e4fa92dd450050a0ea Mon Sep 17 00:00:00 2001 From: raj pandey Date: Thu, 23 Jul 2026 01:04:31 +0530 Subject: [PATCH 03/13] feat(utilities): add readGlobalFieldSchemas utility function Adds a thin wrapper around readContentTypeSchemas specifically for global fields, enabling callers to read per-file {uid}.json exports from the global_fields directory with a semantically accurate function name. Co-Authored-By: Claude Sonnet 4.6 --- packages/contentstack-utilities/src/content-type-utils.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/contentstack-utilities/src/content-type-utils.ts b/packages/contentstack-utilities/src/content-type-utils.ts index 0edfe85b38..3ed3886827 100644 --- a/packages/contentstack-utilities/src/content-type-utils.ts +++ b/packages/contentstack-utilities/src/content-type-utils.ts @@ -7,6 +7,13 @@ import { resolve as pResolve } from 'node:path'; * @param ignoredFiles - Files to ignore (defaults to schema.json, .DS_Store, __master.json, __priority.json) * @returns Array of content type schemas (empty if the path is missing or has no eligible files) */ +export function readGlobalFieldSchemas( + dirPath: string, + ignoredFiles?: string[], +): Record[] { + return readContentTypeSchemas(dirPath, ignoredFiles); +} + export function readContentTypeSchemas( dirPath: string, ignoredFiles: string[] = ['schema.json', '.DS_Store', '__master.json', '__priority.json', 'field_rules_uid.json'], From cd6cca91b51f67705f7c39bb769c9cdc5af3fe0e Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Thu, 23 Jul 2026 12:02:49 +0530 Subject: [PATCH 04/13] docs: mark launch as opt-in plugin in root README (DX-9744) Remove the inline launch command sections and TOC entries from the bundled CLI README and replace them with a note explaining that launch is provided by the opt-in @contentstack/cli-launch plugin (not bundled), including the install command and a link to the plugin's GitHub README. Scoped narrowly to the launch block per the DX-9744 decision; other opt-in-plugin docs are deferred. Co-Authored-By: Claude Opus 4.8 --- packages/contentstack/README.md | 291 +------------------------------- 1 file changed, 7 insertions(+), 284 deletions(-) diff --git a/packages/contentstack/README.md b/packages/contentstack/README.md index d3477ee4d1..9b449ee445 100644 --- a/packages/contentstack/README.md +++ b/packages/contentstack/README.md @@ -76,13 +76,6 @@ USAGE * [`csdx config:set:rate-limit`](#csdx-configsetrate-limit) * [`csdx config:set:region [REGION]`](#csdx-configsetregion-region) * [`csdx help [COMMAND]`](#csdx-help-command) -* [`csdx launch`](#csdx-launch) -* [`csdx launch:deployments`](#csdx-launchdeployments) -* [`csdx launch:environments`](#csdx-launchenvironments) -* [`csdx launch:functions`](#csdx-launchfunctions) -* [`csdx launch:logs`](#csdx-launchlogs) -* [`csdx launch:open`](#csdx-launchopen) -* [`csdx launch:rollback`](#csdx-launchrollback) * [`csdx login`](#csdx-login) * [`csdx logout`](#csdx-logout) * [`csdx plugins`](#csdx-plugins) @@ -1681,283 +1674,13 @@ DESCRIPTION _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.53/src/commands/help.ts)_ -## `csdx launch` - -Launch related operations - -``` -USAGE - $ csdx launch [-d ] [-c ] [--type GitHub|FileUpload] [--framework Gatsby|NextJs|CRA (Create - React App)|CSR (Client-Side Rendered)|Analog|Angular|Nuxt|Astro|VueJs|Remix|Other] [--org ] [-n ] [-e - ] [--branch ] [--build-command ] [--out-dir ] [--server-command ] - [--variable-type Import variables from a stack|Manually add custom variables to the list|Import variables from the - .env.local file|Skip adding environment variables...] [-a ] [--env-variables ] [--redeploy-latest] - [--redeploy-last-upload] [--response-mode buffered|streaming] - -FLAGS - -a, --alias= [optional] Alias (name) for the delivery token. - -c, --config= Path to the local '.cs-launch.json' file - -d, --data-dir= Current working directory - -e, --environment= [optional] Environment name for the Launch project. - -n, --name= [optional] Name of the project. - --branch= [optional] GitHub branch name. - --build-command= [optional] Build Command. - --env-variables= [optional] Provide the environment variables in the key:value format, separated by - comma. For example: APP_ENV:prod, TEST_ENV:testVal. - --framework=