Skip to content

Commit bcee9b5

Browse files
authored
Reduce reliance on github action specific environment vars (#653)
1 parent b703171 commit bcee9b5

6 files changed

Lines changed: 189 additions & 134 deletions

File tree

src/commands/fix/fix-env-helpers.mts

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,68 @@
1-
import { createSocketBranchParser, getBaseGitBranch } from './git.mts'
2-
import { getGithubEnvRepoInfo, getOpenSocketPrs } from './open-pr.mts'
1+
import { debugFn } from '@socketsecurity/registry/lib/debug'
2+
3+
import {
4+
createSocketBranchParser,
5+
getBaseGitBranch,
6+
gitRepoInfo,
7+
} from './git.mts'
8+
import { getOpenSocketPrs } from './open-pr.mts'
39
import constants from '../../constants.mts'
410

5-
import type { SocketBranchParser } from './git.mts'
6-
import type { GithubRepoInfo, PrMatch } from './open-pr.mts'
11+
import type { RepoInfo, SocketBranchParser } from './git.mts'
12+
import type { PrMatch } from './open-pr.mts'
13+
14+
async function getEnvRepoInfo(
15+
cwd?: string | undefined,
16+
): Promise<RepoInfo | null> {
17+
// Lazily access constants.ENV.GITHUB_REPOSITORY.
18+
const { GITHUB_REPOSITORY } = constants.ENV
19+
if (!GITHUB_REPOSITORY) {
20+
debugFn('miss: GITHUB_REPOSITORY env var')
21+
}
22+
const ownerSlashRepo = GITHUB_REPOSITORY
23+
const slashIndex = ownerSlashRepo.indexOf('/')
24+
if (slashIndex !== -1) {
25+
return {
26+
owner: ownerSlashRepo.slice(0, slashIndex),
27+
repo: ownerSlashRepo.slice(slashIndex + 1),
28+
}
29+
}
30+
return await gitRepoInfo(cwd)
31+
}
732

833
export interface CiEnv {
934
gitEmail: string
1035
gitUser: string
1136
githubToken: string
12-
repoInfo: GithubRepoInfo
37+
repoInfo: RepoInfo
1338
baseBranch: string
1439
branchParser: SocketBranchParser
1540
}
1641

17-
export function getCiEnv(): CiEnv | null {
42+
export async function getCiEnv(): Promise<CiEnv | null> {
1843
const gitEmail = constants.ENV.SOCKET_CLI_GIT_USER_EMAIL
1944
const gitUser = constants.ENV.SOCKET_CLI_GIT_USER_NAME
2045
const githubToken = constants.ENV.SOCKET_CLI_GITHUB_TOKEN
21-
const isCi = !!(
22-
constants.ENV.CI &&
23-
constants.ENV.GITHUB_ACTIONS &&
24-
constants.ENV.GITHUB_REPOSITORY &&
25-
gitEmail &&
26-
gitUser &&
27-
githubToken
28-
)
29-
return isCi
30-
? {
31-
gitEmail,
32-
gitUser,
33-
githubToken,
34-
repoInfo: getGithubEnvRepoInfo()!,
35-
baseBranch: getBaseGitBranch(),
36-
branchParser: createSocketBranchParser(),
37-
}
38-
: null
46+
const isCi = !!(constants.ENV.CI && gitEmail && gitUser && githubToken)
47+
if (!isCi) {
48+
return null
49+
}
50+
const baseBranch = await getBaseGitBranch()
51+
if (!baseBranch) {
52+
return null
53+
}
54+
const repoInfo = await getEnvRepoInfo()
55+
if (!repoInfo) {
56+
return null
57+
}
58+
return {
59+
gitEmail,
60+
gitUser,
61+
githubToken,
62+
repoInfo,
63+
baseBranch,
64+
branchParser: createSocketBranchParser(),
65+
}
3966
}
4067

4168
export async function getOpenPrsForEnvironment(

src/commands/fix/git.mts

Lines changed: 130 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,77 @@ function formatBranchName(name: string): string {
2727
return name.replace(/[^-a-zA-Z0-9/._-]+/g, '+')
2828
}
2929

30-
export function getBaseGitBranch(): string {
31-
// Lazily access constants.ENV.GITHUB_REF_NAME.
32-
return (
33-
constants.ENV.GITHUB_REF_NAME ||
34-
// GitHub defaults to branch name "main"
35-
// https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches#about-the-default-branch
36-
'main'
37-
)
30+
export type SocketBranchParser = (
31+
branch: string,
32+
) => SocketBranchParseResult | null
33+
34+
export type SocketBranchParseResult = {
35+
fullName: string
36+
newVersion: string
37+
type: string
38+
workspace: string
39+
version: string
3840
}
3941

40-
export function getSocketBranchPurlTypeComponent(
41-
purl: string | PackageURL | SocketArtifact,
42-
): string {
43-
const purlObj = getPurlObject(purl)
44-
return formatBranchName(purlObj.type)
42+
export type SocketBranchPatternOptions = {
43+
newVersion?: string | undefined
44+
purl?: string | undefined
45+
workspace?: string | undefined
46+
}
47+
48+
export function createSocketBranchParser(
49+
options?: SocketBranchPatternOptions | undefined,
50+
): SocketBranchParser {
51+
const pattern = getSocketBranchPattern(options)
52+
return function parse(branch: string): SocketBranchParseResult | null {
53+
const match = pattern.exec(branch) as
54+
| [string, string, string, string, string, string]
55+
| null
56+
if (!match) {
57+
return null
58+
}
59+
const {
60+
1: type,
61+
2: workspace,
62+
3: fullName,
63+
4: version,
64+
5: newVersion,
65+
} = match
66+
return {
67+
fullName,
68+
newVersion: semver.coerce(newVersion.replaceAll('+', '.'))?.version,
69+
type,
70+
workspace,
71+
version: semver.coerce(version.replaceAll('+', '.'))?.version,
72+
} as SocketBranchParseResult
73+
}
74+
}
75+
76+
export async function getBaseGitBranch(cwd = process.cwd()): Promise<string> {
77+
// Lazily access constants.ENV properties.
78+
const { GITHUB_BASE_REF, GITHUB_REF_NAME, GITHUB_REF_TYPE } = constants.ENV
79+
// 1. In a pull request, this is always the base branch.
80+
if (GITHUB_BASE_REF) {
81+
return GITHUB_BASE_REF
82+
}
83+
// 2. If it's a branch (not a tag), GITHUB_REF_TYPE should be 'branch'.
84+
if (GITHUB_REF_TYPE === 'branch' && GITHUB_REF_NAME) {
85+
return GITHUB_REF_NAME
86+
}
87+
// 3. Try to resolve the default remote branch using 'git remote show origin'.
88+
// This handles detached HEADs or workflows triggered by tags/releases.
89+
try {
90+
const stdout = (
91+
await spawn('git', ['remote', 'show', 'origin'], { cwd })
92+
).stdout.trim()
93+
const match = /(?<=HEAD branch: ).+/.exec(stdout)
94+
if (match?.[0]) {
95+
return match[0].trim()
96+
}
97+
} catch {}
98+
// GitHub defaults to branch name "main"
99+
// https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches#about-the-default-branch
100+
return 'main'
45101
}
46102

47103
export function getSocketBranchFullNameComponent(
@@ -58,23 +114,6 @@ export function getSocketBranchFullNameComponent(
58114
return `${fmtMaybeNamespace}${formatBranchName(purlObj.name)}`
59115
}
60116

61-
export function getSocketBranchPackageVersionComponent(
62-
version: string | PackageURL | SocketArtifact,
63-
): string {
64-
const purlObj = getPurlObject(
65-
typeof version === 'string' && !version.startsWith('pkg:')
66-
? PackageURL.fromString(`pkg:unknown/unknown@${version}`)
67-
: version,
68-
)
69-
return formatBranchName(purlObj.version!)
70-
}
71-
72-
export function getSocketBranchWorkspaceComponent(
73-
workspace: string | undefined,
74-
): string {
75-
return workspace ? formatBranchName(workspace) : 'root'
76-
}
77-
78117
export function getSocketBranchName(
79118
purl: string | PackageURL | SocketArtifact,
80119
newVersion: string,
@@ -89,10 +128,15 @@ export function getSocketBranchName(
89128
return `socket/${fmtType}/${fmtWorkspace}/${fmtFullName}_${fmtVersion}_${fmtNewVersion}`
90129
}
91130

92-
export type SocketBranchPatternOptions = {
93-
newVersion?: string | undefined
94-
purl?: string | undefined
95-
workspace?: string | undefined
131+
export function getSocketBranchPackageVersionComponent(
132+
version: string | PackageURL | SocketArtifact,
133+
): string {
134+
const purlObj = getPurlObject(
135+
typeof version === 'string' && !version.startsWith('pkg:')
136+
? PackageURL.fromString(`pkg:unknown/unknown@${version}`)
137+
: version,
138+
)
139+
return formatBranchName(purlObj.version!)
96140
}
97141

98142
export function getSocketBranchPattern(
@@ -124,54 +168,27 @@ export function getSocketBranchPattern(
124168
)
125169
}
126170

127-
export type SocketBranchParser = (
128-
branch: string,
129-
) => SocketBranchParseResult | null
130-
131-
export type SocketBranchParseResult = {
132-
fullName: string
133-
newVersion: string
134-
type: string
135-
workspace: string
136-
version: string
171+
export function getSocketBranchPurlTypeComponent(
172+
purl: string | PackageURL | SocketArtifact,
173+
): string {
174+
const purlObj = getPurlObject(purl)
175+
return formatBranchName(purlObj.type)
137176
}
138177

139-
export function createSocketBranchParser(
140-
options?: SocketBranchPatternOptions | undefined,
141-
): SocketBranchParser {
142-
const pattern = getSocketBranchPattern(options)
143-
return function parse(branch: string): SocketBranchParseResult | null {
144-
const match = pattern.exec(branch) as
145-
| [string, string, string, string, string, string]
146-
| null
147-
if (!match) {
148-
return null
149-
}
150-
const {
151-
1: type,
152-
2: workspace,
153-
3: fullName,
154-
4: version,
155-
5: newVersion,
156-
} = match
157-
return {
158-
fullName,
159-
newVersion: semver.coerce(newVersion.replaceAll('+', '.'))?.version,
160-
type,
161-
workspace,
162-
version: semver.coerce(version.replaceAll('+', '.'))?.version,
163-
} as SocketBranchParseResult
164-
}
178+
export function getSocketBranchWorkspaceComponent(
179+
workspace: string | undefined,
180+
): string {
181+
return workspace ? formatBranchName(workspace) : 'root'
165182
}
166183

167-
export function getSocketPullRequestTitle(
184+
export function getSocketCommitMessage(
168185
purl: string | PackageURL | SocketArtifact,
169186
newVersion: string,
170187
workspace?: string | undefined,
171188
): string {
172189
const purlObj = getPurlObject(purl)
173190
const fullName = getPkgFullNameFromPurl(purlObj)
174-
return `Bump ${fullName} from ${purlObj.version} to ${newVersion}${workspace ? ` in ${workspace}` : ''}`
191+
return `socket: Bump ${fullName} from ${purlObj.version} to ${newVersion}${workspace ? ` in ${workspace}` : ''}`
175192
}
176193

177194
export function getSocketPullRequestBody(
@@ -185,14 +202,14 @@ export function getSocketPullRequestBody(
185202
return `Bump [${fullName}](${pkgOverviewUrl}) from ${purlObj.version} to ${newVersion}${workspace ? ` in ${workspace}` : ''}.`
186203
}
187204

188-
export function getSocketCommitMessage(
205+
export function getSocketPullRequestTitle(
189206
purl: string | PackageURL | SocketArtifact,
190207
newVersion: string,
191208
workspace?: string | undefined,
192209
): string {
193210
const purlObj = getPurlObject(purl)
194211
const fullName = getPkgFullNameFromPurl(purlObj)
195-
return `socket: Bump ${fullName} from ${purlObj.version} to ${newVersion}${workspace ? ` in ${workspace}` : ''}`
212+
return `Bump ${fullName} from ${purlObj.version} to ${newVersion}${workspace ? ` in ${workspace}` : ''}`
196213
}
197214

198215
export async function gitCleanFdx(cwd = process.cwd()): Promise<void> {
@@ -227,7 +244,10 @@ export async function gitCreateAndPushBranch(
227244
)
228245
return true
229246
} catch (e) {
230-
debugFn('catch: unexpected\n', e)
247+
debugFn(
248+
`catch: git push --force --set-upstream origin ${branch} failed\n`,
249+
e,
250+
)
231251
}
232252
try {
233253
// Will throw with exit code 1 if branch does not exist.
@@ -236,6 +256,40 @@ export async function gitCreateAndPushBranch(
236256
return false
237257
}
238258

259+
export type RepoInfo = {
260+
owner: string
261+
repo: string
262+
}
263+
264+
export async function gitRepoInfo(
265+
cwd = process.cwd(),
266+
): Promise<RepoInfo | null> {
267+
try {
268+
const remoteUrl = (
269+
await spawn('git', ['remote', 'get-url', 'origin'], { cwd })
270+
).stdout.trim()
271+
// 1. Handle SSH-style, e.g. git@github.com:owner/repo.git
272+
const sshMatch = /^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/.exec(remoteUrl)
273+
if (sshMatch) {
274+
return { owner: sshMatch[1]!, repo: sshMatch[2]! }
275+
}
276+
// 2. Handle HTTPS/URL-style, e.g. https://github.com/owner/repo.git
277+
try {
278+
const parsed = new URL(remoteUrl)
279+
const segments = parsed.pathname.split('/')
280+
const owner = segments.at(-2)
281+
const repo = segments.at(-1)?.replace(/\.git$/, '')
282+
if (owner && repo) {
283+
return { owner, repo }
284+
}
285+
} catch {}
286+
debugFn('git: unmatched git remote URL format', remoteUrl)
287+
} catch (e) {
288+
debugFn('catch: git remote get-url origin failed\n', e)
289+
}
290+
return null
291+
}
292+
239293
export async function gitEnsureIdentity(
240294
name: string,
241295
email: string,
@@ -260,7 +314,7 @@ export async function gitEnsureIdentity(
260314
try {
261315
await spawn('git', ['config', prop, value], stdioIgnoreOptions)
262316
} catch (e) {
263-
debugFn('catch: unexpected\n', e)
317+
debugFn(`catch: git config ${prop} ${value} failed\n`, e)
264318
}
265319
}
266320
}),

src/commands/fix/npm-fix.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export async function npmFix(
111111

112112
spinner?.start()
113113

114-
const ciEnv = getCiEnv()
114+
const ciEnv = await getCiEnv()
115115
const openPrs = ciEnv ? await getOpenPrsForEnvironment(ciEnv) : []
116116

117117
let count = 0

0 commit comments

Comments
 (0)