@salesforce/plugin-devops-center 1.2.28 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +184 -89
- package/bin/dev.js +0 -0
- package/bin/run.js +0 -0
- package/lib/commands/devops/pipeline/activate.js +5 -0
- package/lib/commands/devops/pipeline/activate.js.map +1 -1
- package/lib/commands/devops/pipeline/create.d.ts +2 -1
- package/lib/commands/devops/pipeline/create.js +37 -7
- package/lib/commands/devops/pipeline/create.js.map +1 -1
- package/lib/commands/devops/pipeline/{attach-project.d.ts → project/add.d.ts} +2 -2
- package/lib/commands/devops/pipeline/{attach-project.js → project/add.js} +5 -5
- package/lib/commands/devops/pipeline/project/add.js.map +1 -0
- package/lib/commands/devops/pull-request/create.d.ts +0 -2
- package/lib/commands/devops/pull-request/create.js +8 -40
- package/lib/commands/devops/pull-request/create.js.map +1 -1
- package/lib/commands/devops/stage/branch/add.d.ts +17 -0
- package/lib/commands/devops/stage/branch/add.js +110 -0
- package/lib/commands/devops/stage/branch/add.js.map +1 -0
- package/lib/commands/devops/stage/environment/add.d.ts +18 -0
- package/lib/commands/devops/stage/environment/add.js +141 -0
- package/lib/commands/devops/stage/environment/add.js.map +1 -0
- package/lib/commands/devops/work-item/{status/update.d.ts → update.d.ts} +6 -4
- package/lib/commands/devops/work-item/{status/update.js → update.js} +30 -9
- package/lib/commands/devops/work-item/update.js.map +1 -0
- package/lib/utils/addStageBranch.d.ts +22 -0
- package/lib/utils/addStageBranch.js +48 -0
- package/lib/utils/addStageBranch.js.map +1 -0
- package/lib/utils/addStageEnvironment.d.ts +92 -0
- package/lib/utils/addStageEnvironment.js +120 -0
- package/lib/utils/addStageEnvironment.js.map +1 -0
- package/lib/utils/createPipeline.d.ts +14 -1
- package/lib/utils/createPipeline.js +52 -6
- package/lib/utils/createPipeline.js.map +1 -1
- package/lib/utils/createProject.js +1 -0
- package/lib/utils/createProject.js.map +1 -1
- package/lib/utils/createPullRequest.d.ts +7 -19
- package/lib/utils/createPullRequest.js +49 -121
- package/lib/utils/createPullRequest.js.map +1 -1
- package/lib/utils/{updateWorkItemStatus.d.ts → updateWorkItem.d.ts} +12 -14
- package/lib/utils/{updateWorkItemStatus.js → updateWorkItem.js} +28 -26
- package/lib/utils/updateWorkItem.js.map +1 -0
- package/messages/devops.pipeline.create.md +26 -6
- package/messages/devops.pull-request.create.md +6 -30
- package/messages/devops.stage.branch.add.md +46 -0
- package/messages/devops.stage.environment.add.md +65 -0
- package/messages/devops.work-item.update.md +45 -0
- package/npm-shrinkwrap.json +2 -2
- package/oclif.manifest.json +470 -267
- package/package.json +3 -3
- package/schemas/devops-pull__request-create.json +0 -3
- package/schemas/devops-stage-branch-add.json +34 -0
- package/schemas/devops-stage-environment-add.json +47 -0
- package/schemas/{devops-work__item-status-update.json → devops-work__item-update.json} +8 -2
- package/lib/commands/devops/pipeline/attach-project.js.map +0 -1
- package/lib/commands/devops/work-item/status/update.js.map +0 -1
- package/lib/utils/updateWorkItemStatus.js.map +0 -1
- package/messages/devops.work-item.status.update.md +0 -29
- /package/messages/{devops.pipeline.attach-project.md → devops.pipeline.project.add.md} +0 -0
- /package/schemas/{devops-pipeline-attach__project.json → devops-pipeline-project-add.json} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Connection } from '@salesforce/core';
|
|
2
|
+
export type OrgType = 'Production' | 'Sandbox';
|
|
3
|
+
export declare const POLL_INTERVAL_MS = 3000;
|
|
4
|
+
export declare const POLL_TIMEOUT_MS: number;
|
|
5
|
+
export type AddStageEnvironmentParams = {
|
|
6
|
+
connection: Connection;
|
|
7
|
+
pipelineId: string;
|
|
8
|
+
stageId: string;
|
|
9
|
+
environmentName: string;
|
|
10
|
+
orgType: OrgType;
|
|
11
|
+
};
|
|
12
|
+
export type AddStageEnvironmentResult = {
|
|
13
|
+
success: boolean;
|
|
14
|
+
stageId: string;
|
|
15
|
+
environmentId?: string;
|
|
16
|
+
environmentName?: string;
|
|
17
|
+
orgType?: OrgType;
|
|
18
|
+
pipelineId?: string;
|
|
19
|
+
redirectUrl?: string;
|
|
20
|
+
namedCredential?: string;
|
|
21
|
+
organizationId?: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
};
|
|
24
|
+
type EnvironmentCreateResponse = {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
redirectUrl: string;
|
|
28
|
+
namedCredential: string;
|
|
29
|
+
externalCredential: string;
|
|
30
|
+
pipelineId?: string;
|
|
31
|
+
};
|
|
32
|
+
export type EnvironmentGetResponse = {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
organizationId?: string;
|
|
36
|
+
orgType?: string;
|
|
37
|
+
namedCredential?: string;
|
|
38
|
+
redirectUrl?: string;
|
|
39
|
+
};
|
|
40
|
+
type EnvironmentValidateResponse = {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
organizationId?: string;
|
|
44
|
+
orgType?: string;
|
|
45
|
+
namedCredential?: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Creates an environment and associates it with a pipeline stage via the Connect API.
|
|
49
|
+
* POST /services/data/v{version}/connect/devops/environment
|
|
50
|
+
*/
|
|
51
|
+
export declare function createEnvironment(connection: Connection, params: {
|
|
52
|
+
pipelineId: string;
|
|
53
|
+
stageId: string;
|
|
54
|
+
environmentName: string;
|
|
55
|
+
orgType: OrgType;
|
|
56
|
+
}): Promise<EnvironmentCreateResponse>;
|
|
57
|
+
/**
|
|
58
|
+
* Fetches the current state of an environment.
|
|
59
|
+
* GET /services/data/v{version}/connect/devops/environment/{environmentId}
|
|
60
|
+
*/
|
|
61
|
+
export declare function getEnvironment(connection: Connection, environmentId: string): Promise<EnvironmentGetResponse>;
|
|
62
|
+
/**
|
|
63
|
+
* Validates an authenticated environment by triggering server-side org extraction.
|
|
64
|
+
* PATCH /services/data/v{version}/connect/devops/environment/{environmentId}
|
|
65
|
+
*/
|
|
66
|
+
export declare function validateEnvironment(connection: Connection, environmentId: string): Promise<EnvironmentValidateResponse>;
|
|
67
|
+
/**
|
|
68
|
+
* Polls until authentication is complete by attempting PATCH validation.
|
|
69
|
+
* The PATCH call triggers server-side org extraction; if the user has
|
|
70
|
+
* completed OAuth, it returns a response with organizationId populated.
|
|
71
|
+
* If auth isn't done yet, the PATCH throws — we catch and retry.
|
|
72
|
+
* Falls back to GET polling as a secondary check.
|
|
73
|
+
* Throws on timeout.
|
|
74
|
+
*/
|
|
75
|
+
export declare function pollForAuthentication(connection: Connection, environmentId: string, timeoutMs?: number, intervalMs?: number): Promise<EnvironmentValidateResponse>;
|
|
76
|
+
/**
|
|
77
|
+
* Full orchestration: create environment -> poll for auth -> validate.
|
|
78
|
+
* Returns the final result including organizationId.
|
|
79
|
+
*
|
|
80
|
+
* The caller is responsible for opening the browser at the returned redirectUrl
|
|
81
|
+
* and displaying progress to the user. Use `onCreated` to receive the redirect URL
|
|
82
|
+
* before polling begins.
|
|
83
|
+
*/
|
|
84
|
+
export declare function addStageEnvironment(params: AddStageEnvironmentParams & {
|
|
85
|
+
onCreated?: (data: {
|
|
86
|
+
environmentId: string;
|
|
87
|
+
redirectUrl: string;
|
|
88
|
+
}) => void;
|
|
89
|
+
pollTimeoutMs?: number;
|
|
90
|
+
pollIntervalMs?: number;
|
|
91
|
+
}): Promise<AddStageEnvironmentResult>;
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, Salesforce, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
const ORG_TYPE_API_MAP = {
|
|
17
|
+
Production: 'PRODUCTION',
|
|
18
|
+
Sandbox: 'SANDBOX',
|
|
19
|
+
};
|
|
20
|
+
export const POLL_INTERVAL_MS = 3000;
|
|
21
|
+
export const POLL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Creates an environment and associates it with a pipeline stage via the Connect API.
|
|
24
|
+
* POST /services/data/v{version}/connect/devops/environment
|
|
25
|
+
*/
|
|
26
|
+
export async function createEnvironment(connection, params) {
|
|
27
|
+
const path = `/services/data/v${connection.getApiVersion()}/connect/devops/environment`;
|
|
28
|
+
return connection.request({
|
|
29
|
+
method: 'POST',
|
|
30
|
+
url: path,
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
envName: params.environmentName,
|
|
33
|
+
orgType: ORG_TYPE_API_MAP[params.orgType],
|
|
34
|
+
pipelineStageId: params.stageId,
|
|
35
|
+
pipelineId: params.pipelineId,
|
|
36
|
+
}),
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Fetches the current state of an environment.
|
|
42
|
+
* GET /services/data/v{version}/connect/devops/environment/{environmentId}
|
|
43
|
+
*/
|
|
44
|
+
export async function getEnvironment(connection, environmentId) {
|
|
45
|
+
const path = `/services/data/v${connection.getApiVersion()}/connect/devops/environment/${environmentId}`;
|
|
46
|
+
return connection.request({ method: 'GET', url: path });
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validates an authenticated environment by triggering server-side org extraction.
|
|
50
|
+
* PATCH /services/data/v{version}/connect/devops/environment/{environmentId}
|
|
51
|
+
*/
|
|
52
|
+
export async function validateEnvironment(connection, environmentId) {
|
|
53
|
+
const path = `/services/data/v${connection.getApiVersion()}/connect/devops/environment/${environmentId}`;
|
|
54
|
+
return connection.request({
|
|
55
|
+
method: 'PATCH',
|
|
56
|
+
url: path,
|
|
57
|
+
body: JSON.stringify({}),
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function sleep(ms) {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
setTimeout(resolve, ms);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Polls until authentication is complete by attempting PATCH validation.
|
|
68
|
+
* The PATCH call triggers server-side org extraction; if the user has
|
|
69
|
+
* completed OAuth, it returns a response with organizationId populated.
|
|
70
|
+
* If auth isn't done yet, the PATCH throws — we catch and retry.
|
|
71
|
+
* Falls back to GET polling as a secondary check.
|
|
72
|
+
* Throws on timeout.
|
|
73
|
+
*/
|
|
74
|
+
// eslint-disable-next-line no-await-in-loop -- polling requires sequential awaits by design
|
|
75
|
+
export async function pollForAuthentication(connection, environmentId, timeoutMs = POLL_TIMEOUT_MS, intervalMs = POLL_INTERVAL_MS) {
|
|
76
|
+
const deadline = Date.now() + timeoutMs;
|
|
77
|
+
while (Date.now() < deadline) {
|
|
78
|
+
try {
|
|
79
|
+
// eslint-disable-next-line no-await-in-loop
|
|
80
|
+
const validated = await validateEnvironment(connection, environmentId);
|
|
81
|
+
if (validated.organizationId) {
|
|
82
|
+
return validated;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// PATCH failed — auth likely not yet complete, continue polling
|
|
87
|
+
}
|
|
88
|
+
// eslint-disable-next-line no-await-in-loop
|
|
89
|
+
await sleep(intervalMs);
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`Authentication timed out after ${timeoutMs / 1000} seconds. Re-run the command or authenticate manually.`);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Full orchestration: create environment -> poll for auth -> validate.
|
|
95
|
+
* Returns the final result including organizationId.
|
|
96
|
+
*
|
|
97
|
+
* The caller is responsible for opening the browser at the returned redirectUrl
|
|
98
|
+
* and displaying progress to the user. Use `onCreated` to receive the redirect URL
|
|
99
|
+
* before polling begins.
|
|
100
|
+
*/
|
|
101
|
+
export async function addStageEnvironment(params) {
|
|
102
|
+
const { connection, pipelineId, stageId, environmentName, orgType, onCreated, pollTimeoutMs, pollIntervalMs } = params;
|
|
103
|
+
const createData = await createEnvironment(connection, { pipelineId, stageId, environmentName, orgType });
|
|
104
|
+
if (onCreated) {
|
|
105
|
+
onCreated({ environmentId: createData.id, redirectUrl: createData.redirectUrl });
|
|
106
|
+
}
|
|
107
|
+
const validated = await pollForAuthentication(connection, createData.id, pollTimeoutMs, pollIntervalMs);
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
stageId,
|
|
111
|
+
environmentId: createData.id,
|
|
112
|
+
environmentName: createData.name,
|
|
113
|
+
orgType,
|
|
114
|
+
pipelineId,
|
|
115
|
+
redirectUrl: createData.redirectUrl,
|
|
116
|
+
namedCredential: createData.namedCredential ?? validated.namedCredential,
|
|
117
|
+
organizationId: validated.organizationId,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=addStageEnvironment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addStageEnvironment.js","sourceRoot":"","sources":["../../src/utils/addStageEnvironment.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,7 +12,8 @@ export type CreatePipelineParams = {
|
|
|
12
12
|
repoType: string;
|
|
13
13
|
createRepo?: boolean;
|
|
14
14
|
repoOwner?: string;
|
|
15
|
-
|
|
15
|
+
bitbucketWorkspace?: string;
|
|
16
|
+
bitbucketProjectKey?: string;
|
|
16
17
|
};
|
|
17
18
|
export type CreatePipelineResult = {
|
|
18
19
|
success: boolean;
|
|
@@ -27,6 +28,18 @@ export type CreatePipelineResult = {
|
|
|
27
28
|
* Detects repo type from a URL. Returns 'github' or 'bitbucket', or undefined.
|
|
28
29
|
*/
|
|
29
30
|
export declare function detectRepoType(repoUrl: string): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Validates that a GitHub owner (org or user) exists.
|
|
33
|
+
* Primary: uses `gh api` which handles keyring-based auth automatically.
|
|
34
|
+
* Fallback: direct GitHub API call with token (for environments without gh CLI).
|
|
35
|
+
* Skips validation silently when neither mechanism can authenticate, so we
|
|
36
|
+
* don't block the command when the check itself is unavailable.
|
|
37
|
+
*/
|
|
38
|
+
export declare function validateGitHubOwner(owner: string, token?: string, fetchFn?: typeof fetch): Promise<void>;
|
|
39
|
+
export declare class GitHubOwnerNotFoundError extends Error {
|
|
40
|
+
readonly owner: string;
|
|
41
|
+
constructor(owner: string);
|
|
42
|
+
}
|
|
30
43
|
/**
|
|
31
44
|
* Creates a new DevOps Center pipeline via the Connect API.
|
|
32
45
|
* POST /services/data/v{version}/connect/devops/pipelines
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import { execSync } from 'node:child_process';
|
|
16
17
|
const DEFAULT_STAGES = [{ name: 'Integration' }, { name: 'UAT' }, { name: 'Staging' }, { name: 'Production' }];
|
|
17
18
|
/**
|
|
18
19
|
* Detects repo type from a URL. Returns 'github' or 'bitbucket', or undefined.
|
|
@@ -25,12 +26,55 @@ export function detectRepoType(repoUrl) {
|
|
|
25
26
|
return 'bitbucket';
|
|
26
27
|
return undefined;
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Validates that a GitHub owner (org or user) exists.
|
|
31
|
+
* Primary: uses `gh api` which handles keyring-based auth automatically.
|
|
32
|
+
* Fallback: direct GitHub API call with token (for environments without gh CLI).
|
|
33
|
+
* Skips validation silently when neither mechanism can authenticate, so we
|
|
34
|
+
* don't block the command when the check itself is unavailable.
|
|
35
|
+
*/
|
|
36
|
+
export async function validateGitHubOwner(owner, token, fetchFn = fetch) {
|
|
37
|
+
try {
|
|
38
|
+
execSync(`gh api users/${encodeURIComponent(owner)}`, {
|
|
39
|
+
encoding: 'utf-8',
|
|
40
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
const stderr = String(e.stderr ?? '');
|
|
46
|
+
const stdout = String(e.stdout ?? '');
|
|
47
|
+
if (stderr.includes('404') || stdout.includes('"Not Found"')) {
|
|
48
|
+
throw new GitHubOwnerNotFoundError(owner);
|
|
49
|
+
}
|
|
50
|
+
// gh not available or not authenticated — fall through to token-based check
|
|
51
|
+
}
|
|
52
|
+
if (!token)
|
|
53
|
+
return;
|
|
54
|
+
const response = await fetchFn(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
|
|
55
|
+
headers: {
|
|
56
|
+
Accept: 'application/vnd.github+json',
|
|
57
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
58
|
+
Authorization: `Bearer ${token}`,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
if (response.status === 404)
|
|
62
|
+
throw new GitHubOwnerNotFoundError(owner);
|
|
63
|
+
// 403 or other non-404: skip validation
|
|
64
|
+
}
|
|
65
|
+
export class GitHubOwnerNotFoundError extends Error {
|
|
66
|
+
owner;
|
|
67
|
+
constructor(owner) {
|
|
68
|
+
super(`GitHub owner "${owner}" does not exist`);
|
|
69
|
+
this.owner = owner;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
28
72
|
/**
|
|
29
73
|
* Creates a new DevOps Center pipeline via the Connect API.
|
|
30
74
|
* POST /services/data/v{version}/connect/devops/pipelines
|
|
31
75
|
*/
|
|
32
76
|
export async function createPipeline(params) {
|
|
33
|
-
const { connection, name, description, repo, repoType, createRepo, repoOwner,
|
|
77
|
+
const { connection, name, description, repo, repoType, createRepo, repoOwner, bitbucketWorkspace, bitbucketProjectKey, } = params;
|
|
34
78
|
const path = `/services/data/v${connection.getApiVersion()}/connect/devops/pipelines`;
|
|
35
79
|
const payload = {
|
|
36
80
|
name,
|
|
@@ -40,14 +84,16 @@ export async function createPipeline(params) {
|
|
|
40
84
|
if (createRepo) {
|
|
41
85
|
payload.createVcsRepo = true;
|
|
42
86
|
payload.vcsRepoName = repo;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
providerInfo.bitbucketProject = bitbucketProject;
|
|
87
|
+
if (repoType === 'bitbucket') {
|
|
88
|
+
const providerInfo = { bitbucketWorkspace: bitbucketWorkspace };
|
|
89
|
+
if (bitbucketProjectKey) {
|
|
90
|
+
providerInfo.bitbucketProjectKey = bitbucketProjectKey;
|
|
48
91
|
}
|
|
49
92
|
payload.repoProviderInfo = JSON.stringify(providerInfo);
|
|
50
93
|
}
|
|
94
|
+
else {
|
|
95
|
+
payload.vcsRepoOwner = repoOwner;
|
|
96
|
+
}
|
|
51
97
|
}
|
|
52
98
|
else {
|
|
53
99
|
payload.vcsRepoUrl = repo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createPipeline.js","sourceRoot":"","sources":["../../src/utils/createPipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"createPipeline.js","sourceRoot":"","sources":["../../src/utils/createPipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createProject.js","sourceRoot":"","sources":["../../src/utils/createProject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAkBH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAA2B;IAC7D,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAEjD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC;QAC9D,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,WAAW,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"createProject.js","sourceRoot":"","sources":["../../src/utils/createProject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAkBH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAA2B;IAC7D,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAEjD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC;QAC9D,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,WAAW,IAAI,IAAI;QAChC,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO;YACL,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,MAAM,CAAC,EAAE;YACpB,IAAI;YACJ,WAAW,EAAE,WAAW,IAAI,SAAS;SACtC,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5G,OAAO;QACL,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,aAAa,IAAI,eAAe;KACxC,CAAC;AACJ,CAAC"}
|
|
@@ -6,32 +6,18 @@ export type WorkItemDetail = {
|
|
|
6
6
|
branchName?: string;
|
|
7
7
|
targetBranch?: string;
|
|
8
8
|
projectId: string;
|
|
9
|
-
repoOwner?: string;
|
|
10
|
-
repoName?: string;
|
|
11
9
|
provider?: string;
|
|
12
10
|
};
|
|
13
|
-
export type CreatePullRequestParams = {
|
|
14
|
-
owner: string;
|
|
15
|
-
repo: string;
|
|
16
|
-
head: string;
|
|
17
|
-
base: string;
|
|
18
|
-
title: string;
|
|
19
|
-
body?: string;
|
|
20
|
-
provider: string;
|
|
21
|
-
token: string;
|
|
22
|
-
};
|
|
23
11
|
export type CreatePullRequestResult = {
|
|
24
12
|
success: boolean;
|
|
25
13
|
workItemName?: string;
|
|
26
|
-
title?: string;
|
|
27
14
|
url?: string;
|
|
28
15
|
sourceBranch?: string;
|
|
29
16
|
targetBranch?: string;
|
|
30
17
|
error?: string;
|
|
31
18
|
};
|
|
32
19
|
/**
|
|
33
|
-
* Queries a work item (by Name or Id) and resolves
|
|
34
|
-
* to create a pull request: branch, repo, owner, provider, and target branch.
|
|
20
|
+
* Queries a work item (by Name or Id) and resolves info needed to create a pull request.
|
|
35
21
|
*/
|
|
36
22
|
export declare function fetchWorkItemDetail(connection: Connection, filter: {
|
|
37
23
|
name: string;
|
|
@@ -39,11 +25,13 @@ export declare function fetchWorkItemDetail(connection: Connection, filter: {
|
|
|
39
25
|
id: string;
|
|
40
26
|
}): Promise<WorkItemDetail>;
|
|
41
27
|
/**
|
|
42
|
-
* Creates a pull request
|
|
28
|
+
* Creates a pull request via the Salesforce Connect API.
|
|
29
|
+
* POST /connect/devops/workItems/{workItemId}/review
|
|
30
|
+
* Salesforce handles VCS authentication and workspace resolution.
|
|
43
31
|
*/
|
|
44
|
-
export declare function createPullRequest(
|
|
32
|
+
export declare function createPullRequest(connection: Connection, workItemId: string): Promise<CreatePullRequestResult>;
|
|
45
33
|
/**
|
|
46
|
-
* Attempts to resolve a
|
|
47
|
-
*
|
|
34
|
+
* Attempts to resolve a GitHub authentication token from the environment or the gh CLI.
|
|
35
|
+
* Used by pipeline create for pre-flight owner validation.
|
|
48
36
|
*/
|
|
49
37
|
export declare function resolveGitHubToken(): Promise<string | undefined>;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import { execSync } from 'node:child_process';
|
|
16
17
|
import { getPipelineIdForProject, fetchPipelineStages, computeFirstStageId, findStageById, getBranchNameFromStage, resolveTargetStageId, } from './pipelineUtils.js';
|
|
17
18
|
function normalizeProvider(provider) {
|
|
18
19
|
if (!provider)
|
|
@@ -22,52 +23,14 @@ function normalizeProvider(provider) {
|
|
|
22
23
|
return 'bitbucket';
|
|
23
24
|
return normalized;
|
|
24
25
|
}
|
|
25
|
-
function providerToVcsType(provider) {
|
|
26
|
-
const normalized = normalizeProvider(provider);
|
|
27
|
-
if (normalized === 'github')
|
|
28
|
-
return 'GITHUB';
|
|
29
|
-
if (normalized === 'bitbucket')
|
|
30
|
-
return 'BITBUCKET';
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
function extractOwnerFromVcsPayload(payload) {
|
|
34
|
-
if (!payload)
|
|
35
|
-
return undefined;
|
|
36
|
-
if (typeof payload === 'string')
|
|
37
|
-
return payload.trim() || undefined;
|
|
38
|
-
const obj = payload;
|
|
39
|
-
if (typeof obj.owner === 'string' && obj.owner.trim())
|
|
40
|
-
return obj.owner.trim();
|
|
41
|
-
const list = (obj.owners ?? obj.items ?? obj.records);
|
|
42
|
-
if (Array.isArray(list)) {
|
|
43
|
-
for (const candidate of list) {
|
|
44
|
-
if (typeof candidate === 'string' && candidate.trim())
|
|
45
|
-
return candidate.trim();
|
|
46
|
-
if (candidate && typeof candidate === 'object') {
|
|
47
|
-
const ownerVal = candidate.owner;
|
|
48
|
-
if (typeof ownerVal === 'string' && ownerVal.trim())
|
|
49
|
-
return ownerVal.trim();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
async function fetchOwnerByVcsType(connection, vcsType) {
|
|
56
|
-
const path = `/services/data/v${connection.getApiVersion()}/connect/devops/vcs/${vcsType}`;
|
|
57
|
-
const response = await connection.request({ method: 'GET', url: path });
|
|
58
|
-
return extractOwnerFromVcsPayload(response);
|
|
59
|
-
}
|
|
60
26
|
/**
|
|
61
|
-
* Queries a work item (by Name or Id) and resolves
|
|
62
|
-
* to create a pull request: branch, repo, owner, provider, and target branch.
|
|
27
|
+
* Queries a work item (by Name or Id) and resolves info needed to create a pull request.
|
|
63
28
|
*/
|
|
64
29
|
export async function fetchWorkItemDetail(connection, filter) {
|
|
65
30
|
const whereClause = 'name' in filter ? `Name = '${filter.name}'` : `Id = '${filter.id}'`;
|
|
66
31
|
const identifier = 'name' in filter ? filter.name : filter.id;
|
|
67
32
|
const result = await connection.query(`SELECT Id, Name, Subject, DevopsProjectId, DevopsPipelineStageId,
|
|
68
33
|
SourceCodeRepositoryBranch.Name,
|
|
69
|
-
SourceCodeRepositoryBranch.SourceCodeRepository.Name,
|
|
70
|
-
SourceCodeRepositoryBranch.SourceCodeRepository.RepositoryOwner,
|
|
71
34
|
SourceCodeRepositoryBranch.SourceCodeRepository.Provider
|
|
72
35
|
FROM WorkItem
|
|
73
36
|
WHERE ${whereClause}
|
|
@@ -77,24 +40,12 @@ export async function fetchWorkItemDetail(connection, filter) {
|
|
|
77
40
|
throw new Error(`Work item '${identifier}' not found.`);
|
|
78
41
|
}
|
|
79
42
|
const provider = normalizeProvider(record.SourceCodeRepositoryBranch?.SourceCodeRepository?.Provider);
|
|
80
|
-
let repoOwner = record.SourceCodeRepositoryBranch?.SourceCodeRepository?.RepositoryOwner ?? undefined;
|
|
81
|
-
const vcsType = providerToVcsType(provider);
|
|
82
|
-
if (vcsType && !repoOwner) {
|
|
83
|
-
try {
|
|
84
|
-
repoOwner = (await fetchOwnerByVcsType(connection, vcsType)) ?? undefined;
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// owner lookup is best-effort
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
43
|
const detail = {
|
|
91
44
|
workItemId: record.Id,
|
|
92
45
|
workItemName: record.Name,
|
|
93
46
|
subject: record.Subject ?? '',
|
|
94
47
|
branchName: record.SourceCodeRepositoryBranch?.Name ?? undefined,
|
|
95
48
|
projectId: record.DevopsProjectId,
|
|
96
|
-
repoOwner,
|
|
97
|
-
repoName: record.SourceCodeRepositoryBranch?.SourceCodeRepository?.Name ?? undefined,
|
|
98
49
|
provider,
|
|
99
50
|
};
|
|
100
51
|
const pipelineId = await getPipelineIdForProject(connection, record.DevopsProjectId);
|
|
@@ -110,95 +61,72 @@ export async function fetchWorkItemDetail(connection, filter) {
|
|
|
110
61
|
}
|
|
111
62
|
return detail;
|
|
112
63
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
64
|
+
function extractPrError(response) {
|
|
65
|
+
const raw = String(response.errorMessage ?? response.error ?? response.message ?? 'Pull request creation failed');
|
|
66
|
+
// Try to extract inner VCS error from "REVIEW_CREATION_FAILED:Failed to create pull request: ..."
|
|
67
|
+
const innerMatch = raw.match(/Failed to create pull request:\s*(.+)/s);
|
|
68
|
+
const inner = innerMatch ? innerMatch[1].trim() : raw;
|
|
69
|
+
// Try to parse JSON error body from named credential callout responses
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(inner);
|
|
72
|
+
const msg = parsed?.error?.message ?? parsed?.message;
|
|
73
|
+
if (msg)
|
|
74
|
+
return String(msg);
|
|
120
75
|
}
|
|
121
|
-
|
|
122
|
-
|
|
76
|
+
catch {
|
|
77
|
+
// not JSON, use as-is
|
|
123
78
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
async function createGitHubPullRequest(params) {
|
|
127
|
-
const { owner, repo, head, base, title, body, token } = params;
|
|
128
|
-
const url = `https://api.github.com/repos/${owner}/${repo}/pulls`;
|
|
129
|
-
const response = await fetch(url, {
|
|
130
|
-
method: 'POST',
|
|
131
|
-
headers: {
|
|
132
|
-
Authorization: `Bearer ${token}`,
|
|
133
|
-
Accept: 'application/vnd.github+json',
|
|
134
|
-
'Content-Type': 'application/json',
|
|
135
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
136
|
-
},
|
|
137
|
-
body: JSON.stringify({ title, body: body ?? '', head, base }),
|
|
138
|
-
});
|
|
139
|
-
const data = (await response.json());
|
|
140
|
-
if (!response.ok) {
|
|
141
|
-
const errors = Array.isArray(data.errors)
|
|
142
|
-
? data.errors.map((e) => e.message ?? JSON.stringify(e)).join('; ')
|
|
143
|
-
: undefined;
|
|
144
|
-
const msg = data.message ?? `HTTP ${response.status}`;
|
|
145
|
-
throw new Error(errors ? `${msg}: ${errors}` : msg);
|
|
79
|
+
if (inner.includes('no changes')) {
|
|
80
|
+
return 'The branch has no commits ahead of the target branch. Push your changes and try again.';
|
|
146
81
|
}
|
|
147
|
-
return
|
|
148
|
-
success: true,
|
|
149
|
-
title: data.title,
|
|
150
|
-
url: data.html_url,
|
|
151
|
-
sourceBranch: head,
|
|
152
|
-
targetBranch: base,
|
|
153
|
-
};
|
|
82
|
+
return inner;
|
|
154
83
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Creates a pull request via the Salesforce Connect API.
|
|
86
|
+
* POST /connect/devops/workItems/{workItemId}/review
|
|
87
|
+
* Salesforce handles VCS authentication and workspace resolution.
|
|
88
|
+
*/
|
|
89
|
+
export async function createPullRequest(connection, workItemId) {
|
|
90
|
+
const path = `/services/data/v${connection.getApiVersion()}/connect/devops/workItems/${workItemId}/review`;
|
|
91
|
+
let response;
|
|
92
|
+
try {
|
|
93
|
+
response = await connection.request({
|
|
94
|
+
method: 'POST',
|
|
95
|
+
url: path,
|
|
96
|
+
body: JSON.stringify({}),
|
|
97
|
+
headers: { 'Content-Type': 'application/json' },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
102
|
+
throw new Error(extractPrError({ errorMessage: raw }));
|
|
103
|
+
}
|
|
104
|
+
const isError = response.status === 'Error' || response.success === false || !!response.errorMessage;
|
|
105
|
+
if (isError) {
|
|
106
|
+
throw new Error(extractPrError(response));
|
|
175
107
|
}
|
|
176
|
-
const links = data.links;
|
|
177
108
|
return {
|
|
178
109
|
success: true,
|
|
179
|
-
|
|
180
|
-
url: links?.html?.href ?? data.url ?? '',
|
|
181
|
-
sourceBranch: head,
|
|
182
|
-
targetBranch: base,
|
|
110
|
+
url: response.reviewUrl,
|
|
183
111
|
};
|
|
184
112
|
}
|
|
185
113
|
/**
|
|
186
|
-
* Attempts to resolve a
|
|
187
|
-
*
|
|
114
|
+
* Attempts to resolve a GitHub authentication token from the environment or the gh CLI.
|
|
115
|
+
* Used by pipeline create for pre-flight owner validation.
|
|
188
116
|
*/
|
|
189
|
-
export
|
|
117
|
+
export function resolveGitHubToken() {
|
|
190
118
|
const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
191
119
|
if (envToken)
|
|
192
|
-
return envToken;
|
|
120
|
+
return Promise.resolve(envToken);
|
|
193
121
|
try {
|
|
194
|
-
|
|
122
|
+
// execSync with a fixed string — no user input, no injection risk
|
|
195
123
|
const token = execSync('gh auth token', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
196
124
|
if (token)
|
|
197
|
-
return token;
|
|
125
|
+
return Promise.resolve(token);
|
|
198
126
|
}
|
|
199
127
|
catch {
|
|
200
128
|
// gh CLI not installed or not authenticated
|
|
201
129
|
}
|
|
202
|
-
return undefined;
|
|
130
|
+
return Promise.resolve(undefined);
|
|
203
131
|
}
|
|
204
132
|
//# sourceMappingURL=createPullRequest.js.map
|