Skip to content

Commit 5ef60f1

Browse files
neutrino2211claude
andcommitted
feat(connector): include team access in package collaborators
- Add listTeamPackages function to fetch packages a team can access - Modify /package/:pkg/collaborators endpoint to also return teams - For scoped packages, fetch all org teams and check their access - Merge team access into collaborators response This fixes an issue where team access wasn't visible in the UI because npm access list collaborators only returns users, not teams. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0da290c commit 5ef60f1

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

cli/src/npm-client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,16 @@ export async function listUserPackages(user: string): Promise<NpmExecResult> {
548548
return execNpm(['access', 'list', 'packages', `@${user}`, '--json'], { silent: true })
549549
}
550550

551+
/**
552+
* Lists all packages that a team has access to.
553+
* Uses `npm access list packages {scopeTeam} --json`
554+
* Returns a map of package name to permission level
555+
*/
556+
export async function listTeamPackages(scopeTeam: string): Promise<NpmExecResult> {
557+
validateScopeTeam(scopeTeam)
558+
return execNpm(['access', 'list', 'packages', scopeTeam, '--json'], { silent: true })
559+
}
560+
551561
/**
552562
* Initialize and publish a new package to claim the name.
553563
* Creates a minimal package.json in a temp directory and publishes it.

cli/src/server.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
accessGrant,
4949
accessRevoke,
5050
accessListCollaborators,
51+
listTeamPackages,
5152
ownerAdd,
5253
ownerRemove,
5354
packageInit,
@@ -674,7 +675,10 @@ export function createConnectorApp(expectedToken: string) {
674675
throw new HTTPError({ statusCode: 400, message: pkgValidation.error })
675676
}
676677

677-
const result = await accessListCollaborators(pkgValidation.data)
678+
const pkg = pkgValidation.data
679+
680+
// Get user collaborators
681+
const result = await accessListCollaborators(pkg)
678682
if (result.exitCode !== 0) {
679683
return {
680684
success: false,
@@ -684,6 +688,48 @@ export function createConnectorApp(expectedToken: string) {
684688

685689
try {
686690
const collaborators = JSON.parse(result.stdout) as Record<string, 'read-only' | 'read-write'>
691+
692+
// For scoped packages, also fetch team access
693+
if (pkg.startsWith('@')) {
694+
const orgMatch = pkg.match(/^@([^/]+)\//)
695+
if (orgMatch) {
696+
const org = orgMatch[1]
697+
698+
// Get all teams in the org
699+
const teamsResult = await teamListTeams(org)
700+
if (teamsResult.exitCode === 0) {
701+
try {
702+
const teams = JSON.parse(teamsResult.stdout) as string[]
703+
704+
// Check each team's package access
705+
await Promise.all(
706+
teams.map(async team => {
707+
// Add @ prefix to team name since we expect scoped packages
708+
team = '@' + team
709+
const teamPkgsResult = await listTeamPackages(team)
710+
if (teamPkgsResult.exitCode === 0) {
711+
try {
712+
const teamPkgs = JSON.parse(teamPkgsResult.stdout) as Record<
713+
string,
714+
'read-only' | 'read-write'
715+
>
716+
// If this team has access to the package, add it to collaborators
717+
if (teamPkgs[pkg]) {
718+
collaborators[team] = teamPkgs[pkg]
719+
}
720+
} catch {
721+
// Ignore parse errors for individual teams
722+
}
723+
}
724+
}),
725+
)
726+
} catch {
727+
// Ignore team list parse errors, return user collaborators only
728+
}
729+
}
730+
}
731+
}
732+
687733
return {
688734
success: true,
689735
data: collaborators,

0 commit comments

Comments
 (0)