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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `oneblink-release audit-fix` command to run `npm audit fix --package-lock-only` across product repositories, commit lockfile fixes on a shared ticket branch, and create pull requests when `GITHUB_OAUTH_TOKEN` is set (otherwise print create-PR URLs)
- `audit-fix` and `update-dependents` create GitHub pull requests via Octokit when `GITHUB_OAUTH_TOKEN` is set

## [5.0.1] - 2026-08-04

### Changed
Expand Down
33 changes: 32 additions & 1 deletion src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import startProductRelease from './startProductRelease.js'
import promptForReleaseName from './promptForReleaseName.js'
import getRepositoryPlugin from './repositories-plugins/plugins-factory.js'
import startUpdateDependents from './startUpdateDependents.js'
import startAuditFix from './startAuditFix.js'
import waitForNpmPackageVersion from './waitForNpmPackageVersion.js'
import { readPackageUp } from 'read-package-up'

Expand All @@ -34,6 +35,26 @@ ${chalk.bold('Examples')}
oneblink-release product
oneblink-release product --name="Inappropriate Release Name"

${chalk.bold.blue('oneblink-release audit-fix [--force] [--ticket]')}

${chalk.grey(
`Run "npm audit fix --package-lock-only" across each Product repository. Where
package-lock.json changes, create a shared branch, commit, push, and create pull
requests when GITHUB_OAUTH_TOKEN is set (otherwise print create-PR URLs).`,
)}

--force ......... Skip the ticket prompt. Requires --ticket.

--ticket ........ Ticket to use as the branch name and commit prefix
(e.g. ON-4323). Will prompt if not supplied.
Required with --force.

${chalk.bold('Examples')}

oneblink-release audit-fix
oneblink-release audit-fix --ticket ON-4323
oneblink-release audit-fix --force --ticket ON-4323

${chalk.bold.blue(
'oneblink-release repository [next-version] [--no-git] [--name] [--no-name] [--cwd path] [--update-dependents] [--force] [--force-update-dependency] [--force-publish-intermediate-dependency] [--ticket]',
)}
Expand Down Expand Up @@ -91,7 +112,10 @@ ${chalk.bold.blue(
'oneblink-release update-dependents [--cwd path] [--force] [--force-update-dependency] [--force-publish-intermediate-dependency] [--ticket]',
)}

${chalk.grey('Update all product code bases that depend on an NPM package.')}
${chalk.grey(
`Update all product code bases that depend on an NPM package. Creates pull
requests when GITHUB_OAUTH_TOKEN is set (otherwise print create-PR URLs).`,
)}

--cwd .................................... Directory of the repository that is the dependency relative
to the current working directory, defaults to the current
Expand Down Expand Up @@ -212,6 +236,13 @@ async function run(): Promise<void> {
})
break
}
case 'audit-fix': {
await startAuditFix({
ticket: cli.flags.ticket,
force: cli.flags.force,
})
Comment thread
cursor[bot] marked this conversation as resolved.
break
}
case 'product': {
const releaseName = cli.flags.name || (await promptForReleaseName())
await startProductRelease({ releaseName })
Expand Down
77 changes: 77 additions & 0 deletions src/createOrLinkPullRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Octokit } from '@octokit/rest'
import chalk from 'chalk'

export function createPullRequestOctokit(): Octokit | undefined {
const token = process.env.GITHUB_OAUTH_TOKEN
if (!token) {
return undefined
}

const octokit = new Octokit({
auth: token,
})
octokit.log.debug = () => undefined
return octokit
}

export function getCreatePullRequestUrl(
repositoryName: string,
ticket: string,
): string {
return `https://github.com/oneblink/${repositoryName}/pull/new/${ticket}`
}

export default async function createOrLinkPullRequest({
octokit,
repositoryName,
ticket,
title,
body,
}: {
octokit: Octokit | undefined
repositoryName: string
ticket: string
title: string
body?: string
}): Promise<{ url: string; created: boolean }> {
const fallbackUrl = getCreatePullRequestUrl(repositoryName, ticket)

if (!octokit) {
return {
url: fallbackUrl,
created: false,
}
}

try {
const { data: repository } = await octokit.rest.repos.get({
owner: 'oneblink',
repo: repositoryName,
})
const { data } = await octokit.rest.pulls.create({
owner: 'oneblink',
repo: repositoryName,
title,
head: ticket,
base: repository.default_branch,
body,
})

return {
url: data.html_url,
created: true,
}
} catch (error) {
console.warn(
chalk.yellow(
`Failed to create pull request for "${repositoryName}": ${
error instanceof Error ? error.message : String(error)
}. Falling back to create-PR URL.`,
),
)
return {
url: fallbackUrl,
created: false,
}
}
}
45 changes: 45 additions & 0 deletions src/resolveTicket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import enquirer from 'enquirer'

export const TICKET_PATTERN = /^[a-z]{1,3}-\d+$/i

const TICKET_VALIDATION_MESSAGE =
'Ticket must be 1-3 alpha characters, then a hyphen followed by a number'

export default async function resolveTicket({
ticketFlag,
force = false,
}: {
ticketFlag: string | undefined
force?: boolean
}): Promise<string> {
if (ticketFlag) {
if (!TICKET_PATTERN.test(ticketFlag)) {
throw new Error(TICKET_VALIDATION_MESSAGE)
}
return ticketFlag.toUpperCase()
}

if (force) {
throw new Error(
'Cannot use "--force" without "--ticket" because all prompts are skipped.',
)
}

const { ticket } = await enquirer.prompt<{
ticket: string
}>({
type: 'input',
name: 'ticket',
message: `Ticket to associate with pull requests? (e.g. ON-4323, AP-4323, MS-4323)`,
required: true,
validate: (input) => {
if (!TICKET_PATTERN.test(input)) {
return TICKET_VALIDATION_MESSAGE
}
return true
},
result: (input) => input.toUpperCase(),
})

return ticket
}
Loading
Loading