diff --git a/content/repositories/creating-and-managing-repositories/about-repositories.md b/content/repositories/creating-and-managing-repositories/about-repositories.md index 82264a0c5240..1db94dffe657 100644 --- a/content/repositories/creating-and-managing-repositories/about-repositories.md +++ b/content/repositories/creating-and-managing-repositories/about-repositories.md @@ -109,14 +109,7 @@ People with admin permissions for a repository can change an existing repository {% endif %} -All enterprise members have read permissions to the internal repository, but internal repositories are not visible to people {% ifversion fpt or ghec %}outside of the enterprise{% else %}who are not members of any organization{% endif %}, including outside collaborators on organization repositories. For more information, see [AUTOTITLE](/admin/user-management/managing-users-in-your-enterprise/roles-in-an-enterprise#enterprise-members) and [AUTOTITLE](/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization). - -{% ifversion ghes %} - -> [!NOTE] -> A user must be part of an organization to be an enterprise member and have access to internal repositories. If a user on {% data variables.location.product_location %} is not a member of any organization, that user will not have access to internal repositories. - -{% endif %} +Organization members have read permissions to all internal repositories in an enterprise, including those in organizations they are not a member of. Internal repositories are not visible to people {% ifversion fpt or ghec %}outside of the enterprise{% else %}who are not members of any organization{% endif %}, including outside collaborators on organization repositories. For more information, see [AUTOTITLE](/admin/user-management/managing-users-in-your-enterprise/roles-in-an-enterprise#enterprise-members) and [AUTOTITLE](/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization). {% data reusables.repositories.internal-repo-default %} diff --git a/data/reusables/code-scanning/codeql-query-tables/links-to-all-tables.md b/data/reusables/code-scanning/codeql-query-tables/links-to-all-tables.md index 86ffca3cdf5c..b202e9e78371 100644 --- a/data/reusables/code-scanning/codeql-query-tables/links-to-all-tables.md +++ b/data/reusables/code-scanning/codeql-query-tables/links-to-all-tables.md @@ -9,4 +9,5 @@ * [AUTOTITLE](/code-security/code-scanning/managing-your-code-scanning-configuration/javascript-typescript-built-in-queries) * [AUTOTITLE](/code-security/code-scanning/managing-your-code-scanning-configuration/python-built-in-queries) * [AUTOTITLE](/code-security/code-scanning/managing-your-code-scanning-configuration/ruby-built-in-queries) +* [AUTOTITLE](/code-security/code-scanning/managing-your-code-scanning-configuration/rust-built-in-queries) * [AUTOTITLE](/code-security/code-scanning/managing-your-code-scanning-configuration/swift-built-in-queries) diff --git a/src/content-linter/lib/linting-rules/frontmatter-intro-links.ts b/src/content-linter/lib/linting-rules/frontmatter-intro-links.ts new file mode 100644 index 000000000000..2db601eb6fc6 --- /dev/null +++ b/src/content-linter/lib/linting-rules/frontmatter-intro-links.ts @@ -0,0 +1,67 @@ +// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations +import { addError } from 'markdownlint-rule-helpers' + +import { getFrontmatter } from '../helpers/utils' +import { getUIDataMerged } from '@/data-directory/lib/get-data' +import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types' + +interface Frontmatter { + introLinks?: Record + [key: string]: any +} + +// Get the valid introLinks keys from ui.yml +function getValidIntroLinksKeys(): string[] { + try { + const ui = getUIDataMerged('en') + + if (!ui || !ui.product_landing || typeof ui.product_landing !== 'object') { + return [] + } + + // Get all keys from product_landing in ui.yml + return Object.keys(ui.product_landing) + } catch (error) { + console.error('Error loading ui.yml data:', error) + return [] + } +} + +export const frontmatterIntroLinks: Rule = { + names: ['GHD062', 'frontmatter-intro-links'], + description: 'introLinks keys must be valid keys defined in data/ui.yml under product_landing', + tags: ['frontmatter', 'single-source'], + function: (params: RuleParams, onError: RuleErrorCallback) => { + const fm = getFrontmatter(params.lines) as Frontmatter | null + if (!fm || !fm.introLinks) return + + const introLinks = fm.introLinks + if (typeof introLinks !== 'object' || Array.isArray(introLinks)) return + + const validKeys = getValidIntroLinksKeys() + if (validKeys.length === 0) { + // If we can't load the valid keys, skip validation + return + } + + // Check each key in introLinks + for (const key of Object.keys(introLinks)) { + if (!validKeys.includes(key)) { + // Find the line with this key + const line = params.lines.find((line: string) => { + const trimmed = line.trim() + return trimmed.startsWith(`${key}:`) && !trimmed.startsWith('introLinks:') + }) + const lineNumber = line ? params.lines.indexOf(line) + 1 : 1 + + addError( + onError, + lineNumber, + `Invalid introLinks key: '${key}'. Valid keys are: ${validKeys.join(', ')}`, + line || '', + null, // No fix possible + ) + } + } + }, +} diff --git a/src/content-linter/lib/linting-rules/index.ts b/src/content-linter/lib/linting-rules/index.ts index d3cabfa4489d..9f115734384b 100644 --- a/src/content-linter/lib/linting-rules/index.ts +++ b/src/content-linter/lib/linting-rules/index.ts @@ -62,6 +62,7 @@ import { journeyTracksLiquid } from './journey-tracks-liquid' import { journeyTracksGuidePathExists } from './journey-tracks-guide-path-exists' import { journeyTracksUniqueIds } from './journey-tracks-unique-ids' import { frontmatterHeroImage } from './frontmatter-hero-image' +import { frontmatterIntroLinks } from './frontmatter-intro-links' // Using any type because @github/markdownlint-github doesn't provide TypeScript declarations // The elements in the array have a 'names' property that contains rule identifiers @@ -132,6 +133,7 @@ export const gitHubDocsMarkdownlint = { journeyTracksGuidePathExists, // GHD059 journeyTracksUniqueIds, // GHD060 frontmatterHeroImage, // GHD061 + frontmatterIntroLinks, // GHD062 // Search-replace rules searchReplace, // Open-source plugin diff --git a/src/content-linter/style/github-docs.ts b/src/content-linter/style/github-docs.ts index eddf742b46e6..c20a684d9e86 100644 --- a/src/content-linter/style/github-docs.ts +++ b/src/content-linter/style/github-docs.ts @@ -354,6 +354,12 @@ export const githubDocsFrontmatterConfig = { 'partial-markdown-files': false, 'yml-files': false, }, + 'frontmatter-intro-links': { + // GHD062 + severity: 'error', + 'partial-markdown-files': false, + 'yml-files': false, + }, } // Configures rules from the `github/markdownlint-github` repo diff --git a/src/content-linter/tests/unit/frontmatter-intro-links.ts b/src/content-linter/tests/unit/frontmatter-intro-links.ts new file mode 100644 index 000000000000..6a89e997d606 --- /dev/null +++ b/src/content-linter/tests/unit/frontmatter-intro-links.ts @@ -0,0 +1,126 @@ +import path from 'path' + +import { afterAll, beforeAll, describe, expect, test } from 'vitest' + +import { runRule } from '../../lib/init-test' +import { frontmatterIntroLinks } from '../../lib/linting-rules/frontmatter-intro-links' + +const fmOptions = { markdownlintOptions: { frontMatter: null } } + +describe(frontmatterIntroLinks.names.join(' - '), () => { + const envVarValueBefore = process.env.ROOT + + beforeAll(() => { + process.env.ROOT = path.join('src', 'fixtures', 'fixtures') + }) + + afterAll(() => { + process.env.ROOT = envVarValueBefore + }) + + test('valid introLinks keys pass', async () => { + const markdown = [ + '---', + 'title: Test', + 'introLinks:', + ' overview: /path/to/overview', + ' quickstart: /path/to/quickstart', + '---', + '', + '# Test', + ].join('\n') + const result = await runRule(frontmatterIntroLinks, { + strings: { 'content/test/index.md': markdown }, + ...fmOptions, + }) + const errors = result['content/test/index.md'] + expect(errors.length).toBe(0) + }) + + test('missing introLinks is ignored', async () => { + const markdown = ['---', 'title: Test', '---', '', '# Test'].join('\n') + const result = await runRule(frontmatterIntroLinks, { + strings: { 'content/test/index.md': markdown }, + ...fmOptions, + }) + const errors = result['content/test/index.md'] + expect(errors.length).toBe(0) + }) + + test('invalid introLinks key fails', async () => { + const markdown = [ + '---', + 'title: Test', + 'introLinks:', + ' overview: /path/to/overview', + ' invalidKey: /path/to/invalid', + '---', + '', + '# Test', + ].join('\n') + const result = await runRule(frontmatterIntroLinks, { + strings: { 'content/test/index.md': markdown }, + ...fmOptions, + }) + const errors = result['content/test/index.md'] + expect(errors.length).toBe(1) + expect(errors[0].errorDetail).toContain('Invalid introLinks key') + expect(errors[0].errorDetail).toContain('invalidKey') + }) + + test('multiple invalid introLinks keys fail', async () => { + const markdown = [ + '---', + 'title: Test', + 'introLinks:', + ' invalidKey1: /path/to/invalid1', + ' invalidKey2: /path/to/invalid2', + '---', + '', + '# Test', + ].join('\n') + const result = await runRule(frontmatterIntroLinks, { + strings: { 'content/test/index.md': markdown }, + ...fmOptions, + }) + const errors = result['content/test/index.md'] + expect(errors.length).toBe(2) + }) + + test('all common valid introLinks keys pass', async () => { + const markdown = [ + '---', + 'title: Test', + 'introLinks:', + ' overview: /path/to/overview', + ' quickstart: /path/to/quickstart', + ' reference: /path/to/reference', + '---', + '', + '# Test', + ].join('\n') + const result = await runRule(frontmatterIntroLinks, { + strings: { 'content/test/index.md': markdown }, + ...fmOptions, + }) + const errors = result['content/test/index.md'] + expect(errors.length).toBe(0) + }) + + test('non-object introLinks is ignored', async () => { + const markdown = [ + '---', + 'title: Test', + 'introLinks: this is not an object', + '---', + '', + '# Test', + ].join('\n') + const result = await runRule(frontmatterIntroLinks, { + strings: { 'content/test/index.md': markdown }, + ...fmOptions, + }) + const errors = result['content/test/index.md'] + expect(errors.length).toBe(0) + }) +})