Skip to content
Draft
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
26 changes: 26 additions & 0 deletions packages/cli/src/commands/review/publish-assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,32 @@ describe('publish-assets', () => {
expect(ghWithInputPlainMock).not.toHaveBeenCalled();
});

it('warns when the assets repo is the reviewed repo, case-insensitively', () => {
process.env['QWEN_REVIEW_ASSETS_REPO'] = 'qwenlm/qwen-code';
ghMock.mockImplementation((...args: string[]) => {
if (args[0] === 'repo' && args[1] === 'view') {
return JSON.stringify({
owner: { login: 'AaronZ345' },
name: 'qwen-code',
url: 'https://github.com/AaronZ345/qwen-code',
parent: { owner: { login: 'QwenLM' }, name: 'qwen-code' },
});
}
return args.includes('.object.sha') ? 'headsha1234567890' : '{}';
});
ghWithInputMock.mockImplementation(() => '{}');

run({ files: [pngFile('evidence.png')] });

expect(process.exitCode).toBeUndefined();
const stderr = (stderrSpy.mock.calls.map((c) => c[0]) as string[]).join(
'\n',
);
expect(stderr).toContain(
'QWEN_REVIEW_ASSETS_REPO points at the reviewed repository',
);
});

it('creates the branch when missing, from the default branch head', () => {
// First ref lookup throws (missing branch); the creation path then asks
// for the default branch and its head; the post-upload head read follows.
Expand Down
43 changes: 43 additions & 0 deletions packages/cli/src/commands/review/publish-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,48 @@ interface PublishAssetsArgs {
defaultComment?: boolean;
}

interface GhRepoView {
owner?: { login?: string };
name?: string;
parent?: {
owner?: { login?: string };
name?: string;
};
}

function resolveReviewedRepoForSelfTargetWarning(
args: PublishAssetsArgs,
): string | undefined {
if (args.reviewedRepo !== undefined) {
return args.reviewedRepo;
}
try {
const view = JSON.parse(
gh('repo', 'view', '--json', 'owner,name,url,parent'),
) as GhRepoView;
const target = view.parent ?? view;
const owner = target.owner?.login;
const name = target.name;
return owner && name ? `${owner}/${name}` : undefined;
} catch {
return undefined;
}
}

function warnIfAssetsRepoTargetsReviewedRepo(
assetsRepo: string,
args: PublishAssetsArgs,
): void {
const reviewedRepo = resolveReviewedRepoForSelfTargetWarning(args);
if (reviewedRepo?.toLowerCase() !== assetsRepo.toLowerCase()) return;
writeStderrLine(
'publish-assets: warning — QWEN_REVIEW_ASSETS_REPO points at the reviewed ' +
'repository. Evidence images will still be published because the ' +
'destination was explicitly designated, but this recreates pr-assets/* refs ' +
'in the repository under review.',
);
}

/** The Contents-API dance for one file: create, or update when it exists. */
function putContent(
repo: string,
Expand Down Expand Up @@ -469,6 +511,7 @@ export function runPublishAssets(args: PublishAssetsArgs): void {
const outPath = resolve(args.out);
mkdirSync(dirname(outPath), { recursive: true });
writeFileSync(outPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
warnIfAssetsRepoTargetsReviewedRepo(repo, args);

// ── Pipeline mode: write the URLs back into the findings artifact ─────────
if (findings && !args.findingsOut) {
Expand Down
Loading