diff --git a/scripts/lib/github-discussions.js b/scripts/lib/github-discussions.js index b9c2c6fc..17767fea 100644 --- a/scripts/lib/github-discussions.js +++ b/scripts/lib/github-discussions.js @@ -4,6 +4,7 @@ const { spawnSync } = require('child_process'); const DEFAULT_DISCUSSION_FIRST = 100; const MAINTAINER_ASSOCIATIONS = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); +const DISCUSSION_ENABLED_QUERY = 'query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } }'; const DISCUSSION_QUERY = 'query($owner: String!, $name: String!, $first: Int!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled discussions(first: $first, orderBy: {field: UPDATED_AT, direction: DESC}) { totalCount nodes { number title url updatedAt authorAssociation category { name isAnswerable } answer { url authorAssociation } comments(first: 20) { nodes { authorAssociation } } } } } }'; function splitRepo(repo) { @@ -91,6 +92,22 @@ function summarizeDiscussion(discussion) { function fetchDiscussionSummary(repo, options = {}) { const { owner, name } = splitRepo(repo); const first = Number.isFinite(options.first) ? options.first : DEFAULT_DISCUSSION_FIRST; + const enabledPayload = runGhJson([ + 'api', + 'graphql', + '-f', + `owner=${owner}`, + '-f', + `name=${name}`, + '-f', + `query=${DISCUSSION_ENABLED_QUERY}`, + ], options); + const enabledRepository = enabledPayload && enabledPayload.data && enabledPayload.data.repository; + + if (!enabledRepository || !enabledRepository.hasDiscussionsEnabled) { + return emptyDiscussionSummary(); + } + const payload = runGhJson([ 'api', 'graphql', @@ -130,6 +147,7 @@ function emptyDiscussionSummary() { module.exports = { DEFAULT_DISCUSSION_FIRST, + DISCUSSION_ENABLED_QUERY, DISCUSSION_QUERY, MAINTAINER_ASSOCIATIONS, discussionNeedsAcceptedAnswer, diff --git a/tests/scripts/discussion-audit.test.js b/tests/scripts/discussion-audit.test.js index fb26c2ee..1a6ff218 100644 --- a/tests/scripts/discussion-audit.test.js +++ b/tests/scripts/discussion-audit.test.js @@ -9,7 +9,10 @@ const path = require('path'); const { execFileSync, spawnSync } = require('child_process'); const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'discussion-audit.js'); -const { DISCUSSION_QUERY } = require(path.join(__dirname, '..', '..', 'scripts', 'lib', 'github-discussions')); +const { + DISCUSSION_ENABLED_QUERY, + DISCUSSION_QUERY +} = require(path.join(__dirname, '..', '..', 'scripts', 'lib', 'github-discussions')); function createTempDir(prefix) { return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); @@ -23,6 +26,10 @@ function discussionGhKey(owner, name, first = 100) { return `api graphql -f owner=${owner} -f name=${name} -F first=${first} -f query=${DISCUSSION_QUERY}`; } +function discussionEnabledGhKey(owner, name) { + return `api graphql -f owner=${owner} -f name=${name} -f query=${DISCUSSION_ENABLED_QUERY}`; +} + function writeGhShim(rootDir, responses) { const shimPath = path.join(rootDir, 'gh-shim.js'); fs.writeFileSync(shimPath, ` @@ -95,6 +102,9 @@ function runTests() { try { const shimPath = writeGhShim(rootDir, { + [discussionEnabledGhKey('affaan-m', 'everything-claude-code')]: { + data: { repository: { hasDiscussionsEnabled: true } } + }, [discussionGhKey('affaan-m', 'everything-claude-code')]: { data: { repository: { @@ -155,6 +165,9 @@ function runTests() { try { const shimPath = writeGhShim(rootDir, { + [discussionEnabledGhKey('affaan-m', 'everything-claude-code')]: { + data: { repository: { hasDiscussionsEnabled: true } } + }, [discussionGhKey('affaan-m', 'everything-claude-code')]: { data: { repository: { @@ -207,6 +220,9 @@ function runTests() { try { const shimPath = writeGhShim(rootDir, { + [discussionEnabledGhKey('affaan-m', 'everything-claude-code')]: { + data: { repository: { hasDiscussionsEnabled: true } } + }, [discussionGhKey('affaan-m', 'everything-claude-code')]: { data: { repository: { @@ -237,6 +253,34 @@ function runTests() { } })) passed++; else failed++; + if (test('passes without heavy query when discussions are disabled', () => { + const rootDir = createTempDir('discussion-audit-disabled-'); + + try { + const shimPath = writeGhShim(rootDir, { + [discussionEnabledGhKey('ECC-Tools', 'ECC-website')]: { + data: { repository: { hasDiscussionsEnabled: false } } + } + }); + + const parsed = JSON.parse(run([ + '--json', + '--repo', + 'ECC-Tools/ECC-website' + ], { + cwd: rootDir, + env: { ECC_GH_SHIM: shimPath } + })); + + assert.strictEqual(parsed.ready, true); + assert.strictEqual(parsed.repos[0].discussions.enabled, false); + assert.strictEqual(parsed.totals.totalDiscussions, 0); + assert.strictEqual(parsed.totals.errors, 0); + } finally { + cleanup(rootDir); + } + })) passed++; else failed++; + if (test('cli help and invalid args exit cleanly', () => { const help = runProcess(['--help']); assert.strictEqual(help.status, 0); diff --git a/tests/scripts/platform-audit.test.js b/tests/scripts/platform-audit.test.js index 3419d182..04c08547 100644 --- a/tests/scripts/platform-audit.test.js +++ b/tests/scripts/platform-audit.test.js @@ -9,7 +9,10 @@ const path = require('path'); const { execFileSync, spawnSync } = require('child_process'); const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'platform-audit.js'); -const { DISCUSSION_QUERY } = require(path.join(__dirname, '..', '..', 'scripts', 'lib', 'github-discussions')); +const { + DISCUSSION_ENABLED_QUERY, + DISCUSSION_QUERY +} = require(path.join(__dirname, '..', '..', 'scripts', 'lib', 'github-discussions')); function createTempDir(prefix) { return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); @@ -91,6 +94,10 @@ function discussionGhKey(owner, name, first = 100) { return `api graphql -f owner=${owner} -f name=${name} -F first=${first} -f query=${DISCUSSION_QUERY}`; } +function discussionEnabledGhKey(owner, name) { + return `api graphql -f owner=${owner} -f name=${name} -f query=${DISCUSSION_ENABLED_QUERY}`; +} + function writeGhShim(rootDir, responses) { const shimPath = path.join(rootDir, 'gh-shim.js'); fs.writeFileSync(shimPath, ` @@ -251,6 +258,9 @@ function runTests() { const shimPath = writeGhShim(projectRoot, { 'pr list --repo affaan-m/everything-claude-code --state open --json number,title,isDraft,mergeStateStatus,updatedAt,url,author': [], 'issue list --repo affaan-m/everything-claude-code --state open --json number,title,updatedAt,url,author,labels': [], + [discussionEnabledGhKey('affaan-m', 'everything-claude-code')]: { + data: { repository: { hasDiscussionsEnabled: true } } + }, [discussionGhKey('affaan-m', 'everything-claude-code')]: { data: { repository: { @@ -317,6 +327,9 @@ function runTests() { const shimPath = writeGhShim(projectRoot, { 'pr list --repo affaan-m/everything-claude-code --state open --json number,title,isDraft,mergeStateStatus,updatedAt,url,author': prs, 'issue list --repo affaan-m/everything-claude-code --state open --json number,title,updatedAt,url,author,labels': [], + [discussionEnabledGhKey('affaan-m', 'everything-claude-code')]: { + data: { repository: { hasDiscussionsEnabled: true } } + }, [discussionGhKey('affaan-m', 'everything-claude-code')]: { data: { repository: { @@ -364,6 +377,38 @@ function runTests() { } })) passed++; else failed++; + if (test('discussion-disabled repos skip the heavy discussion query', () => { + const projectRoot = createTempDir('platform-audit-discussions-disabled-'); + + try { + seedRepo(projectRoot); + const shimPath = writeGhShim(projectRoot, { + 'pr list --repo ECC-Tools/ECC-website --state open --json number,title,isDraft,mergeStateStatus,updatedAt,url,author': [], + 'issue list --repo ECC-Tools/ECC-website --state open --json number,title,updatedAt,url,author,labels': [], + [discussionEnabledGhKey('ECC-Tools', 'ECC-website')]: { + data: { repository: { hasDiscussionsEnabled: false } } + } + }); + + const parsed = JSON.parse(run([ + '--format=json', + `--root=${projectRoot}`, + '--repo', + 'ECC-Tools/ECC-website' + ], { + cwd: projectRoot, + env: { ECC_GH_SHIM: shimPath } + })); + + assert.strictEqual(parsed.ready, true); + assert.strictEqual(parsed.github.repos[0].discussions.enabled, false); + assert.strictEqual(parsed.github.repos[0].discussions.totalCount, 0); + assert.strictEqual(parsed.github.totals.errors, 0); + } finally { + cleanup(projectRoot); + } + })) passed++; else failed++; + if (test('cli help and invalid args exit cleanly', () => { const help = runProcess(['--help']); assert.strictEqual(help.status, 0);