From 6fc0d2c578bc9a0dff14bc638d096d61e0e59b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Allaire?= Date: Wed, 4 Mar 2026 16:22:15 -0500 Subject: [PATCH 1/3] Add troubleshoot deploy docs --- README.md | 28 ++ package-lock.json | 6 + package.json | 1 + .../org/commerce/troubleshoot/deploy.ts | 299 ++++++++++++++++++ .../org/commerce/troubleshoot/deploy.test.ts | 204 ++++++++++++ 5 files changed, 538 insertions(+) create mode 100644 src/commands/org/commerce/troubleshoot/deploy.ts create mode 100644 test/commands/org/commerce/troubleshoot/deploy.test.ts diff --git a/README.md b/README.md index 0a82913..b2816fc 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,34 @@ USAGE ... ``` +## Commerce Troubleshoot Console Deploy Examples + +Managed key strategy (default): + +```sh +coveops org commerce troubleshoot deploy --page-name commerce-troubleshoot-console +``` + +Provided tokens strategy: + +```sh +coveops org commerce troubleshoot deploy --page-name commerce-troubleshoot-console --engine-token --cmh-token +``` + +Update an existing hosted page with a known ID: + +```sh +coveops org commerce troubleshoot deploy --page-name commerce-troubleshoot-console --page-id +``` + +Name-only update behavior: + +```sh +coveops org commerce troubleshoot deploy --page-name commerce-troubleshoot-console +``` + +When `--page-id` is omitted, the deployer resolves by page name first: if a hosted page already matches that name, it updates it; otherwise deployment can create a new hosted page. + # Commands * [`coveops hello PERSON`](#coveops-hello-person) diff --git a/package-lock.json b/package-lock.json index 9c3c110..c90fef9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@coveo/cli-commons": "^2.9.4", "@coveo/platform-client": "^57.12.0", + "@coveops/commerce-troubleshoot-deployer": "^0.1.0", "@oclif/core": "^4", "@oclif/plugin-help": "^6", "@oclif/plugin-plugins": "^5" @@ -1335,6 +1336,11 @@ "query-string-esm": "npm:query-string@^9.0.0" } }, + "node_modules/@coveops/commerce-troubleshoot-deployer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@coveops/commerce-troubleshoot-deployer/-/commerce-troubleshoot-deployer-0.1.0.tgz", + "integrity": "sha512-Ck16q4kMdwbJ9H3PyrIf9BHFLKcYizwEoIQhKTKJg65nXDPwS+aole36QX2Q5rylhQEW9nkV4+LROA94VcQP2Q==" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", diff --git a/package.json b/package.json index 20471e9..462ef16 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "@coveo/cli-commons": "^2.9.4", "@coveo/platform-client": "^57.12.0", + "@coveops/commerce-troubleshoot-deployer": "^0.1.0", "@oclif/core": "^4", "@oclif/plugin-help": "^6", "@oclif/plugin-plugins": "^5" diff --git a/src/commands/org/commerce/troubleshoot/deploy.ts b/src/commands/org/commerce/troubleshoot/deploy.ts new file mode 100644 index 0000000..01cbd69 --- /dev/null +++ b/src/commands/org/commerce/troubleshoot/deploy.ts @@ -0,0 +1,299 @@ +import {Config, type Configuration} from '@coveo/cli-commons/config/config'; +import {Command, Flags} from '@oclif/core'; + +type RuntimeDefaults = { + country?: string; + currency?: string; + language?: string; + trackingId?: string; + viewUrl?: string; +}; + +type KeyStrategyProvided = { + cmhAccessToken?: string; + engineAccessToken: string; + mode: 'provided'; +}; + +type KeyStrategyManaged = { + mode: 'managed'; + rotate?: boolean; +}; + +type KeyStrategy = KeyStrategyManaged | KeyStrategyProvided; + +export type DeployTroubleshootRequest = { + auth: { + accessToken: string; + }; + deploy?: { + dryRun?: boolean; + }; + keyStrategy?: KeyStrategy; + runtimeDefaults?: RuntimeDefaults; + target: { + environment?: string; + hostedPageId?: string; + hostedPageName: string; + organizationId: string; + region?: string; + }; +}; + +type DeployTroubleshootResult = { + bundleDir: string; + deployConfigPath: string; + deployed: boolean; + diagnostics: string[]; + hostedPageId?: string; + hostedPageName: string; + keyInfo: { + cmhKeyId?: string; + created: boolean; + engineKeyId?: string; + reused: boolean; + source: 'managed' | 'provided'; + }; + organizationId: string; + runtimeConfigPath: string; +}; + +type DeployerModule = { + deployTroubleshootConsole: ( + request: DeployTroubleshootRequest, + options?: { + logger?: (message: string) => void; + } + ) => Promise; +}; + +type CommandHooks = { + loadDeployerModule: () => Promise; + readConfiguration: (configDir: string) => Configuration; +}; + +function readString(value: unknown): string | undefined { + if (typeof value !== 'string') { + return undefined; + } + + const trimmed = value.trim(); + return trimmed || undefined; +} + +const deployerPackageName = ['@coveops', 'commerce-troubleshoot-deployer'].join('/'); + +const defaultCommandHooks: CommandHooks = { + loadDeployerModule: async () => (await import(deployerPackageName)) as DeployerModule, + readConfiguration: (configDir) => new Config(configDir).get(), +}; + +const commandHooks: CommandHooks = { + ...defaultCommandHooks, +}; + +export const commerceTroubleshootDeployTestHooks = { + reset() { + commandHooks.loadDeployerModule = defaultCommandHooks.loadDeployerModule; + commandHooks.readConfiguration = defaultCommandHooks.readConfiguration; + }, + setLoadDeployerModule(loader: CommandHooks['loadDeployerModule']) { + commandHooks.loadDeployerModule = loader; + }, + setReadConfiguration(reader: CommandHooks['readConfiguration']) { + commandHooks.readConfiguration = reader; + }, +}; + +export default class CommerceTroubleshootDeploy extends Command { + static description = + 'Deploy or update the Commerce Troubleshoot Console hosted page through @coveops/commerce-troubleshoot-deployer.'; + + static examples = [ + '<%= config.bin %> <%= command.id %> --page-name commerce-troubleshoot-console', + '<%= config.bin %> <%= command.id %> --page-name commerce-troubleshoot-console --engine-token --cmh-token ', + '<%= config.bin %> <%= command.id %> --page-name commerce-troubleshoot-console --page-id f8f9b7d1-1f44-4f7c-9854-a2b0a4df1c13', + '<%= config.bin %> <%= command.id %> --page-name commerce-troubleshoot-console # Name-only deploy updates an existing page if one matches the name.', + ]; + + static flags = { + accessToken: Flags.string({ + aliases: ['access-token'], + description: 'Platform access token. Falls back to coveo config value accessToken.', + }), + cmhToken: Flags.string({ + aliases: ['cmh-token'], + description: 'CMH API key used when --engine-token is provided (provided key strategy).', + }), + country: Flags.string({ + default: 'US', + description: 'Runtime default country code for hosted app payload.', + }), + currency: Flags.string({ + default: 'USD', + description: 'Runtime default currency code for hosted app payload.', + }), + dryRun: Flags.boolean({ + aliases: ['dry-run'], + default: false, + description: 'Generate bundle and config without running coveo deploy.', + }), + engineToken: Flags.string({ + aliases: ['engine-token'], + description: 'Engine API key. Providing this switches key strategy to provided mode.', + }), + environment: Flags.string({ + description: 'Platform environment. Falls back to coveo config value environment.', + }), + language: Flags.string({ + default: 'en', + description: 'Runtime default language for hosted app payload.', + }), + organization: Flags.string({ + description: 'Organization ID. Falls back to coveo config value organization.', + }), + pageId: Flags.string({ + aliases: ['page-id'], + description: 'Hosted page ID to update directly.', + }), + pageName: Flags.string({ + aliases: ['page-name'], + description: 'Hosted page name for deploy target.', + required: true, + }), + region: Flags.string({ + description: 'Platform region. Falls back to coveo config value region.', + }), + rotate: Flags.boolean({ + default: false, + description: 'Rotate managed API keys before deploy (managed key strategy only).', + }), + trackingId: Flags.string({ + aliases: ['tracking-id'], + description: 'Runtime default tracking ID for hosted app payload.', + }), + viewUrl: Flags.string({ + aliases: ['view-url'], + default: 'https://www.example.com/', + description: 'Runtime default product listing URL for hosted app payload.', + }), + }; + + public async run() { + const {flags} = await this.parse(CommerceTroubleshootDeploy); + const resolvedConfiguration = commandHooks.readConfiguration(this.config.configDir); + + const organizationId = readString(flags.organization) ?? readString(resolvedConfiguration.organization); + const accessToken = readString(flags.accessToken) ?? readString(resolvedConfiguration.accessToken); + const region = readString(flags.region) ?? readString(resolvedConfiguration.region); + const environment = readString(flags.environment) ?? readString(resolvedConfiguration.environment); + const hostedPageId = readString(flags.pageId); + const trackingId = readString(flags.trackingId); + + if (!organizationId) { + this.error('Missing organization ID. Provide --organization or set it with coveo config:set organization .'); + } + + if (!accessToken) { + this.error('Missing access token. Provide --access-token or set it with coveo config:set accessToken .'); + } + + const keyStrategy = this.resolveKeyStrategy(flags); + + const request: DeployTroubleshootRequest = { + auth: { + accessToken, + }, + deploy: { + dryRun: flags.dryRun, + }, + keyStrategy, + runtimeDefaults: { + ...(trackingId ? {trackingId} : {}), + country: flags.country, + currency: flags.currency, + language: flags.language, + viewUrl: flags.viewUrl, + }, + target: { + hostedPageName: flags.pageName, + organizationId, + ...(hostedPageId ? {hostedPageId} : {}), + ...(region ? {region} : {}), + ...(environment ? {environment} : {}), + }, + }; + + const deployTroubleshootConsole = await this.loadDeployFunction(); + const result = await deployTroubleshootConsole(request, { + logger: (line) => this.debug(line), + }); + + this.log(`Hosted page name: ${result.hostedPageName}`); + this.log(`Hosted page id: ${result.hostedPageId ?? '(not resolved)'}`); + this.log(`Execution: ${result.deployed ? 'deploy executed' : 'dry-run (deploy skipped)'}`); + this.log( + `Key resolution: source=${result.keyInfo.source}, created=${result.keyInfo.created ? 'yes' : 'no'}, reused=${result.keyInfo.reused ? 'yes' : 'no'}` + ); + + if (result.keyInfo.engineKeyId) { + this.log(`Engine key id: ${result.keyInfo.engineKeyId}`); + } + + if (result.keyInfo.cmhKeyId) { + this.log(`CMH key id: ${result.keyInfo.cmhKeyId}`); + } + + this.log('Diagnostics:'); + if (!Array.isArray(result.diagnostics) || result.diagnostics.length === 0) { + this.log('- (none)'); + return; + } + + for (const line of result.diagnostics) { + this.log(`- ${line}`); + } + } + + private async loadDeployFunction(): Promise { + try { + const module = await commandHooks.loadDeployerModule(); + if (typeof module.deployTroubleshootConsole !== 'function') { + this.error('Invalid @coveops/commerce-troubleshoot-deployer package: deployTroubleshootConsole export is missing.'); + } + + return module.deployTroubleshootConsole; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + this.error( + `Unable to load @coveops/commerce-troubleshoot-deployer. Install the package and try again. Details: ${message}` + ); + } + } + + private resolveKeyStrategy(flags: { + cmhToken?: string; + engineToken?: string; + rotate: boolean; + }): KeyStrategy { + const engineAccessToken = readString(flags.engineToken); + const cmhAccessToken = readString(flags.cmhToken); + + if (!engineAccessToken) { + if (cmhAccessToken) { + this.warn('Ignoring --cmh-token because --engine-token was not provided; using managed key strategy.'); + } + + return { + mode: 'managed', + rotate: flags.rotate, + }; + } + + return { + engineAccessToken, + mode: 'provided', + ...(cmhAccessToken ? {cmhAccessToken} : {}), + }; + } +} diff --git a/test/commands/org/commerce/troubleshoot/deploy.test.ts b/test/commands/org/commerce/troubleshoot/deploy.test.ts new file mode 100644 index 0000000..45ba565 --- /dev/null +++ b/test/commands/org/commerce/troubleshoot/deploy.test.ts @@ -0,0 +1,204 @@ +import { + DefaultConfig, +} from '@coveo/cli-commons/config/config'; +import {captureOutput} from '@oclif/test'; +import {expect} from 'chai'; +import {afterEach, describe, it} from 'mocha'; + +import CommerceTroubleshootDeploy, { + type DeployTroubleshootRequest, + commerceTroubleshootDeployTestHooks, +} from '../../../../../src/commands/org/commerce/troubleshoot/deploy.js'; + +type DeployResult = { + bundleDir: string; + deployConfigPath: string; + deployed: boolean; + diagnostics: string[]; + hostedPageId?: string; + hostedPageName: string; + keyInfo: { + created: boolean; + reused: boolean; + source: 'managed' | 'provided'; + }; + organizationId: string; + runtimeConfigPath: string; +}; + +describe('org:commerce:troubleshoot:deploy', () => { + afterEach(() => { + commerceTroubleshootDeployTestHooks.reset(); + }); + + it('maps managed strategy flags + config fallback and invokes service', async () => { + let capturedRequest: DeployTroubleshootRequest | undefined; + + commerceTroubleshootDeployTestHooks.setReadConfiguration(() => ({ + ...DefaultConfig, + accessToken: 'cfg-access-token', + environment: 'prod', + organization: 'cfg-org', + region: 'us', + }) as never); + + commerceTroubleshootDeployTestHooks.setLoadDeployerModule(async () => ({ + async deployTroubleshootConsole(request) { + capturedRequest = request; + const result: DeployResult = { + bundleDir: '/tmp/bundle', + deployConfigPath: '/tmp/coveo.deploy.json', + deployed: true, + diagnostics: ['managed diagnostics line'], + hostedPageId: 'hp-managed-1', + hostedPageName: request.target.hostedPageName, + keyInfo: { + created: false, + reused: true, + source: 'managed', + }, + organizationId: request.target.organizationId, + runtimeConfigPath: '/tmp/runtime.js', + }; + + return result; + }, + })); + + const {error, stdout} = await captureOutput(() => CommerceTroubleshootDeploy.run([ + '--page-name', + 'commerce-troubleshoot-console', + '--rotate', + '--tracking-id', + 'storefront-main', + ])); + + expect(error).to.equal(undefined); + expect(capturedRequest).to.deep.equal({ + auth: { + accessToken: 'cfg-access-token', + }, + deploy: { + dryRun: false, + }, + keyStrategy: { + mode: 'managed', + rotate: true, + }, + runtimeDefaults: { + country: 'US', + currency: 'USD', + language: 'en', + trackingId: 'storefront-main', + viewUrl: 'https://www.example.com/', + }, + target: { + environment: 'prod', + hostedPageName: 'commerce-troubleshoot-console', + organizationId: 'cfg-org', + region: 'us', + }, + }); + expect(stdout).to.contain('Hosted page name: commerce-troubleshoot-console'); + expect(stdout).to.contain('Hosted page id: hp-managed-1'); + expect(stdout).to.contain('Execution: deploy executed'); + expect(stdout).to.contain('Key resolution: source=managed, created=no, reused=yes'); + expect(stdout).to.contain('managed diagnostics line'); + }); + + it('maps provided strategy and explicit flags before invoking service', async () => { + let capturedRequest: DeployTroubleshootRequest | undefined; + + commerceTroubleshootDeployTestHooks.setReadConfiguration(() => ({ + ...DefaultConfig, + accessToken: 'ignored-access-token', + environment: 'prod', + organization: 'ignored-org', + region: 'us', + }) as never); + + commerceTroubleshootDeployTestHooks.setLoadDeployerModule(async () => ({ + async deployTroubleshootConsole(request) { + capturedRequest = request; + const result: DeployResult = { + bundleDir: '/tmp/bundle', + deployConfigPath: '/tmp/coveo.deploy.json', + deployed: false, + diagnostics: ['dry-run only'], + hostedPageId: request.target.hostedPageId, + hostedPageName: request.target.hostedPageName, + keyInfo: { + created: false, + reused: false, + source: 'provided', + }, + organizationId: request.target.organizationId, + runtimeConfigPath: '/tmp/runtime.js', + }; + + return result; + }, + })); + + const {error, stdout} = await captureOutput(() => CommerceTroubleshootDeploy.run([ + '--page-name', + 'commerce-troubleshoot-console', + '--page-id', + 'hp-existing-id', + '--organization', + 'flag-org', + '--access-token', + 'flag-access-token', + '--region', + 'eu', + '--environment', + 'stg', + '--engine-token', + 'provided-engine-token', + '--cmh-token', + 'provided-cmh-token', + '--language', + 'fr', + '--country', + 'CA', + '--currency', + 'CAD', + '--view-url', + 'https://www.example.ca/plp', + '--dry-run', + ])); + + expect(error).to.equal(undefined); + expect(capturedRequest).to.deep.equal({ + auth: { + accessToken: 'flag-access-token', + }, + deploy: { + dryRun: true, + }, + keyStrategy: { + cmhAccessToken: 'provided-cmh-token', + engineAccessToken: 'provided-engine-token', + mode: 'provided', + }, + runtimeDefaults: { + country: 'CA', + currency: 'CAD', + language: 'fr', + viewUrl: 'https://www.example.ca/plp', + }, + target: { + environment: 'stg', + hostedPageId: 'hp-existing-id', + hostedPageName: 'commerce-troubleshoot-console', + organizationId: 'flag-org', + region: 'eu', + }, + }); + expect(stdout).to.contain('Hosted page name: commerce-troubleshoot-console'); + expect(stdout).to.contain('Hosted page id: hp-existing-id'); + expect(stdout).to.contain('Execution: dry-run (deploy skipped)'); + expect(stdout).to.contain('Key resolution: source=provided, created=no, reused=no'); + expect(stdout).to.contain('dry-run only'); + }); +}); From 0a203267b66b518ca35207a8a597a917475e3370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Allaire?= Date: Wed, 4 Mar 2026 16:36:11 -0500 Subject: [PATCH 2/3] =?UTF-8?q?Align=20release=20workflow=20with=20trusted?= =?UTF-8?q?=E2=80=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/onPushToMain.yml | 80 +++++++++++++++--------------- .github/workflows/onRelease.yml | 39 ++++++++++++--- .github/workflows/test.yml | 20 ++++++++ 3 files changed, 92 insertions(+), 47 deletions(-) diff --git a/.github/workflows/onPushToMain.yml b/.github/workflows/onPushToMain.yml index b6faa52..fdb40f2 100644 --- a/.github/workflows/onPushToMain.yml +++ b/.github/workflows/onPushToMain.yml @@ -1,56 +1,54 @@ -# test -name: version, tag and github release +name: Create Release From Package Version on: push: - branches: [main] + branches: + - main + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false jobs: release: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - - name: Check if version already exists - id: version-check + with: + node-version: 24 + + - name: Resolve release tag from package version + id: version + run: | + PACKAGE_VERSION=$(node -p "require('./package.json').version") + TAG="v${PACKAGE_VERSION}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + + - name: Check whether release already exists + id: release_check + env: + GH_TOKEN: ${{ github.token }} run: | - package_version=$(node -p "require('./package.json').version") - exists=$(gh api repos/${{ github.repository }}/releases/tags/v$package_version >/dev/null 2>&1 && echo "true" || echo "") - - if [ -n "$exists" ]; - then - echo "Version v$package_version already exists" - echo "::warning file=package.json,line=1::Version v$package_version already exists - no release will be created. If you want to create a new release, please update the version in package.json and push again." - echo "skipped=true" >> $GITHUB_OUTPUT + if gh release view "${{ steps.version.outputs.tag }}" --repo "${{ github.repository }}" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "Release ${{ steps.version.outputs.tag }} already exists; skipping." else - echo "Version v$package_version does not exist. Creating release..." - echo "skipped=false" >> $GITHUB_OUTPUT - echo "tag=v$package_version" >> $GITHUB_OUTPUT + echo "exists=false" >> "$GITHUB_OUTPUT" fi + + - name: Create GitHub release + if: steps.release_check.outputs.exists == 'false' env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - - name: Setup git - if: ${{ steps.version-check.outputs.skipped == 'false' }} + GH_TOKEN: ${{ github.token }} run: | - git config --global user.email ${{ secrets.GH_EMAIL }} - git config --global user.name ${{ secrets.GH_USERNAME }} - - name: Generate oclif README - if: ${{ steps.version-check.outputs.skipped == 'false' }} - id: oclif-readme - run: | - npm install - npm exec oclif readme - if [ -n "$(git status --porcelain)" ]; then - git add . - git commit -am "chore: update README.md" - git push -u origin ${{ github.ref_name }} - fi - - name: Create Github Release - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 - if: ${{ steps.version-check.outputs.skipped == 'false' }} - with: - name: ${{ steps.version-check.outputs.tag }} - tag: ${{ steps.version-check.outputs.tag }} - commit: ${{ github.ref_name }} - token: ${{ secrets.GH_TOKEN }} - skipIfReleaseExists: true + gh release create "${{ steps.version.outputs.tag }}" \ + --repo "${{ github.repository }}" \ + --target "${{ github.sha }}" \ + --title "${{ steps.version.outputs.tag }}" \ + --generate-notes diff --git a/.github/workflows/onRelease.yml b/.github/workflows/onRelease.yml index 16277a2..689c48f 100644 --- a/.github/workflows/onRelease.yml +++ b/.github/workflows/onRelease.yml @@ -2,17 +2,44 @@ name: publish on: release: - types: [released] + types: [published] + workflow_dispatch: + +permissions: + id-token: write + contents: read jobs: publish: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: latest - - run: npm install - - uses: JS-DevTools/npm-publish@19c28f1ef146469e409470805ea4279d47c3d35c - with: - token: ${{ secrets.NPM_TOKEN }} + node-version: 24 + registry-url: "https://registry.npmjs.org/" + cache: npm + + - name: Install dependencies + run: npm install + + - name: Build package + run: npm run build + + - name: Check npm for existing version + id: npm_check + run: | + PACKAGE_NAME=$(node -p "require('./package.json').name") + PACKAGE_VERSION=$(node -p "require('./package.json').version") + + if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version >/dev/null 2>&1; then + echo "should_publish=false" >> "$GITHUB_OUTPUT" + echo "Version ${PACKAGE_VERSION} already exists for ${PACKAGE_NAME}; skipping publish." + else + echo "should_publish=true" >> "$GITHUB_OUTPUT" + fi + + - name: Publish package (Trusted Publishing) + if: steps.npm_check.outputs.should_publish == 'true' + run: npm publish --provenance --access public diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d156cf..0533826 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,10 +1,30 @@ name: tests on: + pull_request: push: branches-ignore: [main] workflow_dispatch: jobs: + readme-sync-check: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + - run: npm ci + - run: npm run build + - run: npm exec oclif readme + - name: Validate README sync + run: | + if ! git diff --exit-code -- README.md; then + echo "::error file=README.md,line=1::README.md is out of sync with Oclif command docs. Run 'npm exec oclif readme' and commit the result." + exit 1 + fi + unit-tests: strategy: matrix: From 58be633844d5fb8bb903014c4d4a95614f8f40ae Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:51:55 -0500 Subject: [PATCH 3/3] Fix `as never` test casts and add `--page-name` whitespace normalization (#4) * Initial plan * Fix as never casts in tests and normalize pageName flag Co-authored-by: jfallaire <7849359+jfallaire@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jfallaire <7849359+jfallaire@users.noreply.github.com> --- src/commands/org/commerce/troubleshoot/deploy.ts | 7 ++++++- test/commands/org/commerce/troubleshoot/deploy.test.ts | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/commands/org/commerce/troubleshoot/deploy.ts b/src/commands/org/commerce/troubleshoot/deploy.ts index 01cbd69..9f9e4d0 100644 --- a/src/commands/org/commerce/troubleshoot/deploy.ts +++ b/src/commands/org/commerce/troubleshoot/deploy.ts @@ -189,6 +189,7 @@ export default class CommerceTroubleshootDeploy extends Command { const environment = readString(flags.environment) ?? readString(resolvedConfiguration.environment); const hostedPageId = readString(flags.pageId); const trackingId = readString(flags.trackingId); + const hostedPageName = readString(flags.pageName); if (!organizationId) { this.error('Missing organization ID. Provide --organization or set it with coveo config:set organization .'); @@ -198,6 +199,10 @@ export default class CommerceTroubleshootDeploy extends Command { this.error('Missing access token. Provide --access-token or set it with coveo config:set accessToken .'); } + if (!hostedPageName) { + this.error('Missing page name. Provide a non-empty value for --page-name.'); + } + const keyStrategy = this.resolveKeyStrategy(flags); const request: DeployTroubleshootRequest = { @@ -216,7 +221,7 @@ export default class CommerceTroubleshootDeploy extends Command { viewUrl: flags.viewUrl, }, target: { - hostedPageName: flags.pageName, + hostedPageName, organizationId, ...(hostedPageId ? {hostedPageId} : {}), ...(region ? {region} : {}), diff --git a/test/commands/org/commerce/troubleshoot/deploy.test.ts b/test/commands/org/commerce/troubleshoot/deploy.test.ts index 45ba565..27ee4bf 100644 --- a/test/commands/org/commerce/troubleshoot/deploy.test.ts +++ b/test/commands/org/commerce/troubleshoot/deploy.test.ts @@ -1,5 +1,6 @@ import { DefaultConfig, + type Configuration, } from '@coveo/cli-commons/config/config'; import {captureOutput} from '@oclif/test'; import {expect} from 'chai'; @@ -40,7 +41,7 @@ describe('org:commerce:troubleshoot:deploy', () => { environment: 'prod', organization: 'cfg-org', region: 'us', - }) as never); + }) as Configuration); commerceTroubleshootDeployTestHooks.setLoadDeployerModule(async () => ({ async deployTroubleshootConsole(request) { @@ -115,7 +116,7 @@ describe('org:commerce:troubleshoot:deploy', () => { environment: 'prod', organization: 'ignored-org', region: 'us', - }) as never); + }) as Configuration); commerceTroubleshootDeployTestHooks.setLoadDeployerModule(async () => ({ async deployTroubleshootConsole(request) {