From c022d18a005de4da35730f7075d6905b98d533a1 Mon Sep 17 00:00:00 2001 From: manjinder Date: Tue, 9 Jun 2026 16:24:34 +0200 Subject: [PATCH 1/8] Add pull/push view-bookmarks commands Add `pull view-bookmarks` and `push view-bookmarks` commands to transfer view (board) bookmarks between teams, mirroring the existing analysis bookmarks implementation but targeting the blueprint BookmarksTransferController import/export endpoints (/blueprint/api/bookmarks). Includes-AI-Code: true Co-authored-by: Cursor --- docs/user-guide/studio-commands.md | 19 ++++++ src/commands/view/module.ts | 37 +++++++++++ .../view/view-bookmarks-command.service.ts | 18 ++++++ .../view/view-bookmarks.manager-factory.ts | 39 +++++++++++ src/commands/view/view-bookmarks.manager.ts | 64 +++++++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 src/commands/view/module.ts create mode 100644 src/commands/view/view-bookmarks-command.service.ts create mode 100644 src/commands/view/view-bookmarks.manager-factory.ts create mode 100644 src/commands/view/view-bookmarks.manager.ts diff --git a/docs/user-guide/studio-commands.md b/docs/user-guide/studio-commands.md index 9614b816..25052f72 100644 --- a/docs/user-guide/studio-commands.md +++ b/docs/user-guide/studio-commands.md @@ -208,3 +208,22 @@ that analysis. Use the ***--help*** flag to see all options for a specific comma content-cli pull analysis --help content-cli push analysis --help ``` + +## Pull and Push View Bookmarks + +Enable users to pull and push view (board) bookmarks using content-cli. For pulling view bookmarks +you can specify --type (SHARED/ALL), and by default it fetches USER bookmarks. + +``` +// Pull view bookmarks +content-cli pull view-bookmarks --profile my-profile-name --id 73d39112-73ae-4bbe-8051-3c0f14e065ec --type SHARED +``` + +After you have pulled your view bookmarks with the --type option, +it's time to push them inside a view in a different package. You can accomplish this using +the same command as with pushing other assets to Studio: + +``` +// Push view bookmarks to Studio +content-cli push view-bookmarks -p my-profile-name --id 73d39112-73ae-4bbe-8051-3c0f14e065ec --file studio_view_bookmarks_39c5bb7b-b486-4230-ab01-854a17ddbff2.json +``` diff --git a/src/commands/view/module.ts b/src/commands/view/module.ts new file mode 100644 index 00000000..556d392d --- /dev/null +++ b/src/commands/view/module.ts @@ -0,0 +1,37 @@ +/** + * Commands related to the View feature. + */ + +import { Configurator, IModule } from "../../core/command/module-handler"; +import { Context } from "../../core/command/cli-context"; +import { Command, OptionValues } from "commander"; +import { ViewBookmarksCommandService } from "./view-bookmarks-command.service"; + +class Module extends IModule { + + public register(context: Context, configurator: Configurator): void { + const pullCommand = configurator.command("pull"); + pullCommand.command("view-bookmarks") + .description("Command to pull a view bookmarks") + .option("--type ", "Pull SHARED/ALL View Bookmarks, else by default get USER bookmarks") + .requiredOption("--id ", "Id of the view (board) you want to pull") + .action(this.pullViewBookmarks); + + const pushCommand = configurator.command("push"); + pushCommand.command("view-bookmarks") + .description("Command to push a view bookmarks to a board") + .requiredOption("--id ", "Id of the view (board) to which you want to push the view bookmarks") + .requiredOption("-f, --file ", "The file you want to push") + .action(this.pushViewBookmarks); + } + + private async pullViewBookmarks(context: Context, command: Command, options: OptionValues): Promise { + await new ViewBookmarksCommandService(context).pullViewBookmarks(options.id, options.type); + } + + private async pushViewBookmarks(context: Context, command: Command, options: OptionValues): Promise { + await new ViewBookmarksCommandService(context).pushViewBookmarks(options.id, options.file); + } +} + +export = Module; diff --git a/src/commands/view/view-bookmarks-command.service.ts b/src/commands/view/view-bookmarks-command.service.ts new file mode 100644 index 00000000..711b15c2 --- /dev/null +++ b/src/commands/view/view-bookmarks-command.service.ts @@ -0,0 +1,18 @@ +import { ViewBookmarksManagerFactory } from "./view-bookmarks.manager-factory"; +import { Context } from "../../core/command/cli-context"; + +export class ViewBookmarksCommandService { + private viewBookmarksManagerFactory: ViewBookmarksManagerFactory; + + constructor(context: Context) { + this.viewBookmarksManagerFactory = new ViewBookmarksManagerFactory(context); + } + + public async pullViewBookmarks(boardId: string, type: string): Promise { + await this.viewBookmarksManagerFactory.createViewBookmarksManager(null, boardId, type).pull(); + } + + public async pushViewBookmarks(boardId: string, filename: string): Promise { + await this.viewBookmarksManagerFactory.createViewBookmarksManager(filename, boardId).push(); + } +} diff --git a/src/commands/view/view-bookmarks.manager-factory.ts b/src/commands/view/view-bookmarks.manager-factory.ts new file mode 100644 index 00000000..c89dcec9 --- /dev/null +++ b/src/commands/view/view-bookmarks.manager-factory.ts @@ -0,0 +1,39 @@ +import * as fs from "fs"; +import * as path from "path"; +import { ViewBookmarksManager } from "./view-bookmarks.manager"; +import { FatalError, logger } from "../../core/utils/logger"; +import { Context } from "../../core/command/cli-context"; + +export class ViewBookmarksManagerFactory { + + private readonly context: Context; + + constructor(context: Context) { + this.context = context; + } + + public createViewBookmarksManager( + filename: string, + boardId: string, + type?: string + ): ViewBookmarksManager { + const viewBookmarksManager = new ViewBookmarksManager(this.context); + viewBookmarksManager.boardId = boardId; + if (type === undefined || type === null) { + type = "USER"; + } + + viewBookmarksManager.type = type; + if (filename !== null) { + viewBookmarksManager.fileName = this.readFile(filename); + } + return viewBookmarksManager; + } + + private readFile(fileName: string): string { + if (!fs.existsSync(path.resolve(process.cwd(), fileName))) { + logger.error(new FatalError("The provided file does not exist")); + } + return path.resolve(process.cwd(), fileName); + } +} diff --git a/src/commands/view/view-bookmarks.manager.ts b/src/commands/view/view-bookmarks.manager.ts new file mode 100644 index 00000000..9b1a4924 --- /dev/null +++ b/src/commands/view/view-bookmarks.manager.ts @@ -0,0 +1,64 @@ +import * as fs from "fs"; +import * as FormData from "form-data"; +import { Context } from "../../core/command/cli-context"; +import { BaseManager } from "../../core/http/http-shared/base.manager"; +import { ManagerConfig } from "../../core/http/http-shared/manager-config.interface"; + +export class ViewBookmarksManager extends BaseManager { + private static BASE_URL = "/blueprint/api/bookmarks"; + private static VIEW_BOOKMARKS_FILE_PREFIX = "studio_view_bookmarks_"; + + private _boardId: string; + private _fileName: string; + private _type: string; + + constructor(context: Context) { + super(context); + } + + public get fileName(): string { + return this._fileName; + } + + public set fileName(value: string) { + this._fileName = value; + } + + public get boardId(): string { + return this._boardId; + } + + public set boardId(value: string) { + this._boardId = value; + } + + public get type(): string { + return this._type; + } + + public set type(value: string) { + this._type = value; + } + + public getConfig(): ManagerConfig { + const pullUrl = `${ViewBookmarksManager.BASE_URL}/export?boardId=${this.boardId}&type=${this.type}`; + return { + pushUrl: `${ViewBookmarksManager.BASE_URL}/import?boardId=${this.boardId}`, + pullUrl: pullUrl, + exportFileName: `${ViewBookmarksManager.VIEW_BOOKMARKS_FILE_PREFIX}${this.boardId}.json`, + onPushSuccessMessage: (): string => { + return "View Bookmarks was pushed successfully."; + }, + }; + } + + public getBody(): any { + const formData = new FormData(); + formData.append("file", fs.createReadStream(this.fileName)); + return formData; + } + + protected getSerializedFileContent(data: any): string { + return JSON.stringify(data); + } +} From c00a32a2a6292d7d80e93e90162f8fa9c54de108 Mon Sep 17 00:00:00 2001 From: manjinder Date: Wed, 10 Jun 2026 11:30:01 +0200 Subject: [PATCH 2/8] fix: review PR comments --- docs/user-guide/studio-commands.md | 2 +- src/commands/view/module.ts | 18 ++++++++++-------- .../view/view-bookmarks-command.service.ts | 2 +- .../view/view-bookmarks.manager-factory.ts | 4 +--- src/commands/view/view-bookmarks.manager.ts | 7 +++---- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/user-guide/studio-commands.md b/docs/user-guide/studio-commands.md index 25052f72..3be7e25c 100644 --- a/docs/user-guide/studio-commands.md +++ b/docs/user-guide/studio-commands.md @@ -220,7 +220,7 @@ content-cli pull view-bookmarks --profile my-profile-name --id 73d39112-73ae-4bb ``` After you have pulled your view bookmarks with the --type option, -it's time to push them inside a view in a different package. You can accomplish this using +it's time to push them inside a view in a different team. You can accomplish this using the same command as with pushing other assets to Studio: ``` diff --git a/src/commands/view/module.ts b/src/commands/view/module.ts index 556d392d..64326c52 100644 --- a/src/commands/view/module.ts +++ b/src/commands/view/module.ts @@ -11,17 +11,19 @@ class Module extends IModule { public register(context: Context, configurator: Configurator): void { const pullCommand = configurator.command("pull"); - pullCommand.command("view-bookmarks") - .description("Command to pull a view bookmarks") - .option("--type ", "Pull SHARED/ALL View Bookmarks, else by default get USER bookmarks") - .requiredOption("--id ", "Id of the view (board) you want to pull") + pullCommand + .command("view-bookmarks") + .description("Command to pull view bookmarks") + .option("--type ", "Type of view bookmarks to pull: USER (default), SHARED, or ALL") + .requiredOption("--id ", "ID of the view (board) to pull bookmarks from") .action(this.pullViewBookmarks); const pushCommand = configurator.command("push"); - pushCommand.command("view-bookmarks") - .description("Command to push a view bookmarks to a board") - .requiredOption("--id ", "Id of the view (board) to which you want to push the view bookmarks") - .requiredOption("-f, --file ", "The file you want to push") + pushCommand + .command("view-bookmarks") + .description("Command to push view bookmarks to a board") + .requiredOption("--id ", "ID of the view (board) to push bookmarks into") + .requiredOption("-f, --file ", "The file to push") .action(this.pushViewBookmarks); } diff --git a/src/commands/view/view-bookmarks-command.service.ts b/src/commands/view/view-bookmarks-command.service.ts index 711b15c2..558d6f16 100644 --- a/src/commands/view/view-bookmarks-command.service.ts +++ b/src/commands/view/view-bookmarks-command.service.ts @@ -8,7 +8,7 @@ export class ViewBookmarksCommandService { this.viewBookmarksManagerFactory = new ViewBookmarksManagerFactory(context); } - public async pullViewBookmarks(boardId: string, type: string): Promise { + public async pullViewBookmarks(boardId: string, type?: string): Promise { await this.viewBookmarksManagerFactory.createViewBookmarksManager(null, boardId, type).pull(); } diff --git a/src/commands/view/view-bookmarks.manager-factory.ts b/src/commands/view/view-bookmarks.manager-factory.ts index c89dcec9..7bc67fd7 100644 --- a/src/commands/view/view-bookmarks.manager-factory.ts +++ b/src/commands/view/view-bookmarks.manager-factory.ts @@ -19,9 +19,7 @@ export class ViewBookmarksManagerFactory { ): ViewBookmarksManager { const viewBookmarksManager = new ViewBookmarksManager(this.context); viewBookmarksManager.boardId = boardId; - if (type === undefined || type === null) { - type = "USER"; - } + type = (type ?? "USER").toUpperCase(); viewBookmarksManager.type = type; if (filename !== null) { diff --git a/src/commands/view/view-bookmarks.manager.ts b/src/commands/view/view-bookmarks.manager.ts index 9b1a4924..6988cda9 100644 --- a/src/commands/view/view-bookmarks.manager.ts +++ b/src/commands/view/view-bookmarks.manager.ts @@ -41,13 +41,12 @@ export class ViewBookmarksManager extends BaseManager { } public getConfig(): ManagerConfig { - const pullUrl = `${ViewBookmarksManager.BASE_URL}/export?boardId=${this.boardId}&type=${this.type}`; return { - pushUrl: `${ViewBookmarksManager.BASE_URL}/import?boardId=${this.boardId}`, - pullUrl: pullUrl, + pushUrl: `${ViewBookmarksManager.BASE_URL}/import?boardId=${encodeURIComponent(this.boardId)}`, + pullUrl: `${ViewBookmarksManager.BASE_URL}/export?boardId=${encodeURIComponent(this.boardId)}&type=${encodeURIComponent(this.type)}`, exportFileName: `${ViewBookmarksManager.VIEW_BOOKMARKS_FILE_PREFIX}${this.boardId}.json`, onPushSuccessMessage: (): string => { - return "View Bookmarks was pushed successfully."; + return "View Bookmarks were pushed successfully."; }, }; } From 981177cd3a6ef82d1724828d00e2a4a901878b26 Mon Sep 17 00:00:00 2001 From: manjinder Date: Wed, 10 Jun 2026 13:21:06 +0200 Subject: [PATCH 3/8] Add tests for view-bookmarks commands Cover the new pull/push view-bookmarks module, command service, manager, and manager-factory (100% line/branch coverage) to satisfy SonarCloud coverage on new code. Includes-AI-Code: true Co-authored-by: Cursor --- .../view/view-bookmarks-module.spec.ts | 63 +++++++++++++++ tests/commands/view/view-bookmarks.spec.ts | 76 +++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 tests/commands/view/view-bookmarks-module.spec.ts create mode 100644 tests/commands/view/view-bookmarks.spec.ts diff --git a/tests/commands/view/view-bookmarks-module.spec.ts b/tests/commands/view/view-bookmarks-module.spec.ts new file mode 100644 index 00000000..0766defc --- /dev/null +++ b/tests/commands/view/view-bookmarks-module.spec.ts @@ -0,0 +1,63 @@ +import Module = require("../../../src/commands/view/module"); +import { Command, OptionValues } from "commander"; +import { ViewBookmarksCommandService } from "../../../src/commands/view/view-bookmarks-command.service"; +import { testContext } from "../../utls/test-context"; +import { createMockConfigurator } from "../../utls/configurator-mock"; + +jest.mock("../../../src/commands/view/view-bookmarks-command.service"); + +describe("View Bookmarks Module", () => { + let module: Module; + let mockCommand: Command; + let mockService: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + module = new Module(); + mockCommand = {} as Command; + + mockService = { + pullViewBookmarks: jest.fn().mockResolvedValue(undefined), + pushViewBookmarks: jest.fn().mockResolvedValue(undefined), + } as any; + + (ViewBookmarksCommandService as jest.MockedClass) + .mockImplementation(() => mockService); + }); + + it("should call pullViewBookmarks with id and type", async () => { + const options: OptionValues = { id: "board-123", type: "SHARED" }; + await (module as any).pullViewBookmarks(testContext, mockCommand, options); + expect(mockService.pullViewBookmarks).toHaveBeenCalledWith("board-123", "SHARED"); + }); + + it("should call pushViewBookmarks with id and file", async () => { + const options: OptionValues = { id: "board-123", file: "bookmarks.json" }; + await (module as any).pushViewBookmarks(testContext, mockCommand, options); + expect(mockService.pushViewBookmarks).toHaveBeenCalledWith("board-123", "bookmarks.json"); + }); + + describe("register", () => { + it("registers the pull and push command groups without throwing", () => { + const mockConfigurator = createMockConfigurator(); + + expect(() => new Module().register(testContext, mockConfigurator)).not.toThrow(); + + expect(mockConfigurator.command).toHaveBeenCalledWith("pull"); + expect(mockConfigurator.command).toHaveBeenCalledWith("push"); + }); + + it("wires an action handler for every leaf subcommand", () => { + const mockConfigurator = createMockConfigurator(); + + new Module().register(testContext, mockConfigurator); + + // pull view-bookmarks + push view-bookmarks + const expectedLeafCommands = 2; + expect(mockConfigurator.action).toHaveBeenCalledTimes(expectedLeafCommands); + for (const call of mockConfigurator.action.mock.calls) { + expect(typeof call[0]).toBe("function"); + } + }); + }); +}); diff --git a/tests/commands/view/view-bookmarks.spec.ts b/tests/commands/view/view-bookmarks.spec.ts new file mode 100644 index 00000000..260f3ad1 --- /dev/null +++ b/tests/commands/view/view-bookmarks.spec.ts @@ -0,0 +1,76 @@ +import * as fs from "fs"; +import * as path from "path"; +import { mockAxiosGet, mockAxiosPost, mockedAxiosInstance } from "../../utls/http-requests-mock"; +import { mockCreateReadStream, mockExistsSync } from "../../utls/fs-mock-utils"; +import { ViewBookmarksCommandService } from "../../../src/commands/view/view-bookmarks-command.service"; +import { ViewBookmarksManagerFactory } from "../../../src/commands/view/view-bookmarks.manager-factory"; +import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup"; +import { testContext } from "../../utls/test-context"; + +describe("View bookmarks", () => { + + const boardId = "73d39112-73ae-4bbe-8051-3c0f14e065ec"; + const exportBaseUrl = `https://myTeam.celonis.cloud/blueprint/api/bookmarks/export?boardId=${boardId}`; + const importUrl = `https://myTeam.celonis.cloud/blueprint/api/bookmarks/import?boardId=${boardId}`; + const bookmarksResponse = [ + { + bookmark: { name: "My View Bookmark", ownerId: "user-1", userPreferenceId: "pref-1" }, + preference: { id: "pref-1", value: "{}" }, + }, + ]; + + describe("pull", () => { + it("Should call export API with the default USER type and write the response to a file", async () => { + mockAxiosGet(`${exportBaseUrl}&type=USER`, bookmarksResponse); + + await new ViewBookmarksCommandService(testContext).pullViewBookmarks(boardId, undefined); + + expect(mockedAxiosInstance.get).toHaveBeenCalledWith(`${exportBaseUrl}&type=USER`, expect.anything()); + expect(mockWriteFileSync).toHaveBeenCalledWith( + path.resolve(process.cwd(), `studio_view_bookmarks_${boardId}.json`), + JSON.stringify(bookmarksResponse), + { encoding: "utf-8", mode: 0o600 } + ); + expect(loggingTestTransport.logMessages.length).toBe(1); + expect(loggingTestTransport.logMessages[0].message).toContain("File downloaded successfully. New filename: "); + }); + + it("Should call export API with the provided type", async () => { + mockAxiosGet(`${exportBaseUrl}&type=SHARED`, bookmarksResponse); + + await new ViewBookmarksCommandService(testContext).pullViewBookmarks(boardId, "SHARED"); + + expect(mockedAxiosInstance.get).toHaveBeenCalledWith(`${exportBaseUrl}&type=SHARED`, expect.anything()); + expect(mockWriteFileSync).toHaveBeenCalledWith( + path.resolve(process.cwd(), `studio_view_bookmarks_${boardId}.json`), + JSON.stringify(bookmarksResponse), + { encoding: "utf-8", mode: 0o600 } + ); + }); + }); + + describe("push", () => { + it("Should call import API with the file as multipart body", async () => { + mockAxiosPost(importUrl, {}); + mockExistsSync(); + mockCreateReadStream(Buffer.from(JSON.stringify(bookmarksResponse))); + + await new ViewBookmarksCommandService(testContext).pushViewBookmarks(boardId, "bookmarks.json"); + + expect(mockedAxiosInstance.post).toHaveBeenCalledWith(importUrl, expect.anything(), expect.anything()); + expect(loggingTestTransport.logMessages.length).toBe(1); + expect(loggingTestTransport.logMessages[0].message).toContain("View Bookmarks were pushed successfully."); + }); + }); + + describe("manager factory", () => { + it("Should report a fatal error when the push file does not exist", () => { + (fs.existsSync as jest.Mock).mockReturnValue(false); + const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => undefined) as never); + + new ViewBookmarksManagerFactory(testContext).createViewBookmarksManager("missing.json", boardId); + + expect(exitSpy).toHaveBeenCalledWith(1); + }); + }); +}); From 23c406ae77bb58b1f71a12a279f731288dce9952 Mon Sep 17 00:00:00 2001 From: manjinder Date: Wed, 10 Jun 2026 13:35:11 +0200 Subject: [PATCH 4/8] fix: address sonar issues --- src/commands/view/view-bookmarks-command.service.ts | 2 +- src/commands/view/view-bookmarks.manager-factory.ts | 4 ++-- src/commands/view/view-bookmarks.manager.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands/view/view-bookmarks-command.service.ts b/src/commands/view/view-bookmarks-command.service.ts index 558d6f16..fae9b9b6 100644 --- a/src/commands/view/view-bookmarks-command.service.ts +++ b/src/commands/view/view-bookmarks-command.service.ts @@ -2,7 +2,7 @@ import { ViewBookmarksManagerFactory } from "./view-bookmarks.manager-factory"; import { Context } from "../../core/command/cli-context"; export class ViewBookmarksCommandService { - private viewBookmarksManagerFactory: ViewBookmarksManagerFactory; + private readonly viewBookmarksManagerFactory: ViewBookmarksManagerFactory; constructor(context: Context) { this.viewBookmarksManagerFactory = new ViewBookmarksManagerFactory(context); diff --git a/src/commands/view/view-bookmarks.manager-factory.ts b/src/commands/view/view-bookmarks.manager-factory.ts index 7bc67fd7..d5263918 100644 --- a/src/commands/view/view-bookmarks.manager-factory.ts +++ b/src/commands/view/view-bookmarks.manager-factory.ts @@ -1,5 +1,5 @@ -import * as fs from "fs"; -import * as path from "path"; +import * as fs from "node:fs"; +import * as path from "node:path"; import { ViewBookmarksManager } from "./view-bookmarks.manager"; import { FatalError, logger } from "../../core/utils/logger"; import { Context } from "../../core/command/cli-context"; diff --git a/src/commands/view/view-bookmarks.manager.ts b/src/commands/view/view-bookmarks.manager.ts index 6988cda9..6595cfe6 100644 --- a/src/commands/view/view-bookmarks.manager.ts +++ b/src/commands/view/view-bookmarks.manager.ts @@ -1,12 +1,12 @@ -import * as fs from "fs"; +import * as fs from "node:fs"; import * as FormData from "form-data"; import { Context } from "../../core/command/cli-context"; import { BaseManager } from "../../core/http/http-shared/base.manager"; import { ManagerConfig } from "../../core/http/http-shared/manager-config.interface"; export class ViewBookmarksManager extends BaseManager { - private static BASE_URL = "/blueprint/api/bookmarks"; - private static VIEW_BOOKMARKS_FILE_PREFIX = "studio_view_bookmarks_"; + private static readonly BASE_URL = "/blueprint/api/bookmarks"; + private static readonly VIEW_BOOKMARKS_FILE_PREFIX = "studio_view_bookmarks_"; private _boardId: string; private _fileName: string; From 333ad7e897204a7416328c48a965c07e662853bc Mon Sep 17 00:00:00 2001 From: manjinder Date: Wed, 10 Jun 2026 13:52:47 +0200 Subject: [PATCH 5/8] fix: tests --- tests/commands/view/view-bookmarks.spec.ts | 4 ++-- tests/jest.setup.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/commands/view/view-bookmarks.spec.ts b/tests/commands/view/view-bookmarks.spec.ts index 260f3ad1..1767e11a 100644 --- a/tests/commands/view/view-bookmarks.spec.ts +++ b/tests/commands/view/view-bookmarks.spec.ts @@ -1,5 +1,5 @@ -import * as fs from "fs"; -import * as path from "path"; +import * as fs from "node:fs"; +import * as path from "node:path"; import { mockAxiosGet, mockAxiosPost, mockedAxiosInstance } from "../../utls/http-requests-mock"; import { mockCreateReadStream, mockExistsSync } from "../../utls/fs-mock-utils"; import { ViewBookmarksCommandService } from "../../../src/commands/view/view-bookmarks-command.service"; diff --git a/tests/jest.setup.ts b/tests/jest.setup.ts index ae3002dd..1473412e 100644 --- a/tests/jest.setup.ts +++ b/tests/jest.setup.ts @@ -6,6 +6,9 @@ import { logger } from "../src/core/utils/logger"; mockAxios(); jest.mock("fs"); +// Route "node:fs" imports to the same mock instance as "fs" so that helpers +// operating on "fs" (e.g. mockExistsSync) also affect modules importing "node:fs". +jest.mock("node:fs", () => require("fs")); const mockWriteFileSync = jest.fn(); (fs.writeFileSync as jest.Mock).mockImplementation(mockWriteFileSync); From eb9122eefd8dd9e961afb9a752afc289ef7a8bca Mon Sep 17 00:00:00 2001 From: manjinder Date: Wed, 10 Jun 2026 16:30:06 +0200 Subject: [PATCH 6/8] fix: address PR comments --- src/commands/view/view-bookmarks.manager-factory.ts | 11 +++-------- tests/jest.setup.ts | 2 -- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/commands/view/view-bookmarks.manager-factory.ts b/src/commands/view/view-bookmarks.manager-factory.ts index d5263918..92a7fcb5 100644 --- a/src/commands/view/view-bookmarks.manager-factory.ts +++ b/src/commands/view/view-bookmarks.manager-factory.ts @@ -5,30 +5,25 @@ import { FatalError, logger } from "../../core/utils/logger"; import { Context } from "../../core/command/cli-context"; export class ViewBookmarksManagerFactory { - private readonly context: Context; constructor(context: Context) { this.context = context; } - public createViewBookmarksManager( - filename: string, - boardId: string, - type?: string - ): ViewBookmarksManager { + public createViewBookmarksManager(filename: string, boardId: string, type?: string): ViewBookmarksManager { const viewBookmarksManager = new ViewBookmarksManager(this.context); viewBookmarksManager.boardId = boardId; type = (type ?? "USER").toUpperCase(); viewBookmarksManager.type = type; if (filename !== null) { - viewBookmarksManager.fileName = this.readFile(filename); + viewBookmarksManager.fileName = this.resolveFilePath(filename); } return viewBookmarksManager; } - private readFile(fileName: string): string { + private resolveFilePath(fileName: string): string { if (!fs.existsSync(path.resolve(process.cwd(), fileName))) { logger.error(new FatalError("The provided file does not exist")); } diff --git a/tests/jest.setup.ts b/tests/jest.setup.ts index 1473412e..54cc8944 100644 --- a/tests/jest.setup.ts +++ b/tests/jest.setup.ts @@ -6,8 +6,6 @@ import { logger } from "../src/core/utils/logger"; mockAxios(); jest.mock("fs"); -// Route "node:fs" imports to the same mock instance as "fs" so that helpers -// operating on "fs" (e.g. mockExistsSync) also affect modules importing "node:fs". jest.mock("node:fs", () => require("fs")); const mockWriteFileSync = jest.fn(); From 9c26621b8ac037b682dcd95257b42638211b1b51 Mon Sep 17 00:00:00 2001 From: manjinder Date: Wed, 10 Jun 2026 16:33:37 +0200 Subject: [PATCH 7/8] fix: instructions comment --- docs/user-guide/studio-commands.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user-guide/studio-commands.md b/docs/user-guide/studio-commands.md index 3be7e25c..f738495a 100644 --- a/docs/user-guide/studio-commands.md +++ b/docs/user-guide/studio-commands.md @@ -212,16 +212,16 @@ content-cli push analysis --help ## Pull and Push View Bookmarks Enable users to pull and push view (board) bookmarks using content-cli. For pulling view bookmarks -you can specify --type (SHARED/ALL), and by default it fetches USER bookmarks. +you can specify --type (SHARED/ALL/USER), and by default it fetches USER bookmarks: ``` // Pull view bookmarks content-cli pull view-bookmarks --profile my-profile-name --id 73d39112-73ae-4bbe-8051-3c0f14e065ec --type SHARED ``` -After you have pulled your view bookmarks with the --type option, +After you have pulled your view bookmarks, it's time to push them inside a view in a different team. You can accomplish this using -the same command as with pushing other assets to Studio: +the same command as with pushing other assets in Studio: ``` // Push view bookmarks to Studio From 036ae2086f4330042b4ec6024b21812cce55426c Mon Sep 17 00:00:00 2001 From: manjinder Date: Thu, 11 Jun 2026 10:17:13 +0200 Subject: [PATCH 8/8] fix: variable name --- src/commands/view/view-bookmarks.manager-factory.ts | 2 +- src/commands/view/view-bookmarks.manager.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/commands/view/view-bookmarks.manager-factory.ts b/src/commands/view/view-bookmarks.manager-factory.ts index 92a7fcb5..8047768d 100644 --- a/src/commands/view/view-bookmarks.manager-factory.ts +++ b/src/commands/view/view-bookmarks.manager-factory.ts @@ -18,7 +18,7 @@ export class ViewBookmarksManagerFactory { viewBookmarksManager.type = type; if (filename !== null) { - viewBookmarksManager.fileName = this.resolveFilePath(filename); + viewBookmarksManager.filePath = this.resolveFilePath(filename); } return viewBookmarksManager; } diff --git a/src/commands/view/view-bookmarks.manager.ts b/src/commands/view/view-bookmarks.manager.ts index 6595cfe6..9f1c736d 100644 --- a/src/commands/view/view-bookmarks.manager.ts +++ b/src/commands/view/view-bookmarks.manager.ts @@ -9,19 +9,19 @@ export class ViewBookmarksManager extends BaseManager { private static readonly VIEW_BOOKMARKS_FILE_PREFIX = "studio_view_bookmarks_"; private _boardId: string; - private _fileName: string; + private _filePath: string; private _type: string; constructor(context: Context) { super(context); } - public get fileName(): string { - return this._fileName; + public get filePath(): string { + return this._filePath; } - public set fileName(value: string) { - this._fileName = value; + public set filePath(value: string) { + this._filePath = value; } public get boardId(): string { @@ -53,7 +53,7 @@ export class ViewBookmarksManager extends BaseManager { public getBody(): any { const formData = new FormData(); - formData.append("file", fs.createReadStream(this.fileName)); + formData.append("file", fs.createReadStream(this.filePath)); return formData; }