diff --git a/content/copilot/tutorials/enhancing-copilot-agent-mode-with-mcp.md b/content/copilot/tutorials/enhancing-copilot-agent-mode-with-mcp.md index 9f0f165cea4d..b686cf74c718 100644 --- a/content/copilot/tutorials/enhancing-copilot-agent-mode-with-mcp.md +++ b/content/copilot/tutorials/enhancing-copilot-agent-mode-with-mcp.md @@ -2,7 +2,7 @@ title: Enhancing Copilot agent mode with MCP allowTitleToDifferFromFilename: true shortTitle: Enhance agent mode with MCP -intro: "Learn how to use the Model Context Protocol (MCP) to expand {% data variables.copilot.copilot_chat_short %}''s agentic capabilities." +intro: "Learn how to use the Model Context Protocol (MCP) to expand the agentic capabilities of {% data variables.copilot.copilot_chat_short %}." versions: feature: copilot topics: diff --git a/data/reusables/actions/about-larger-runners.md b/data/reusables/actions/about-larger-runners.md index bb93d2a6c6a3..62bced0e40fd 100644 --- a/data/reusables/actions/about-larger-runners.md +++ b/data/reusables/actions/about-larger-runners.md @@ -1,4 +1,4 @@ -Customers on {% data variables.product.prodname_team %} and {% data variables.product.prodname_ghe_cloud %} plans can choose from a range of managed virtual machines that have more resources than the [standard {% data variables.product.prodname_dotcom %}-hosted runners](/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources). These machines are referred to as "{% data variables.actions.hosted_runner %}." They offer the following advanced features: +Customers on {% data variables.product.prodname_team %} and {% data variables.product.prodname_ghe_cloud %} plans can choose from a range of managed virtual machines that have more resources than the [standard {% data variables.product.prodname_dotcom %}-hosted runners](/actions/how-tos/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources). These machines are referred to as "{% data variables.actions.hosted_runner %}." They offer the following advanced features: * More RAM, CPU, and disk space * Static IP addresses diff --git a/data/reusables/contributing/content-linter-rules.md b/data/reusables/contributing/content-linter-rules.md index 5362e5701388..ea01a4548154 100644 --- a/data/reusables/contributing/content-linter-rules.md +++ b/data/reusables/contributing/content-linter-rules.md @@ -53,6 +53,7 @@ | GHD011 | frontmatter-video-transcripts | Video transcript must be configured correctly | error | frontmatter, feature, video-transcripts | | GHD012 | frontmatter-schema | Frontmatter must conform to the schema | error | frontmatter, schema | | GHD007 | code-annotations | Code annotations defined in Markdown must contain a specific layout frontmatter property | error | code, feature, annotate, frontmatter | +| GHD045 | code-annotation-comment-spacing | Code comments in annotation blocks must have exactly one space after the comment character(s) | warning | code, comments, annotate, spacing | | GHD017 | frontmatter-liquid-syntax | Frontmatter properties must use valid Liquid | error | liquid, frontmatter | | GHD018 | liquid-syntax | Markdown content must use valid Liquid | error | liquid | | GHD019 | liquid-if-tags | Liquid `ifversion` tags should be used instead of `if` tags when the argument is a valid version | error | liquid, versioning | diff --git a/data/reusables/gated-features/copilot-coding-agent.md b/data/reusables/gated-features/copilot-coding-agent.md index 0f1ef88d65a8..85cd8d2bd9d2 100644 --- a/data/reusables/gated-features/copilot-coding-agent.md +++ b/data/reusables/gated-features/copilot-coding-agent.md @@ -1 +1 @@ -{% data variables.copilot.copilot_coding_agent %} is available with the {% data variables.copilot.copilot_pro %}, {% data variables.copilot.copilot_pro_plus %}, {% data variables.copilot.copilot_for_business %} and {% data variables.copilot.copilot_enterprise %} plans in repositories where it has not been disabled. {% data variables.copilot.copilot_coding_agent %} is not available in repositories owned by {% data variables.enterprise.prodname_managed_users %}. +{% data variables.copilot.copilot_coding_agent %} is available with the {% data variables.copilot.copilot_pro %}, {% data variables.copilot.copilot_pro_plus %}, {% data variables.copilot.copilot_for_business %} and {% data variables.copilot.copilot_enterprise %} plans. Access for {% data variables.product.prodname_copilot_short %} trials is coming soon. The agent is available in all repositories, except where it has been explicitly disabled and repositories owned by {% data variables.enterprise.prodname_managed_users %}. diff --git a/src/content-linter/lib/linting-rules/code-annotation-comment-spacing.js b/src/content-linter/lib/linting-rules/code-annotation-comment-spacing.js new file mode 100644 index 000000000000..759e5a5ec319 --- /dev/null +++ b/src/content-linter/lib/linting-rules/code-annotation-comment-spacing.js @@ -0,0 +1,98 @@ +import { addError, filterTokens } from 'markdownlint-rule-helpers' + +export const codeAnnotationCommentSpacing = { + names: ['GHD045', 'code-annotation-comment-spacing'], + description: + 'Code comments in annotation blocks must have exactly one space after the comment character(s)', + tags: ['code', 'comments', 'annotate', 'spacing'], + parser: 'markdownit', + function: (params, onError) => { + filterTokens(params, 'fence', (token) => { + if (!token.info.includes('annotate')) return + + const lines = token.content.split('\n') + + lines.forEach((line, index) => { + const trimmedLine = line.trim() + if (!trimmedLine) return + + // Define a map of comment patterns + const commentPatterns = { + '//': /^(\/\/)(.*)/, // JavaScript/TypeScript/Java/C# style comments + '#': /^(#)(.*)/, // Python/Ruby/Shell/YAML style comments + '--': /^(--)(.*)/, // SQL/Lua style comments + } + + // Check for different comment patterns + let commentMatch = null + let commentChar = null + let restOfLine = null + + // Iterate over the map to find a matching comment style + for (const [char, pattern] of Object.entries(commentPatterns)) { + if (trimmedLine.startsWith(char)) { + commentMatch = trimmedLine.match(pattern) + commentChar = char + restOfLine = commentMatch ? commentMatch[2] : '' + break + } + } + + if (commentMatch && restOfLine !== null) { + // Skip shebang lines (#!/...) + if (trimmedLine.startsWith('#!')) { + return + } + + // Allow empty comments or comments with exactly one space + if (restOfLine === '' || restOfLine.startsWith(' ')) { + // If it starts with a space, make sure it's exactly one space + if (restOfLine.startsWith(' ') && restOfLine.length > 1 && restOfLine[1] === ' ') { + // Multiple spaces - this is an error + const lineNumber = token.lineNumber + index + 1 + const fixedLine = line.replace( + new RegExp(`^(\\s*${commentChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})\\s+`), + `$1 `, + ) + + addError( + onError, + lineNumber, + `Comment must have exactly one space after '${commentChar}', found multiple spaces`, + line, + [1, line.length], + { + lineNumber, + editColumn: 1, + deleteCount: line.length, + insertText: fixedLine, + }, + ) + } + // Single space or empty - this is correct + return + } else { + // No space after comment character - this is an error + const lineNumber = token.lineNumber + index + 1 + const leadingWhitespace = line.match(/^\s*/)[0] + const fixedLine = leadingWhitespace + commentChar + ' ' + restOfLine + + addError( + onError, + lineNumber, + `Comment must have exactly one space after '${commentChar}'`, + line, + [1, line.length], + { + lineNumber, + editColumn: 1, + deleteCount: line.length, + insertText: fixedLine, + }, + ) + } + } + }) + }) + }, +} diff --git a/src/content-linter/lib/linting-rules/index.js b/src/content-linter/lib/linting-rules/index.js index 5312740267c3..f82f5b8ddc15 100644 --- a/src/content-linter/lib/linting-rules/index.js +++ b/src/content-linter/lib/linting-rules/index.js @@ -24,6 +24,7 @@ import { liquidQuotedConditionalArg } from './liquid-quoted-conditional-arg.js' import { liquidDataReferencesDefined, liquidDataTagFormat } from './liquid-data-tags.js' import { frontmatterSchema } from './frontmatter-schema.js' import { codeAnnotations } from './code-annotations.js' +import { codeAnnotationCommentSpacing } from './code-annotation-comment-spacing.js' import { frontmatterLiquidSyntax, liquidSyntax } from './liquid-syntax.js' import { liquidIfTags, liquidIfVersionTags } from './liquid-versioning.js' import { raiReusableUsage } from './rai-reusable-usage.js' @@ -73,6 +74,7 @@ export const gitHubDocsMarkdownlint = { frontmatterVideoTranscripts, frontmatterSchema, codeAnnotations, + codeAnnotationCommentSpacing, frontmatterLiquidSyntax, liquidSyntax, liquidIfTags, diff --git a/src/content-linter/style/github-docs.js b/src/content-linter/style/github-docs.js index 4cc8cd2f7ae2..91d1c6cfd047 100644 --- a/src/content-linter/style/github-docs.js +++ b/src/content-linter/style/github-docs.js @@ -180,6 +180,12 @@ const githubDocsConfig = { 'partial-markdown-files': true, 'yml-files': true, }, + 'code-annotation-comment-spacing': { + // GHD045 + severity: 'warning', + 'partial-markdown-files': true, + 'yml-files': true, + }, 'british-english-quotes': { // GHD048 severity: 'warning', diff --git a/src/content-linter/tests/unit/code-annotation-comment-spacing.js b/src/content-linter/tests/unit/code-annotation-comment-spacing.js new file mode 100644 index 000000000000..bcf08fa011d5 --- /dev/null +++ b/src/content-linter/tests/unit/code-annotation-comment-spacing.js @@ -0,0 +1,264 @@ +import { describe, expect, test } from 'vitest' + +import { runRule } from '../../lib/init-test.js' +import { codeAnnotationCommentSpacing } from '../../lib/linting-rules/code-annotation-comment-spacing.js' + +describe(codeAnnotationCommentSpacing.names.join(' - '), () => { + test('correctly formatted comments pass', async () => { + const markdown = [ + '---', + 'layout: inline', + '---', + '```javascript copy annotate', + '// This is a correct comment', + 'const express = require("express");', + '', + '# This is also correct', + 'const app = express();', + '', + '//', + 'const emptyCommentAbove = "fine";', + '', + '-- SQL comment with space', + 'SELECT * FROM users;', + '```', + ].join('\n') + + const result = await runRule(codeAnnotationCommentSpacing, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(0) + }) + + test('comments without space after comment character fail', async () => { + const markdown = [ + '---', + 'layout: inline', + '---', + '```javascript copy annotate', + '//This should fail the content linter', + 'const express = require("express");', + '', + '#This should also fail', + 'const app = express();', + '', + '--This should fail too', + 'SELECT * FROM users;', + '```', + ].join('\n') + + const result = await runRule(codeAnnotationCommentSpacing, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(3) + + // Check first error (JavaScript comment) + expect(errors[0].lineNumber).toBe(5) + expect(errors[0].errorDetail).toContain("Comment must have exactly one space after '//'") + expect(errors[0].fixInfo).toEqual({ + lineNumber: 5, + editColumn: 1, + deleteCount: 37, + insertText: '// This should fail the content linter', + }) + + // Check second error (Python/Shell comment) + expect(errors[1].lineNumber).toBe(8) + expect(errors[1].errorDetail).toContain("Comment must have exactly one space after '#'") + expect(errors[1].fixInfo).toEqual({ + lineNumber: 8, + editColumn: 1, + deleteCount: 22, + insertText: '# This should also fail', + }) + + // Check third error (SQL comment) + expect(errors[2].lineNumber).toBe(11) + expect(errors[2].errorDetail).toContain("Comment must have exactly one space after '--'") + expect(errors[2].fixInfo).toEqual({ + lineNumber: 11, + editColumn: 1, + deleteCount: 22, + insertText: '-- This should fail too', + }) + }) + + test('comments with multiple spaces after comment character fail', async () => { + const markdown = [ + '---', + 'layout: inline', + '---', + '```javascript copy annotate', + '// This has too many spaces', + 'const express = require("express");', + '', + '# This also has too many', + 'const app = express();', + '', + '-- This has way too many', + 'SELECT * FROM users;', + '```', + ].join('\n') + + const result = await runRule(codeAnnotationCommentSpacing, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(3) + + // Check first error (JavaScript comment) + expect(errors[0].lineNumber).toBe(5) + expect(errors[0].errorDetail).toContain( + "Comment must have exactly one space after '//', found multiple spaces", + ) + expect(errors[0].fixInfo).toEqual({ + lineNumber: 5, + editColumn: 1, + deleteCount: 28, + insertText: '// This has too many spaces', + }) + + // Check second error (Python/Shell comment) + expect(errors[1].lineNumber).toBe(8) + expect(errors[1].errorDetail).toContain( + "Comment must have exactly one space after '#', found multiple spaces", + ) + expect(errors[1].fixInfo).toEqual({ + lineNumber: 8, + editColumn: 1, + deleteCount: 26, + insertText: '# This also has too many', + }) + + // Check third error (SQL comment) + expect(errors[2].lineNumber).toBe(11) + expect(errors[2].errorDetail).toContain( + "Comment must have exactly one space after '--', found multiple spaces", + ) + expect(errors[2].fixInfo).toEqual({ + lineNumber: 11, + editColumn: 1, + deleteCount: 27, + insertText: '-- This has way too many', + }) + }) + + test('indented comments are handled correctly', async () => { + const markdown = [ + '---', + 'layout: inline', + '---', + '```javascript copy annotate', + 'function test() {', + ' //Missing space in indented comment', + ' const x = 1;', + ' ', + ' # Too many spaces', + ' const y = 2;', + '}', + '```', + ].join('\n') + + const result = await runRule(codeAnnotationCommentSpacing, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(2) + + // Check first error (indented JavaScript comment without space) + expect(errors[0].lineNumber).toBe(6) + expect(errors[0].errorDetail).toContain("Comment must have exactly one space after '//'") + expect(errors[0].fixInfo).toEqual({ + lineNumber: 6, + editColumn: 1, + deleteCount: 37, + insertText: ' // Missing space in indented comment', + }) + + // Check second error (indented comment with multiple spaces) + expect(errors[1].lineNumber).toBe(9) + expect(errors[1].errorDetail).toContain( + "Comment must have exactly one space after '#', found multiple spaces", + ) + expect(errors[1].fixInfo).toEqual({ + lineNumber: 9, + editColumn: 1, + deleteCount: 20, + insertText: ' # Too many spaces', + }) + }) + + test('non-annotate code blocks are ignored', async () => { + const markdown = [ + '---', + 'layout: inline', + '---', + '```javascript copy', + '//This should be ignored', + 'const express = require("express");', + '', + '#This should also be ignored', + 'const app = express();', + '```', + ].join('\n') + + const result = await runRule(codeAnnotationCommentSpacing, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(0) + }) + + test('empty lines and non-comment lines are ignored', async () => { + const markdown = [ + '---', + 'layout: inline', + '---', + '```javascript copy annotate', + 'const express = require("express");', + '', + '// This is fine', + 'const app = express();', + '', + 'app.listen(3000);', + '```', + ].join('\n') + + const result = await runRule(codeAnnotationCommentSpacing, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(0) + }) + + test('mixed comment types in same block', async () => { + const markdown = [ + '---', + 'layout: inline', + '---', + '```bash copy annotate', + '#!/bin/bash', + '#This shell comment needs space', + 'echo "Hello"', + '', + '// This JS-style comment is fine', + 'node script.js', + '', + '--This SQL comment needs space', + 'psql -c "SELECT 1;"', + '```', + ].join('\n') + + const result = await runRule(codeAnnotationCommentSpacing, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(2) + + expect(errors[0].lineNumber).toBe(6) + expect(errors[0].errorDetail).toContain("Comment must have exactly one space after '#'") + expect(errors[0].fixInfo).toEqual({ + lineNumber: 6, + editColumn: 1, + deleteCount: 31, + insertText: '# This shell comment needs space', + }) + + expect(errors[1].lineNumber).toBe(12) + expect(errors[1].errorDetail).toContain("Comment must have exactly one space after '--'") + expect(errors[1].fixInfo).toEqual({ + lineNumber: 12, + editColumn: 1, + deleteCount: 30, + insertText: '-- This SQL comment needs space', + }) + }) +}) diff --git a/src/data-directory/scripts/find-orphaned-features/find.ts b/src/data-directory/scripts/find-orphaned-features/find.ts index 6801902de169..136227e61d4f 100644 --- a/src/data-directory/scripts/find-orphaned-features/find.ts +++ b/src/data-directory/scripts/find-orphaned-features/find.ts @@ -155,6 +155,12 @@ function searchAndRemove(features: Set, pages: Page[], verbose = false) // them in, we'll need the English equivalent content to be able to // use the correctTranslatedContentStrings function. + // Check variables files + for (const filePath of getVariableFiles(path.join(languages.en.dir, 'data', 'variables'))) { + const fileContent = fs.readFileSync(filePath, 'utf-8') + checkString(fileContent, features, { filePath, verbose, languageCode: 'en' }) + } + const englishReusables = new Map() for (const filePath of getReusableFiles(path.join(languages.en.dir, 'data', 'reusables'))) { const relativePath = path.relative(languages.en.dir, filePath) @@ -197,7 +203,7 @@ function searchAndRemove(features: Set, pages: Page[], verbose = false) } } -function getReusableFiles(root: string): string[] { +export function getReusableFiles(root: string): string[] { const here = [] for (const file of fs.readdirSync(root)) { const filePath = `${root}/${file}` @@ -210,6 +216,19 @@ function getReusableFiles(root: string): string[] { return here } +export function getVariableFiles(root: string): string[] { + const here = [] + for (const file of fs.readdirSync(root)) { + const filePath = `${root}/${file}` + if (fs.statSync(filePath).isDirectory()) { + here.push(...getVariableFiles(filePath)) + } else if (file.endsWith('.yml') && file !== 'README.yml') { + here.push(filePath) + } + } + return here +} + const IGNORE_ARGS = new Set(['or', 'and', 'not', '<', '>', 'ghes', 'fpt', 'ghec', '!=', '=']) function checkString( diff --git a/src/data-directory/tests/orphaned-features.js b/src/data-directory/tests/orphaned-features.js new file mode 100644 index 000000000000..b3c30bdf80ca --- /dev/null +++ b/src/data-directory/tests/orphaned-features.js @@ -0,0 +1,186 @@ +import { fileURLToPath } from 'url' +import path from 'path' +import fs from 'fs' + +import { describe, expect, test } from 'vitest' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const fixturesDir = path.join(__dirname, 'orphaned-features', 'fixtures') + +// Import the actual helper functions from the orphaned features script +const { getVariableFiles, getReusableFiles } = await import( + '#src/data-directory/scripts/find-orphaned-features/find.js' +) + +describe('orphaned features detection', () => { + test('getVariableFiles finds all yml files in variables directory', () => { + const variablesDir = path.join(fixturesDir, 'data', 'variables') + const variableFiles = getVariableFiles(variablesDir) + + // Should find our test.yml file + expect(variableFiles).toHaveLength(1) + expect(variableFiles[0]).toMatch(/test\.yml$/) + + // Verify the file exists and contains expected content + const testVariableContent = fs.readFileSync(variableFiles[0], 'utf-8') + expect(testVariableContent).toContain('used-in-variables') + expect(testVariableContent).toContain('ifversion') + }) + + test('getReusableFiles finds all md files in reusables directory', () => { + const reusablesDir = path.join(fixturesDir, 'data', 'reusables') + const reusableFiles = getReusableFiles(reusablesDir) + + // Should find our test.md file + expect(reusableFiles).toHaveLength(1) + expect(reusableFiles[0]).toMatch(/test\.md$/) + + // Verify the file exists and contains expected content + const testReusableContent = fs.readFileSync(reusableFiles[0], 'utf-8') + expect(testReusableContent).toContain('used-in-reusables') + expect(testReusableContent).toContain('ifversion') + }) + + test('variables files contain feature references that should be detected', () => { + const variablesDir = path.join(fixturesDir, 'data', 'variables') + const testVariableFile = path.join(variablesDir, 'test.yml') + + expect(fs.existsSync(testVariableFile)).toBe(true) + + const content = fs.readFileSync(testVariableFile, 'utf-8') + + // Verify the test file has the expected feature usage patterns + expect(content).toContain('{% ifversion used-in-variables %}') + expect(content).toContain('test_variable_with_feature') + expect(content).toContain('complex_variable') + }) + + test('helper functions handle nested directories', () => { + // Create a temporary nested structure to test + const tempDir = path.join(__dirname, 'temp-nested-test') + const nestedVariablesDir = path.join(tempDir, 'variables', 'nested') + const nestedReusablesDir = path.join(tempDir, 'reusables', 'nested') + + // Create directories + fs.mkdirSync(nestedVariablesDir, { recursive: true }) + fs.mkdirSync(nestedReusablesDir, { recursive: true }) + + // Create test files + fs.writeFileSync(path.join(nestedVariablesDir, 'nested.yml'), 'test: value') + fs.writeFileSync(path.join(nestedReusablesDir, 'nested.md'), '# Test content') + fs.writeFileSync(path.join(tempDir, 'variables', 'root.yml'), 'root: value') + fs.writeFileSync(path.join(tempDir, 'reusables', 'root.md'), '# Root content') + + try { + // Test getVariableFiles with nested structure + const variableFiles = getVariableFiles(path.join(tempDir, 'variables')) + expect(variableFiles).toHaveLength(2) + expect(variableFiles.some((f) => f.includes('nested.yml'))).toBe(true) + expect(variableFiles.some((f) => f.includes('root.yml'))).toBe(true) + + // Test getReusableFiles with nested structure + const reusableFiles = getReusableFiles(path.join(tempDir, 'reusables')) + expect(reusableFiles).toHaveLength(2) + expect(reusableFiles.some((f) => f.includes('nested.md'))).toBe(true) + expect(reusableFiles.some((f) => f.includes('root.md'))).toBe(true) + } finally { + // Clean up + fs.rmSync(tempDir, { recursive: true, force: true }) + } + }) + + test('helper functions ignore non-target files', () => { + // Create a temporary directory with mixed file types + const tempDir = path.join(__dirname, 'temp-mixed-files') + fs.mkdirSync(tempDir, { recursive: true }) + + // Create various file types + fs.writeFileSync(path.join(tempDir, 'test.yml'), 'yml: content') + fs.writeFileSync(path.join(tempDir, 'test.md'), '# MD content') + fs.writeFileSync(path.join(tempDir, 'test.json'), '{"json": true}') + fs.writeFileSync(path.join(tempDir, 'test.txt'), 'text content') + fs.writeFileSync(path.join(tempDir, 'README.yml'), 'readme: content') + fs.writeFileSync(path.join(tempDir, 'README.md'), '# README') + + try { + // getVariableFiles should only find .yml files (excluding README.yml) + const variableFiles = getVariableFiles(tempDir) + expect(variableFiles).toHaveLength(1) + expect(variableFiles[0]).toMatch(/test\.yml$/) + + // getReusableFiles should only find .md files (excluding README.md) + const reusableFiles = getReusableFiles(tempDir) + expect(reusableFiles).toHaveLength(1) + expect(reusableFiles[0]).toMatch(/test\.md$/) + } finally { + // Clean up + fs.rmSync(tempDir, { recursive: true, force: true }) + } + }) + + test('verify fix addresses the original issue scenario', () => { + // This test simulates the original issue where features were used only in variables + // but not detected by the orphaned features script + + const variablesDir = path.join(fixturesDir, 'data', 'variables') + const featuresDir = path.join(fixturesDir, 'data', 'features') + + // Verify our test setup has the scenario described in the issue + expect(fs.existsSync(path.join(featuresDir, 'used-in-variables.yml'))).toBe(true) + expect(fs.existsSync(path.join(featuresDir, 'truly-orphaned.yml'))).toBe(true) + + // Check that the variable file references the feature + const variableContent = fs.readFileSync(path.join(variablesDir, 'test.yml'), 'utf-8') + expect(variableContent).toContain('used-in-variables') + + // Verify that the getVariableFiles function would find this file + const variableFiles = getVariableFiles(variablesDir) + expect(variableFiles.length).toBeGreaterThan(0) + + // This proves that the fix would catch features used in variables files + // because the orphaned features script now scans these files + const foundFeatureUsage = variableFiles.some((filePath) => { + const content = fs.readFileSync(filePath, 'utf-8') + return content.includes('used-in-variables') + }) + + expect(foundFeatureUsage).toBe(true) + }) + + test('functions correctly identify different file types in same directory', () => { + // Create a directory with both .yml and .md files to ensure each function + // only picks up its target file types + const tempDir = path.join(__dirname, 'temp-mixed-target-files') + fs.mkdirSync(tempDir, { recursive: true }) + + // Create files that both functions might encounter + fs.writeFileSync( + path.join(tempDir, 'variables.yml'), + 'var: {% ifversion test-feature %}enabled{% endif %}', + ) + fs.writeFileSync( + path.join(tempDir, 'reusable.md'), + '{% ifversion test-feature %}Reusable content{% endif %}', + ) + fs.writeFileSync(path.join(tempDir, 'other.txt'), 'other content') + + try { + // Each function should only find its target file type + const variableFiles = getVariableFiles(tempDir) + const reusableFiles = getReusableFiles(tempDir) + + expect(variableFiles).toHaveLength(1) + expect(variableFiles[0]).toMatch(/variables\.yml$/) + + expect(reusableFiles).toHaveLength(1) + expect(reusableFiles[0]).toMatch(/reusable\.md$/) + + // Verify no cross-contamination + expect(variableFiles.some((f) => f.endsWith('.md'))).toBe(false) + expect(reusableFiles.some((f) => f.endsWith('.yml'))).toBe(false) + } finally { + // Clean up + fs.rmSync(tempDir, { recursive: true, force: true }) + } + }) +}) diff --git a/src/data-directory/tests/orphaned-features/fixtures/content/test-article.md b/src/data-directory/tests/orphaned-features/fixtures/content/test-article.md new file mode 100644 index 000000000000..cb2a561f6db0 --- /dev/null +++ b/src/data-directory/tests/orphaned-features/fixtures/content/test-article.md @@ -0,0 +1,21 @@ +--- +title: Test article for orphaned features testing +versions: + feature: used-in-content +--- + +This is a test article that uses the `used-in-content` feature. + +{% ifversion used-in-content %} +This content is only shown when the used-in-content feature is enabled. +{% endif %} + +Some regular content that's always available. + +## Test section + +{% ifversion used-in-content %} +Another conditional section using the feature. +{% else %} +Fallback content when feature is not available. +{% endif %} \ No newline at end of file diff --git a/src/data-directory/tests/orphaned-features/fixtures/data/features/truly-orphaned.yml b/src/data-directory/tests/orphaned-features/fixtures/data/features/truly-orphaned.yml new file mode 100644 index 000000000000..3f728763ead3 --- /dev/null +++ b/src/data-directory/tests/orphaned-features/fixtures/data/features/truly-orphaned.yml @@ -0,0 +1 @@ +title: Test feature that is truly orphaned and should be detected diff --git a/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-content.yml b/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-content.yml new file mode 100644 index 000000000000..1acdcdff41f1 --- /dev/null +++ b/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-content.yml @@ -0,0 +1 @@ +title: Test feature used in content diff --git a/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-reusables.yml b/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-reusables.yml new file mode 100644 index 000000000000..179c9bc72a07 --- /dev/null +++ b/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-reusables.yml @@ -0,0 +1 @@ +title: Test feature used in reusables diff --git a/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-variables.yml b/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-variables.yml new file mode 100644 index 000000000000..33bd9965d5cd --- /dev/null +++ b/src/data-directory/tests/orphaned-features/fixtures/data/features/used-in-variables.yml @@ -0,0 +1 @@ +title: Test feature used in variables diff --git a/src/data-directory/tests/orphaned-features/fixtures/data/reusables/test.md b/src/data-directory/tests/orphaned-features/fixtures/data/reusables/test.md new file mode 100644 index 000000000000..9f11f499144e --- /dev/null +++ b/src/data-directory/tests/orphaned-features/fixtures/data/reusables/test.md @@ -0,0 +1,7 @@ +This is a test reusable that uses a feature. + +{% ifversion used-in-reusables %} +This content is only shown when the used-in-reusables feature is enabled. +{% endif %} + +Some regular content that's always shown. \ No newline at end of file diff --git a/src/data-directory/tests/orphaned-features/fixtures/data/variables/test.yml b/src/data-directory/tests/orphaned-features/fixtures/data/variables/test.yml new file mode 100644 index 000000000000..48d9cb6008ea --- /dev/null +++ b/src/data-directory/tests/orphaned-features/fixtures/data/variables/test.yml @@ -0,0 +1,4 @@ +# Test variables file for orphaned features testing +test_variable_with_feature: '{% ifversion used-in-variables %}This variable uses a feature{% else %}Default value{% endif %}' +another_variable: 'Static value without features' +complex_variable: '{% ifversion used-in-variables %}Feature enabled{% elsif fpt %}Free tier{% else %}Other{% endif %}' diff --git a/src/github-apps/data/fpt-2022-11-28/fine-grained-pat-permissions.json b/src/github-apps/data/fpt-2022-11-28/fine-grained-pat-permissions.json index 9e6791e482ed..66dc27855714 100644 --- a/src/github-apps/data/fpt-2022-11-28/fine-grained-pat-permissions.json +++ b/src/github-apps/data/fpt-2022-11-28/fine-grained-pat-permissions.json @@ -6656,201 +6656,6 @@ } ] }, - "repository_projects": { - "title": "Projects", - "displayTitle": "Repository permissions for \"Projects\"", - "permissions": [ - { - "category": "projects", - "slug": "get-a-project-card", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-card", - "subcategory": "cards", - "verb": "patch", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-card", - "subcategory": "cards", - "verb": "delete", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/cards/{card_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project-column", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-column", - "subcategory": "columns", - "verb": "patch", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-column", - "subcategory": "columns", - "verb": "delete", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-cards", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project", - "subcategory": "projects", - "verb": "get", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-a-project", - "subcategory": "projects", - "verb": "patch", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project", - "subcategory": "projects", - "verb": "delete", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-collaborators", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "add-project-collaborator", - "subcategory": "collaborators", - "verb": "put", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "remove-user-as-a-collaborator", - "subcategory": "collaborators", - "verb": "delete", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-project-permission-for-a-user", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators/{username}/permission", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-columns", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-repository-projects", - "subcategory": "projects", - "verb": "get", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-repository-project", - "subcategory": "projects", - "verb": "post", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "write" - } - ] - }, "pull_requests": { "title": "Pull requests", "displayTitle": "Repository permissions for \"Pull requests\"", @@ -8483,4 +8288,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/github-apps/data/ghec-2022-11-28/fine-grained-pat-permissions.json b/src/github-apps/data/ghec-2022-11-28/fine-grained-pat-permissions.json index 52162a7fd8ca..75e64e80181a 100644 --- a/src/github-apps/data/ghec-2022-11-28/fine-grained-pat-permissions.json +++ b/src/github-apps/data/ghec-2022-11-28/fine-grained-pat-permissions.json @@ -7289,201 +7289,6 @@ } ] }, - "repository_projects": { - "title": "Projects", - "displayTitle": "Repository permissions for \"Projects\"", - "permissions": [ - { - "category": "projects", - "slug": "get-a-project-card", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-card", - "subcategory": "cards", - "verb": "patch", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-card", - "subcategory": "cards", - "verb": "delete", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/cards/{card_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project-column", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-column", - "subcategory": "columns", - "verb": "patch", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-column", - "subcategory": "columns", - "verb": "delete", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-cards", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project", - "subcategory": "projects", - "verb": "get", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-a-project", - "subcategory": "projects", - "verb": "patch", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project", - "subcategory": "projects", - "verb": "delete", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-collaborators", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "add-project-collaborator", - "subcategory": "collaborators", - "verb": "put", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "remove-user-as-a-collaborator", - "subcategory": "collaborators", - "verb": "delete", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-project-permission-for-a-user", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators/{username}/permission", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-columns", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-repository-projects", - "subcategory": "projects", - "verb": "get", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-repository-project", - "subcategory": "projects", - "verb": "post", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "write" - } - ] - }, "pull_requests": { "title": "Pull requests", "displayTitle": "Repository permissions for \"Pull requests\"", @@ -9197,4 +9002,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/github-apps/data/ghes-3.13-2022-11-28/fine-grained-pat-permissions.json b/src/github-apps/data/ghes-3.13-2022-11-28/fine-grained-pat-permissions.json index 4ce0ccd0ee76..e360ad6f8bd5 100644 --- a/src/github-apps/data/ghes-3.13-2022-11-28/fine-grained-pat-permissions.json +++ b/src/github-apps/data/ghes-3.13-2022-11-28/fine-grained-pat-permissions.json @@ -5195,201 +5195,6 @@ } ] }, - "repository_projects": { - "title": "Projects", - "displayTitle": "Repository permissions for \"Projects\"", - "permissions": [ - { - "category": "projects", - "slug": "get-a-project-card", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-card", - "subcategory": "cards", - "verb": "patch", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-card", - "subcategory": "cards", - "verb": "delete", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/cards/{card_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project-column", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-column", - "subcategory": "columns", - "verb": "patch", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-column", - "subcategory": "columns", - "verb": "delete", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-cards", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project", - "subcategory": "projects", - "verb": "get", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-a-project", - "subcategory": "projects", - "verb": "patch", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project", - "subcategory": "projects", - "verb": "delete", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-collaborators", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "add-project-collaborator", - "subcategory": "collaborators", - "verb": "put", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "remove-user-as-a-collaborator", - "subcategory": "collaborators", - "verb": "delete", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-project-permission-for-a-user", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators/{username}/permission", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-columns", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-repository-projects", - "subcategory": "projects", - "verb": "get", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-repository-project", - "subcategory": "projects", - "verb": "post", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "write" - } - ] - }, "pull_requests": { "title": "Pull requests", "displayTitle": "Repository permissions for \"Pull requests\"", @@ -6722,4 +6527,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/github-apps/data/ghes-3.14-2022-11-28/fine-grained-pat-permissions.json b/src/github-apps/data/ghes-3.14-2022-11-28/fine-grained-pat-permissions.json index d20f4d1f083e..23ed1f8913fd 100644 --- a/src/github-apps/data/ghes-3.14-2022-11-28/fine-grained-pat-permissions.json +++ b/src/github-apps/data/ghes-3.14-2022-11-28/fine-grained-pat-permissions.json @@ -5327,201 +5327,6 @@ } ] }, - "repository_projects": { - "title": "Projects", - "displayTitle": "Repository permissions for \"Projects\"", - "permissions": [ - { - "category": "projects", - "slug": "get-a-project-card", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-card", - "subcategory": "cards", - "verb": "patch", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-card", - "subcategory": "cards", - "verb": "delete", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/cards/{card_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project-column", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-column", - "subcategory": "columns", - "verb": "patch", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-column", - "subcategory": "columns", - "verb": "delete", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-cards", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project", - "subcategory": "projects", - "verb": "get", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-a-project", - "subcategory": "projects", - "verb": "patch", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project", - "subcategory": "projects", - "verb": "delete", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-collaborators", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "add-project-collaborator", - "subcategory": "collaborators", - "verb": "put", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "remove-user-as-a-collaborator", - "subcategory": "collaborators", - "verb": "delete", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-project-permission-for-a-user", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators/{username}/permission", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-columns", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-repository-projects", - "subcategory": "projects", - "verb": "get", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-repository-project", - "subcategory": "projects", - "verb": "post", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "write" - } - ] - }, "pull_requests": { "title": "Pull requests", "displayTitle": "Repository permissions for \"Pull requests\"", @@ -6854,4 +6659,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/github-apps/data/ghes-3.15-2022-11-28/fine-grained-pat-permissions.json b/src/github-apps/data/ghes-3.15-2022-11-28/fine-grained-pat-permissions.json index 20e0a8e632d0..e51bb0c1b42b 100644 --- a/src/github-apps/data/ghes-3.15-2022-11-28/fine-grained-pat-permissions.json +++ b/src/github-apps/data/ghes-3.15-2022-11-28/fine-grained-pat-permissions.json @@ -5435,201 +5435,6 @@ } ] }, - "repository_projects": { - "title": "Projects", - "displayTitle": "Repository permissions for \"Projects\"", - "permissions": [ - { - "category": "projects", - "slug": "get-a-project-card", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-card", - "subcategory": "cards", - "verb": "patch", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-card", - "subcategory": "cards", - "verb": "delete", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/cards/{card_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project-column", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-column", - "subcategory": "columns", - "verb": "patch", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-column", - "subcategory": "columns", - "verb": "delete", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-cards", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project", - "subcategory": "projects", - "verb": "get", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-a-project", - "subcategory": "projects", - "verb": "patch", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project", - "subcategory": "projects", - "verb": "delete", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-collaborators", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "add-project-collaborator", - "subcategory": "collaborators", - "verb": "put", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "remove-user-as-a-collaborator", - "subcategory": "collaborators", - "verb": "delete", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-project-permission-for-a-user", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators/{username}/permission", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-columns", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-repository-projects", - "subcategory": "projects", - "verb": "get", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-repository-project", - "subcategory": "projects", - "verb": "post", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "write" - } - ] - }, "pull_requests": { "title": "Pull requests", "displayTitle": "Repository permissions for \"Pull requests\"", @@ -6962,4 +6767,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/github-apps/data/ghes-3.16-2022-11-28/fine-grained-pat-permissions.json b/src/github-apps/data/ghes-3.16-2022-11-28/fine-grained-pat-permissions.json index 47abfaf6d278..771e80d91687 100644 --- a/src/github-apps/data/ghes-3.16-2022-11-28/fine-grained-pat-permissions.json +++ b/src/github-apps/data/ghes-3.16-2022-11-28/fine-grained-pat-permissions.json @@ -5486,201 +5486,6 @@ } ] }, - "repository_projects": { - "title": "Projects", - "displayTitle": "Repository permissions for \"Projects\"", - "permissions": [ - { - "category": "projects", - "slug": "get-a-project-card", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-card", - "subcategory": "cards", - "verb": "patch", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-card", - "subcategory": "cards", - "verb": "delete", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/cards/{card_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project-column", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-column", - "subcategory": "columns", - "verb": "patch", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-column", - "subcategory": "columns", - "verb": "delete", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-cards", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project", - "subcategory": "projects", - "verb": "get", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-a-project", - "subcategory": "projects", - "verb": "patch", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project", - "subcategory": "projects", - "verb": "delete", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-collaborators", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "add-project-collaborator", - "subcategory": "collaborators", - "verb": "put", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "remove-user-as-a-collaborator", - "subcategory": "collaborators", - "verb": "delete", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-project-permission-for-a-user", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators/{username}/permission", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-columns", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-repository-projects", - "subcategory": "projects", - "verb": "get", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-repository-project", - "subcategory": "projects", - "verb": "post", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "write" - } - ] - }, "pull_requests": { "title": "Pull requests", "displayTitle": "Repository permissions for \"Pull requests\"", @@ -7022,4 +6827,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/github-apps/data/ghes-3.17-2022-11-28/fine-grained-pat-permissions.json b/src/github-apps/data/ghes-3.17-2022-11-28/fine-grained-pat-permissions.json index e29e193293da..14e134c38203 100644 --- a/src/github-apps/data/ghes-3.17-2022-11-28/fine-grained-pat-permissions.json +++ b/src/github-apps/data/ghes-3.17-2022-11-28/fine-grained-pat-permissions.json @@ -5513,201 +5513,6 @@ } ] }, - "repository_projects": { - "title": "Projects", - "displayTitle": "Repository permissions for \"Projects\"", - "permissions": [ - { - "category": "projects", - "slug": "get-a-project-card", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-card", - "subcategory": "cards", - "verb": "patch", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-card", - "subcategory": "cards", - "verb": "delete", - "requestPath": "/projects/columns/cards/{card_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/cards/{card_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project-column", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-an-existing-project-column", - "subcategory": "columns", - "verb": "patch", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project-column", - "subcategory": "columns", - "verb": "delete", - "requestPath": "/projects/columns/{column_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-cards", - "subcategory": "cards", - "verb": "get", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-card", - "subcategory": "cards", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/cards", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "move-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/columns/{column_id}/moves", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-a-project", - "subcategory": "projects", - "verb": "get", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "update-a-project", - "subcategory": "projects", - "verb": "patch", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "delete-a-project", - "subcategory": "projects", - "verb": "delete", - "requestPath": "/projects/{project_id}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-collaborators", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "add-project-collaborator", - "subcategory": "collaborators", - "verb": "put", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "remove-user-as-a-collaborator", - "subcategory": "collaborators", - "verb": "delete", - "requestPath": "/projects/{project_id}/collaborators/{username}", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "get-project-permission-for-a-user", - "subcategory": "collaborators", - "verb": "get", - "requestPath": "/projects/{project_id}/collaborators/{username}/permission", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-project-columns", - "subcategory": "columns", - "verb": "get", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-project-column", - "subcategory": "columns", - "verb": "post", - "requestPath": "/projects/{project_id}/columns", - "additional-permissions": true, - "access": "write" - }, - { - "category": "projects", - "slug": "list-repository-projects", - "subcategory": "projects", - "verb": "get", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "read" - }, - { - "category": "projects", - "slug": "create-a-repository-project", - "subcategory": "projects", - "verb": "post", - "requestPath": "/repos/{owner}/{repo}/projects", - "additional-permissions": false, - "access": "write" - } - ] - }, "pull_requests": { "title": "Pull requests", "displayTitle": "Repository permissions for \"Pull requests\"", @@ -7094,4 +6899,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/github-apps/scripts/sync.js b/src/github-apps/scripts/sync.js index 3656818fd2f9..be1ac4fa0092 100755 --- a/src/github-apps/scripts/sync.js +++ b/src/github-apps/scripts/sync.js @@ -99,6 +99,15 @@ export async function syncGitHubAppsData(openApiSource, sourceSchemas, progAcces // fine-grained pats if (isFineGrainedPat) { + // Hardcoded exception: exclude repository_projects from fine-grained PAT permissions + // This is because fine-grained PATs can only operate on organization-level Projects (classic), + // not repository-level Projects (classic). Users cannot grant the repository Projects (classic) + // fine-grained permission in the fine-grained PAT UI. + // See: https://github.com/github/docs-engineering/issues/4613 + if (permissionName === 'repository_projects') { + continue + } + const findGrainedPatPermissions = githubAppsData['fine-grained-pat-permissions'] if (!findGrainedPatPermissions[permissionName]) { findGrainedPatPermissions[permissionName] = {