diff --git a/content/actions/creating-actions/metadata-syntax-for-github-actions.md b/content/actions/creating-actions/metadata-syntax-for-github-actions.md index 42cdf81f4adf..22495f6fe76d 100644 --- a/content/actions/creating-actions/metadata-syntax-for-github-actions.md +++ b/content/actions/creating-actions/metadata-syntax-for-github-actions.md @@ -270,7 +270,7 @@ For more information, see "[AUTOTITLE](/actions/learn-github-actions/contexts#gi #### `runs.steps[*].shell` -**Optional** The shell where you want to run the command. You can use any of the shells listed [here](/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell). Required if `run` is set. +**Optional** The shell where you want to run the command. You can use any of the shells listed in "[AUTOTITLE](/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell)." Required if `run` is set. #### `runs.steps[*].if` diff --git a/content/actions/creating-actions/releasing-and-maintaining-actions.md b/content/actions/creating-actions/releasing-and-maintaining-actions.md index 8c476da878f3..75f0eaa1ddbc 100644 --- a/content/actions/creating-actions/releasing-and-maintaining-actions.md +++ b/content/actions/creating-actions/releasing-and-maintaining-actions.md @@ -63,7 +63,7 @@ Here is an example process that you can follow to automatically run tests, creat - When a pull request is opened, either from a branch or a fork, your testing workflow will again run the tests, this time with the merge commit. - - **Note:** for security reasons, workflows triggered by `pull_request` from forks have restricted `GITHUB_TOKEN` permissions and do not have access to secrets. If your tests or other workflows triggered upon pull request require access to secrets, consider using a different event like a [manual trigger](/actions/using-workflows/events-that-trigger-workflows#manual-events) or a [`pull_request_target`](/actions/using-workflows/events-that-trigger-workflows#pull_request_target). Read more [here](/actions/using-workflows/events-that-trigger-workflows#pull-request-events-for-forked-repositories). + - **Note:** for security reasons, workflows triggered by `pull_request` from forks have restricted `GITHUB_TOKEN` permissions and do not have access to secrets. If your tests or other workflows triggered upon pull request require access to secrets, consider using a different event like a [manual trigger](/actions/using-workflows/events-that-trigger-workflows#manual-events) or a [`pull_request_target`](/actions/using-workflows/events-that-trigger-workflows#pull_request_target). For more information, see "[AUTOTITLE](/actions/using-workflows/events-that-trigger-workflows#pull-request-events-for-forked-repositories)." 1. Create a semantically tagged release. {% ifversion fpt or ghec %} You may also publish to {% data variables.product.prodname_marketplace %} with a simple checkbox. {% endif %} For more information, see "[AUTOTITLE](/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release)"{% ifversion fpt or ghec %} and "[AUTOTITLE](/actions/creating-actions/publishing-actions-in-github-marketplace#publishing-an-action)"{% endif %}. diff --git a/content/actions/migrating-to-github-actions/automated-migrations/migrating-from-azure-devops-with-github-actions-importer.md b/content/actions/migrating-to-github-actions/automated-migrations/migrating-from-azure-devops-with-github-actions-importer.md index 5937a6b36ab9..4d5e3fe99465 100644 --- a/content/actions/migrating-to-github-actions/automated-migrations/migrating-from-azure-devops-with-github-actions-importer.md +++ b/content/actions/migrating-to-github-actions/automated-migrations/migrating-from-azure-devops-with-github-actions-importer.md @@ -275,7 +275,7 @@ By default, {% data variables.product.prodname_actions_importer %} fetches pipel For example: ```shell -gh actions-importer dry-run azure-devops --output-dir ./output/ --source-file-path ./path/to/azure_devops/pipeline.yml +gh actions-importer dry-run azure-devops pipeline --output-dir ./output/ --source-file-path ./path/to/azure_devops/pipeline.yml ``` #### `--config-file-path` diff --git a/content/actions/migrating-to-github-actions/automated-migrations/migrating-from-gitlab-with-github-actions-importer.md b/content/actions/migrating-to-github-actions/automated-migrations/migrating-from-gitlab-with-github-actions-importer.md index 7da4107fe93c..fcaa85a12595 100644 --- a/content/actions/migrating-to-github-actions/automated-migrations/migrating-from-gitlab-with-github-actions-importer.md +++ b/content/actions/migrating-to-github-actions/automated-migrations/migrating-from-gitlab-with-github-actions-importer.md @@ -262,7 +262,7 @@ The `--config-file-path` argument can also be used to specify which repository a In this example, {% data variables.product.prodname_actions_importer %} uses the specified YAML configuration file to perform an audit. ```shell -gh actions-importer audit gitlab --output-dir path/to/output/ --config-file-path path/to/gitlab/config.yml +gh actions-importer audit gitlab --output-dir path/to/output/ --namespace my-gitlab-namespace --config-file-path path/to/gitlab/config.yml ``` To audit a GitLab instance using a configuration file, the file must be in the following format, and each `repository_slug` value must be unique: diff --git a/content/education/manage-coursework-with-github-classroom/integrate-github-classroom-with-an-ide/about-using-visual-studio-code-with-github-classroom.md b/content/education/manage-coursework-with-github-classroom/integrate-github-classroom-with-an-ide/about-using-visual-studio-code-with-github-classroom.md index 41ca66a3693f..8e719c78620e 100644 --- a/content/education/manage-coursework-with-github-classroom/integrate-github-classroom-with-an-ide/about-using-visual-studio-code-with-github-classroom.md +++ b/content/education/manage-coursework-with-github-classroom/integrate-github-classroom-with-an-ide/about-using-visual-studio-code-with-github-classroom.md @@ -27,7 +27,7 @@ This will include an "Open in {% data variables.product.prodname_vscode_shortnam {% note %} -**Note:** The student must have Git installed on their computer to push code from {% data variables.product.prodname_vscode_shortname %} to their repository. This is not automatically installed when clicking the **Open in {% data variables.product.prodname_vscode_shortname %}** button. The student can download Git from [here](https://git-scm.com/downloads). +**Note:** The student must have Git installed on their computer to push code from {% data variables.product.prodname_vscode_shortname %} to their repository. This is not automatically installed when clicking the **Open in {% data variables.product.prodname_vscode_shortname %}** button. The student can download Git from [Git download](https://git-scm.com/downloads). {% endnote %} @@ -40,6 +40,6 @@ When the student launches the extension for the first time, they are automatical The student can push their commits to the latest version of remote, by clicking the **sync changes** button, displayed when hovering over the "Active Assignment" line. This abstracts away source control with Git, allowing instructors to teach Git at their own pace. Syncing changes also triggers "Tests" to run if a teacher has configured autograding for their assignment. -The "Group" node under "Active Assignment" will show members of a group, if the assignment is a group project. It will also show the admin members of the repository who can help when a student is stuck. To collaborate on the project, a student can start a Live Share session with anyone in the group node, and they will immediately share the entire context of the repository with them. You can learn more about Live Share and collaborating with it [here](https://docs.microsoft.com/en-us/visualstudio/liveshare/). +The "Group" node under "Active Assignment" will show members of a group, if the assignment is a group project. It will also show the admin members of the repository who can help when a student is stuck. To collaborate on the project, a student can start a Live Share session with anyone in the group node, and they will immediately share the entire context of the repository with them. For more information about Live Share and collaborating with it, see [What is Visual Studio Live Share?](https://docs.microsoft.com/en-us/visualstudio/liveshare/). Once a student is done with the assignment, they can also navigate to see other Assignments and Classrooms. These can be found under the GitHub tab. diff --git a/content/rest/commits/comments.md b/content/rest/commits/comments.md index 04dffc06e22b..ad02a5a8bd55 100644 --- a/content/rest/commits/comments.md +++ b/content/rest/commits/comments.md @@ -18,8 +18,8 @@ You can create, edit, and view commit comments using the REST API. A commit comm ### Custom media types for commit comments -These are the supported media types for commit comments. You can read more -about the use of media types in the API [here](/rest/overview/media-types). +These are the supported media types for commit comments. For more information +about the use of media types in the API, see "[AUTOTITLE](/rest/overview/media-types)." application/vnd.github-commitcomment.raw+json application/vnd.github-commitcomment.text+json diff --git a/content/rest/git/blobs.md b/content/rest/git/blobs.md index 962d23157e31..e2f884a01e58 100644 --- a/content/rest/git/blobs.md +++ b/content/rest/git/blobs.md @@ -18,7 +18,7 @@ autogenerated: rest ## About Git blobs A Git blob (binary large object) is the object type used to store the contents of each file in a repository. The file's SHA-1 hash is computed and stored in the blob object. These endpoints allow you to read and write [blob objects](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) -to your Git database on {% data variables.product.product_name %}. Blobs leverage [these custom media types](#custom-media-types-for-blobs). You can read more about the use of media types in the API [here](/rest/overview/media-types). +to your Git database on {% data variables.product.product_name %}. Blobs leverage [these custom media types](#custom-media-types-for-blobs). For more information about the use of media types in the API, see "[AUTOTITLE](/rest/overview/media-types)." ### Custom media types for blobs diff --git a/content/rest/repos/contents.md b/content/rest/repos/contents.md index 35ea4e32238a..dc4470c97922 100644 --- a/content/rest/repos/contents.md +++ b/content/rest/repos/contents.md @@ -37,6 +37,6 @@ For markup files such as Markdown or AsciiDoc, you can retrieve the rendered HTM Use the `object` media type parameter to retrieve the contents in a consistent object format regardless of the content type. For example, instead of an array of objects for a directory, the response will be an object with an `entries` attribute containing the array of objects. -You can read more about the use of media types in the API [here](/rest/overview/media-types). +For more information about the use of media types in the API, see "[AUTOTITLE](/rest/overview/media-types)." diff --git a/content/site-policy/github-terms/github-educational-use-agreement.md b/content/site-policy/github-terms/github-educational-use-agreement.md index b8be9e823eea..679ff4e8afeb 100644 --- a/content/site-policy/github-terms/github-educational-use-agreement.md +++ b/content/site-policy/github-terms/github-educational-use-agreement.md @@ -10,7 +10,7 @@ topics: {% note %} -**Note:** The below Educational Use Agreement is related to the GitHub Campus Program Partner Schools, also found [here](https://education.github.com/schools/terms), and is considered the program terms and conditions. +**Note:** The below Educational Use Agreement is related to the GitHub Campus Program Partner Schools, also found [here](https://education.github.com/schools/terms), and is considered the program terms and conditions. {% endnote %} diff --git a/package-lock.json b/package-lock.json index 5ed53d21ac69..ba66ac94fa1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,7 @@ "@actions/core": "^1.10.0", "@actions/github": "^5.0.3", "@axe-core/playwright": "^4.7.3", + "@github/markdownlint-github": "^0.4.1", "@graphql-inspector/core": "^5.0.0", "@graphql-tools/load": "^8.0.0", "@jest/globals": "29.7.0", @@ -991,6 +992,15 @@ "version": "2.1.1", "license": "MIT" }, + "node_modules/@github/markdownlint-github": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@github/markdownlint-github/-/markdownlint-github-0.4.1.tgz", + "integrity": "sha512-KbcDWoPRGzlc/VnvPjRD8AhhEFvpQxUbj2NqTPagdoXLVcCc0ecUXLDM3KctIFIuR3OWEQ/+jlDzdeIGb2qEtA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/@github/mini-throttle": { "version": "2.1.0", "license": "MIT" diff --git a/package.json b/package.json index e68eac0a1fc2..c8c7a13a985d 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,7 @@ "@actions/core": "^1.10.0", "@actions/github": "^5.0.3", "@axe-core/playwright": "^4.7.3", + "@github/markdownlint-github": "^0.4.1", "@graphql-inspector/core": "^5.0.0", "@graphql-tools/load": "^8.0.0", "@jest/globals": "29.7.0", diff --git a/src/content-linter/lib/linting-rules/github-owned-action-references.js b/src/content-linter/lib/linting-rules/github-owned-action-references.js new file mode 100644 index 000000000000..30573f2234af --- /dev/null +++ b/src/content-linter/lib/linting-rules/github-owned-action-references.js @@ -0,0 +1,41 @@ +import { addError, ellipsify } from 'markdownlint-rule-helpers' + +import { getRange } from '../helpers/utils.js' +/* + This rule currently only checks for one hardcoded string but + can be generalized in the future to check for strings that + have data reusables. +*/ +export const githubOwnedActionReferences = { + names: ['GHD013', 'github-owned-action-references'], + description: + 'Strings that have a data reusable should use the reusable instead of the hardcoded string.', + tags: ['actions'], + information: new URL('https://github.com/github/docs/blob/main/src/content-linter/README.md'), + function: function GHD013(params, onError) { + const filepath = params.name + if (filepath.startsWith('data/reusables/actions/action-')) return + + const regex = + /(actions\/(checkout|delete-package-versions|download-artifact|upload-artifact|github-script|setup-dotnet|setup-go|setup-java|setup-node|setup-python|stale|cache)|github\/codeql-action[/a-zA-Z-]*)@v\d+/g + + for (let i = 0; i < params.lines.length; i++) { + const line = params.lines[i] + const matches = line.match(regex) + if (!matches) continue + + for (const match of matches) { + const lineNumber = i + 1 + const range = getRange(line, match) + addError( + onError, + lineNumber, + `The string ${match} is using hardcoding a reference to a GitHub-owned action. You should use the reusables for the action. e.g {% data reusables.actions.action-checkout %}.`, + ellipsify(line), + range, + null, // No fix possible + ) + } + } + }, +} diff --git a/src/content-linter/lib/linting-rules/hardcoded-data-variable.js b/src/content-linter/lib/linting-rules/hardcoded-data-variable.js new file mode 100644 index 000000000000..e70b3f5254d9 --- /dev/null +++ b/src/content-linter/lib/linting-rules/hardcoded-data-variable.js @@ -0,0 +1,41 @@ +import { addError, ellipsify } from 'markdownlint-rule-helpers' + +import { getRange } from '../helpers/utils.js' +import frontmatter from '../../../../lib/read-frontmatter.js' + +/* + This rule currently only checks for one hardcoded string but + can be generalized in the future to check for strings that + have data variables. +*/ +export const hardcodedDataVariable = { + names: ['GHD012', 'hardcoded-data-variable'], + description: + 'Strings that have a data reusable should use the reusable instead of the hardcoded string.', + tags: ['single-source'], + information: new URL('https://github.com/github/docs/blob/main/src/content-linter/README.md'), + function: function GHD012(params, onError) { + const frontmatterString = params.frontMatterLines.join('\n') + const fm = frontmatter(frontmatterString).data + if (fm.autogenerated) return + for (let i = 0; i < params.lines.length; i++) { + const line = params.lines[i] + const regex = /personal access tokens?/gi + const matches = line.match(regex) + if (!matches) continue + + for (const match of matches) { + const lineNumber = i + 1 + const range = getRange(line, match) + addError( + onError, + lineNumber, + `The string ${match} can be replaced with a variable. You should use one of the variables from data/variables/product.yml instead of the literal phrase(s):`, + ellipsify(line), + range, + null, // No fix possible + ) + } + } + }, +} diff --git a/src/content-linter/lib/linting-rules/index.js b/src/content-linter/lib/linting-rules/index.js index a78a2e288ccb..a5b7f0679da7 100644 --- a/src/content-linter/lib/linting-rules/index.js +++ b/src/content-linter/lib/linting-rules/index.js @@ -1,4 +1,5 @@ import searchReplace from 'markdownlint-rule-search-replace' +import markdownlintGitHub from '@github/markdownlint-github' import { codeFenceLineLength } from './code-fence-line-length.js' import { imageAltTextEndPunctuation } from './image-alt-text-end-punctuation.js' @@ -11,11 +12,23 @@ import { listFirstWordCapitalization } from './list-first-word-capitalization.js import { linkPunctuation } from './link-punctuation.js' import { earlyAccessReferences } from './early-access-references.js' import { yamlScheduledJobs } from './yaml-scheduled-jobs.js' +import { internalLinksOldVersion } from './internal-links-old-version.js' +import { hardcodedDataVariable } from './hardcoded-data-variable.js' +import { githubOwnedActionReferences } from './github-owned-action-references.js' import { liquidQuotedConditionalArg } from './liquid-quoted-conditional-arg.js' +const noDefaultAltText = markdownlintGitHub.find((elem) => + elem.names.includes('no-default-alt-text'), +) +const noGenericLinkText = markdownlintGitHub.find((elem) => + elem.names.includes('no-generic-link-text'), +) + export const gitHubDocsMarkdownlint = { rules: [ searchReplace, // Open-source plugin + noDefaultAltText, // markdownlint-github rule + noGenericLinkText, // markdownlint-github rule codeFenceLineLength, imageAltTextEndPunctuation, imageFileKebab, @@ -27,6 +40,9 @@ export const gitHubDocsMarkdownlint = { linkPunctuation, earlyAccessReferences, yamlScheduledJobs, + internalLinksOldVersion, + hardcodedDataVariable, + githubOwnedActionReferences, liquidQuotedConditionalArg, ], } diff --git a/src/content-linter/lib/linting-rules/internal-links-old-version.js b/src/content-linter/lib/linting-rules/internal-links-old-version.js new file mode 100644 index 000000000000..d03f16c3852f --- /dev/null +++ b/src/content-linter/lib/linting-rules/internal-links-old-version.js @@ -0,0 +1,57 @@ +import { filterTokens, addError } from 'markdownlint-rule-helpers' +import { getRange } from '../helpers/utils.js' + +export const internalLinksOldVersion = { + names: ['GHD010', 'internal-links-old-version'], + description: 'Internal links must not have a hardcoded version using old versioning patterns', + tags: ['links', 'url'], + information: new URL('https://github.com/github/docs/blob/main/src/content-linter/README.md'), + function: function GHD010(params, onError) { + filterTokens(params, 'inline', (token) => { + if ( + params.name.endsWith('migrating-from-github-enterprise-1110x-to-2123.md') || + params.name.endsWith('all-releases.md') + ) + return + for (const child of token.children) { + if (child.type !== 'link_open') continue + // Things matched by this RegExp: + // - /enterprise/2.19/admin/blah + // - https://docs.github.com/enterprise/11.10.340/admin/blah + // - http://help.github.com/enterprise/2.8/admin/blah + // + // Things intentionally NOT matched by this RegExp: + // - https://someservice.com/enterprise/1.0/blah + // - /github/site-policy/enterprise/2.2/admin/blah + const versionLinkRegEx = + /(?:(?:https?:\/\/(?:help|docs|developer)\.github\.com)(?:\/enterprise\/\d+(\.\d+)+\/[^)\s]*)?|^\/enterprise\/\d+(\.\d+)+\/[^)\s]*)(?=\s|$)/gm + // Example child.attrs: + // [ + // ['href', 'get-started'], ['target', '_blank'], + // ['rel', 'canonical'], + // ] + const hrefsMissingSlashes = child.attrs + // The attribute could also be `target` or `rel` + .filter((attr) => attr[0] === 'href') + .filter((attr) => attr[1].startsWith('/') || !attr[1].startsWith('//')) + // Filter out link paths that matches the version link regex + .filter((attr) => attr[1].match(versionLinkRegEx)) + // Get the link path from the attribute + .map((attr) => attr[1]) + + // Create errors for each link path that includes a hardcoded version + for (const linkPath of hrefsMissingSlashes) { + const range = getRange(child.line, linkPath) + addError( + onError, + child.lineNumber, + `There looks to be a hardcoded version in this link: ${linkPath}`, + linkPath, + range, + null, // No fix possible + ) + } + } + }) + }, +} diff --git a/src/content-linter/style/github-docs.js b/src/content-linter/style/github-docs.js index 9f244ff3b0fb..e5d952558cb9 100644 --- a/src/content-linter/style/github-docs.js +++ b/src/content-linter/style/github-docs.js @@ -1,4 +1,4 @@ -export const githubDocsConfig = { +const githubDocsConfig = { 'code-fence-line-length': { // GHD001 severity: 'warning', @@ -44,6 +44,11 @@ export const githubDocsConfig = { severity: 'error', 'partial-markdown-files': true, }, + 'internal-links-old-version': { + // GHD010 + severity: 'error', + 'partial-markdown-files': true, + }, 'list-first-word-capitalization': { // GH011 severity: 'warning', @@ -59,6 +64,25 @@ export const githubDocsConfig = { severity: 'error', 'partial-markdown-files': true, }, + 'hardcoded-data-variable': { + severity: 'error', + 'partial-markdown-files': true, + }, + 'github-owned-action-references': { + severity: 'error', + 'partial-markdown-files': true, + }, +} + +const githubMarkdownlintConfig = { + 'no-default-alt-text': { + severity: 'error', + 'partial-markdown-files': true, + }, + 'no-generic-link-text': { + severity: 'error', + 'partial-markdown-files': true, + }, } export const searchReplaceConfig = { @@ -130,49 +154,12 @@ export const searchReplaceConfig = { severity: 'error', 'partial-markdown-files': true, }, - { - // Catches usage of string personal access token, which should - // be replaced with a reusable data variable. - name: 'personal access token reusable', - message: - 'The string "personal access token" can be replaced with a variable. You should use one of the variables from data/variables/product.yml instead of the literal phrase(s):', - searchPattern: '/personal access tokens?/gi', - severity: 'warning', - 'partial-markdown-files': true, - }, - { - // Catches usage of GitHub-owned actions that don't use a - // resuable. - // GitHub-owned actions (e.g. actions/checkout@v2) should use a - // reusable in examples. - // - // - actions/checkout@v2 - // - actions/delete-package-versions@v2 - // - actions/download-artifact@v2 - // - actions/upload-artifact@v2 - // - actions/github-script@v2 - // - actions/setup-dotnet@v2 - // - actions/setup-go@v2 - // - actions/setup-java@v2 - // - actions/setup-node@v2 - // - actions/setup-python@v2 - // - actions/stale@v2 - // - actions/cache@v2 - // - github/codeql-action/init@v2 - // - github/codeql-action/analyze@v2 - // - github/codeql-action/autobuild@v2 - // - github/codeql-action/upload-sarif@v2 - // - name: 'GitHub-owned action references should use a reusable', - message: - 'A GitHub-owned action is referenced, but should be replaced with a reusable from data/reusables/actions.', - searchPattern: - '/(actions\\/(checkout|delete-package-versions|download-artifact|upload-artifact|github-script|setup-dotnet|setup-go|setup-java|setup-node|setup-python|stale|cache)|github\\/codeql-action[/a-zA-Z-]*)/g', - severity: 'warning', - 'partial-markdown-files': true, - }, ], }, } -export const customConfig = { ...searchReplaceConfig, ...githubDocsConfig } +export const customConfig = { + ...searchReplaceConfig, + ...githubDocsConfig, + ...githubMarkdownlintConfig, +} diff --git a/src/content-linter/tests/fixtures/not-secret.md b/src/content-linter/tests/fixtures/not-secret.md index 1d58f125bfec..dca2c8118ef4 100644 --- a/src/content-linter/tests/fixtures/not-secret.md +++ b/src/content-linter/tests/fixtures/not-secret.md @@ -30,4 +30,4 @@ Early access is ok ![Node.js](https://example.org/assets/images/early-access/blah.gif) [link-definition-ref]: http://help.github.com/early-access/github/blah -[image-definition-ref]: http://help.github.com/assets/images/early-access/github/blah.gif \ No newline at end of file +[image-definition-ref]: http://help.github.com/assets/images/early-access/github/blah.gif diff --git a/src/content-linter/tests/lint-files.js b/src/content-linter/tests/lint-files.js index 8ec3ea361227..1c70ab30c9f2 100755 --- a/src/content-linter/tests/lint-files.js +++ b/src/content-linter/tests/lint-files.js @@ -154,28 +154,6 @@ const oldVariableRegex = /{{\s*?site\.data\..*?}}/g // const oldOcticonRegex = /{{\s*?octicon-([a-z-]+)(\s[\w\s\d-]+)?\s*?}}/g -// GitHub-owned actions (e.g. actions/checkout@v2) should use a reusable in examples. -// list: -// - actions/checkout@v2 -// - actions/delete-package-versions@v2 -// - actions/download-artifact@v2 -// - actions/upload-artifact@v2 -// - actions/github-script@v2 -// - actions/setup-dotnet@v2 -// - actions/setup-go@v2 -// - actions/setup-java@v2 -// - actions/setup-node@v2 -// - actions/setup-python@v2 -// - actions/stale@v2 -// - actions/cache@v2 -// - github/codeql-action/init@v2 -// - github/codeql-action/analyze@v2 -// - github/codeql-action/autobuild@v2 -// - github/codeql-action/upload-sarif@v2 -// -const literalActionInsteadOfReusableRegex = - /(actions\/(checkout|delete-package-versions|download-artifact|upload-artifact|github-script|setup-dotnet|setup-go|setup-java|setup-node|setup-python|stale|cache)|github\/codeql-action[/a-zA-Z-]*)@v\d+/g - const relativeArticleLinkErrorText = 'Found unexpected relative article links:' const languageLinkErrorText = 'Found article links with hard-coded language codes:' const versionLinkErrorText = 'Found article links with hard-coded version numbers:' @@ -188,8 +166,6 @@ const oldVariableErrorText = 'Found article uses old {{ site.data... }} syntax. Use {% data example.data.string %} instead!' const oldOcticonErrorText = 'Found octicon variables with the old {{ octicon-name }} syntax. Use {% octicon "name" %} instead!' -const literalActionInsteadOfReusableErrorText = - 'Found a literal mention of a GitHub-owned action. Instead, use the reusables for the action. e.g {% data reusables.actions.action-checkout %}' const mdWalkOptions = { globs: ['**/*.md'], @@ -500,24 +476,6 @@ describe('lint markdown content', () => { expect(matches.length, errorMessage).toBe(0) }) - test('URLs must not contain a hard-coded version number', async () => { - const initialMatches = content.match(versionLinkRegEx) || [] - - // Filter out some very specific false positive matches - const matches = initialMatches.filter(() => { - if ( - markdownRelPath.endsWith('migrating-from-github-enterprise-1110x-to-2123.md') || - markdownRelPath.endsWith('all-releases.md') - ) { - return false - } - return true - }) - - const errorMessage = formatLinkError(versionLinkErrorText, matches) - expect(matches.length, errorMessage).toBe(0) - }) - test('URLs must not contain a hard-coded domain name', async () => { const initialMatches = content.match(domainLinkRegex) || [] @@ -563,24 +521,6 @@ describe('lint markdown content', () => { } }) } - - if (!markdownRelPath.includes('data/reusables/actions/action-')) { - test('must not contain literal GitHub-owned actions', async () => { - const matches = content.match(literalActionInsteadOfReusableRegex) || [] - const errorMessage = formatLinkError(literalActionInsteadOfReusableErrorText, matches) - expect(matches.length, errorMessage).toBe(0) - }) - } - - test('must use personal access token variables', async () => { - const patRegex = /personal access tokens?/gi - const matches = content.match(patRegex) || [] - const errorMessage = formatLinkError( - 'You should use one of the personal access token variables from data/variables/product.yml instead of the literal phrase(s):', - matches, - ) - expect(matches.length, errorMessage).toBe(0) - }) }) }) diff --git a/src/content-linter/tests/unit/github-owned-action-references.js b/src/content-linter/tests/unit/github-owned-action-references.js new file mode 100644 index 000000000000..9a98d83a7a7a --- /dev/null +++ b/src/content-linter/tests/unit/github-owned-action-references.js @@ -0,0 +1,18 @@ +import { runRule } from '../../lib/init-test.js' +import { githubOwnedActionReferences } from '../../lib/linting-rules/github-owned-action-references.js' + +describe(githubOwnedActionReferences.names.join(' - '), () => { + test('Using hardcoded GitHub-owned actions causes error', async () => { + const markdown = [ + 'Hello actions/checkout@v2 apps.', + 'A actions/delete-package-versions@v2 for apps.', + 'Hello actions/download-artifact@v2.', + 'actions/cache@432433423423', + 'actions/cache@', + '[link title](/actions/cache)', + ].join('\n') + const result = await runRule(githubOwnedActionReferences, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(3) + }) +}) diff --git a/src/content-linter/tests/unit/hardcoded-data-variable.js b/src/content-linter/tests/unit/hardcoded-data-variable.js new file mode 100644 index 000000000000..2f539afdb6a3 --- /dev/null +++ b/src/content-linter/tests/unit/hardcoded-data-variable.js @@ -0,0 +1,19 @@ +import { runRule } from '../../lib/init-test.js' +import { hardcodedDataVariable } from '../../lib/linting-rules/hardcoded-data-variable.js' + +describe(hardcodedDataVariable.names.join(' - '), () => { + test('Using hardcoded personal access token string causes error', async () => { + const markdown = [ + 'Hello personal access token for apps.', + 'A Personal access token for apps.', + 'Lots of PERSONAL ACCESS TOKENS for apps.', + 'access tokens for apps.', + 'personal access token and a Personal Access Tokens', + ].join('\n') + const result = await runRule(hardcodedDataVariable, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(5) + expect(errors[errors.length - 2].lineNumber).toBe(5) + expect(errors[errors.length - 1].lineNumber).toBe(5) + }) +}) diff --git a/src/content-linter/tests/unit/internal-links-old-version.js b/src/content-linter/tests/unit/internal-links-old-version.js new file mode 100644 index 000000000000..0174e5d17874 --- /dev/null +++ b/src/content-linter/tests/unit/internal-links-old-version.js @@ -0,0 +1,31 @@ +import { runRule } from '../../lib/init-test.js' +import { internalLinksOldVersion } from '../../lib/linting-rules/internal-links-old-version.js' + +describe(internalLinksOldVersion.names.join(' - '), () => { + test('links with old hardcoded versioning fail', async () => { + const markdown = [ + '[Enterprise 2.19](/enterprise/2.19/admin/site/blah)', + '[Link to Enterprise 11.10.340](https://docs.github.com/enterprise/11.10.340/admin/yes)', + '[Enterprise 2.8](http://help.github.com/enterprise/2.8/admin/)', + ].join('\n') + const result = await runRule(internalLinksOldVersion, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(3) + expect(errors.map((error) => error.lineNumber)).toEqual([1, 2, 3]) + expect(errors[0].errorRange).toEqual([19, 32]) + expect(errors[1].errorRange).toEqual([32, 54]) + expect(errors[2].errorRange).toEqual([18, 44]) + }) + + test('links without old hardcoded versions pass', async () => { + const markdown = [ + // External links with enterprise in them + '[External link](https://someservice.com/enterprise/1.0/admin/yes)', + // Current versioning links is excluded from this test + '[New versioning](/github/site-policy/enterprise/2.2/yes)', + ].join('\n') + const result = await runRule(internalLinksOldVersion, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(0) + }) +}) diff --git a/src/content-linter/tests/unit/search-replace.js b/src/content-linter/tests/unit/search-replace.js index afcdc5de9962..952eca2aa06a 100644 --- a/src/content-linter/tests/unit/search-replace.js +++ b/src/content-linter/tests/unit/search-replace.js @@ -60,35 +60,4 @@ describe(searchReplace.names.join(' - '), () => { const errors = result.markdown expect(errors.length).toBe(4) }) - - test('Using hardcoded personal access token string causes error', async () => { - const markdown = [ - 'Hello personal access token for apps.', - 'A Personal access token for apps.', - 'Lots of PERSONAL ACCESS TOKENS for apps.', - 'access tokens for apps.', - ].join('\n') - const result = await runRule(searchReplace, { - strings: { markdown }, - ruleConfig: searchReplaceConfig['search-replace'], - }) - const errors = result.markdown - expect(errors.length).toBe(3) - }) - - test('Using hardcoded personal access token string causes error', async () => { - const markdown = [ - 'Hello actions/checkout@v2 apps.', - 'A actions/delete-package-versions@v2 for apps.', - 'Hello actions/download-artifact@v2.', - 'actions/cache@432433423423', - 'actions/cache@', - ].join('\n') - const result = await runRule(searchReplace, { - strings: { markdown }, - ruleConfig: searchReplaceConfig['search-replace'], - }) - const errors = result.markdown - expect(errors.length).toBe(5) - }) })