Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified bin/dev.js
100644 → 100755
Empty file.
Empty file modified bin/run.js
100644 → 100755
Empty file.
35 changes: 35 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,41 @@
"flags": ["api-version", "body", "flags-dir", "json", "target-org", "title", "work-item-id", "work-item-name"],
"plugin": "@salesforce/plugin-devops-center"
},
{
"alias": [],
"command": "devops:stage:add-branch",
"flagAliases": [],
"flagChars": ["b", "o"],
"flags": [
"api-version",
"branch-name",
"create-vcs-branch",
"flags-dir",
"json",
"pipeline-id",
"stage-id",
"target-org"
],
"plugin": "@salesforce/plugin-devops-center"
},
{
"alias": [],
"command": "devops:stage:add-environment",
"flagAliases": [],
"flagChars": ["e", "o"],
"flags": [
"api-version",
"environment-name",
"flags-dir",
"json",
"no-browser",
"org-type",
"pipeline-id",
"stage-id",
"target-org"
],
"plugin": "@salesforce/plugin-devops-center"
},
{
"alias": [],
"command": "devops:work-item:create",
Expand Down
46 changes: 46 additions & 0 deletions messages/devops.stage.add-branch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# summary

Add a source code repository branch to a pipeline stage.

# description

By default, the branch must exist in the repository. Use --create-vcs-branch to create a branch if it doesn't exist.
Each pipeline stage supports only one branch. Adding a branch replaces any existing branch linked to the pipeline stage.

# flags.pipeline-id.summary

ID of the pipeline that contains the stage.

# flags.stage-id.summary

ID of the pipeline stage to associate the branch with.

# flags.branch-name.summary

Name of the repository branch to assign to the stage.

# flags.create-vcs-branch.summary

Create the branch in the remote repository if it doesn't already exist.

# examples

- Add an existing branch to a stage:

<%= config.bin %> <%= command.id %> --target-org my-devops-org --pipeline-id 0Xo000000000001 --stage-id 0Xp000000000001 --branch-name main

- Create and add a branch to a pipeline stage:

<%= config.bin %> <%= command.id %> --target-org my-devops-org --pipeline-id 0Xo000000000001 --stage-id 0Xp000000000002 --branch-name integration --create-vcs-branch

# error.StageNotFound

Pipeline stage "%s" doesn't exist in pipeline "%s". Check the stage ID and try again.

# error.NextStageNoBranch

You must set up a branch on stage "%s" before configuring stage "%s". Branches must be configured from right to left (starting from the last stage in the pipeline).

# error.BranchAttachFailed

Failed to associate branch with stage: %s
61 changes: 61 additions & 0 deletions messages/devops.stage.add-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# summary

Add a Salesforce environment to a pipeline stage.

# description

This command triggers an OAuth flow to authenticate the environment. A browser window opens automatically for you to log in.

# flags.pipeline-id.summary

ID of the pipeline that contains the stage.

# flags.stage-id.summary

ID of the pipeline stage.

# flags.environment-name.summary

Name of the environment.

# flags.org-type.summary

Type of the Salesforce org. Valid values: Production, Sandbox.

# flags.no-browser.summary

Don't auto-open the browser for OAuth authentication. The redirect URL is printed for manual use.

# examples

- Add a production environment to a stage using its ID:

<%= config.bin %> <%= command.id %> --target-org my-devops-org --stage-id 0Xp000000000001 --environment-name Production_Org --org-type Production

# info.BrowserOpened

A browser window has been opened for authentication. Log in to the target org to complete the setup.

# info.ManualAuth

Open the following URL in your browser to authenticate the environment:\n%s

# info.WaitingForAuth

Waiting for authentication to complete...

# info.Success

Successfully added environment to the stage.

# error.StageNotFound

Pipeline stage "%s" doesn't exist in pipeline "%s". Check the stage ID and try again.

# error.EnvironmentAttachFailed

Failed to create environment for stage: %s

# error.AuthTimeout

Authentication timed out. The environment was created but not yet authenticated. Re-run the command or authenticate manually via the org's DevOps Center setup.
34 changes: 34 additions & 0 deletions schemas/devops-stage-add__branch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/AddStageBranchResult",
"definitions": {
"AddStageBranchResult": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"stageId": {
"type": "string"
},
"branchName": {
"type": "string"
},
"branchCreated": {
"type": "boolean"
},
"repoBranchId": {
"type": "string"
},
"pipelineId": {
"type": "string"
},
"error": {
"type": "string"
}
},
"required": ["success", "stageId"],
"additionalProperties": false
}
}
}
47 changes: 47 additions & 0 deletions schemas/devops-stage-add__environment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/AddStageEnvironmentResult",
"definitions": {
"AddStageEnvironmentResult": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"stageId": {
"type": "string"
},
"environmentId": {
"type": "string"
},
"environmentName": {
"type": "string"
},
"orgType": {
"$ref": "#/definitions/OrgType"
},
"pipelineId": {
"type": "string"
},
"redirectUrl": {
"type": "string"
},
"namedCredential": {
"type": "string"
},
"organizationId": {
"type": "string"
},
"error": {
"type": "string"
}
},
"required": ["success", "stageId"],
"additionalProperties": false
},
"OrgType": {
"type": "string",
"enum": ["Production", "Sandbox"]
}
}
}
118 changes: 118 additions & 0 deletions src/commands/devops/stage/add-branch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 2026, Salesforce, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Messages, Org } from '@salesforce/core';
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { addStageBranch, AddStageBranchResult } from '../../../utils/addStageBranch.js';
import { fetchPipelineStages } from '../../../utils/pipelineUtils.js';
import { PipelineStageRecord } from '../../../utils/types.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-devops-center', 'devops.stage.add-branch');
const commonErrorMessages = Messages.loadMessages('@salesforce/plugin-devops-center', 'commonErrors');

export default class DevopsStageAddBranch extends SfCommand<AddStageBranchResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');

public static readonly flags = {
'target-org': Flags.requiredOrg(),
'api-version': Flags.orgApiVersion(),
'pipeline-id': Flags.salesforceId({
summary: messages.getMessage('flags.pipeline-id.summary'),
required: true,
char: undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove char since there is no define short flag. Same with 'stage-id'

}),
'stage-id': Flags.salesforceId({
summary: messages.getMessage('flags.stage-id.summary'),
required: true,
char: undefined,
}),
'branch-name': Flags.string({
summary: messages.getMessage('flags.branch-name.summary'),
required: true,
char: 'b',
}),
'create-vcs-branch': Flags.boolean({
summary: messages.getMessage('flags.create-vcs-branch.summary'),
default: false,
}),
};

public async run(): Promise<AddStageBranchResult> {
const { flags } = await this.parse(DevopsStageAddBranch);
const org: Org = flags['target-org'];
const connection = org.getConnection(flags['api-version']);
const pipelineId = flags['pipeline-id'];
const stageId = flags['stage-id'];

let stages: PipelineStageRecord[];
try {
stages = await fetchPipelineStages(connection, pipelineId);
} catch (error: unknown) {
const errMsg = error instanceof Error ? error.message : String(error);
if (errMsg.includes('sObject type') && errMsg.includes('is not supported')) {
this.error(commonErrorMessages.getMessage('error.DevopsCenterNotEnabled'));
}
throw error;
}

const targetStage = stages.find((s) => s.Id === stageId);
if (!targetStage) {
this.error(messages.getMessage('error.StageNotFound', [stageId, pipelineId]));
}

// Enforce right-to-left branch setup order: the next stage (to the right)
// must already have a branch before this stage can be configured.
if (targetStage.NextStageId) {
const nextStage = stages.find((s) => s.Id === targetStage.NextStageId);
if (nextStage && !nextStage.SourceCodeRepositoryBranch?.Name) {
this.error(messages.getMessage('error.NextStageNoBranch', [nextStage.Name ?? nextStage.Id, stageId]));
}
}

let result: AddStageBranchResult;
try {
result = await addStageBranch({
connection,
pipelineId,
stageId,
branchName: flags['branch-name'],
createVcsBranch: flags['create-vcs-branch'],
});
} catch (error: unknown) {
const errMsg = error instanceof Error ? error.message : String(error);
if (errMsg.includes('sObject type') && errMsg.includes('is not supported')) {
this.error(commonErrorMessages.getMessage('error.DevopsCenterNotEnabled'));
}
throw error;
}

if (result.success) {
const action = result.branchCreated ? 'Created branch and associated it' : 'Successfully associated branch';
this.log(`${action} with the stage.`);
this.log(` Stage ID: ${stageId}`);
this.log(` Branch: ${result.branchName ?? ''}${result.branchCreated ? ' (newly created)' : ''}`);
this.log(` Repo Branch ID: ${result.repoBranchId ?? ''}`);
this.log(` Pipeline ID: ${pipelineId}`);
Comment thread
ad-shreya marked this conversation as resolved.
} else {
this.error(messages.getMessage('error.BranchAttachFailed', [result.error ?? '']));
}

return result;
}
}
Loading
Loading