diff --git a/.github/workflows/article-api-docs.yml b/.github/workflows/article-api-docs.yml
new file mode 100644
index 000000000000..286b65a14e78
--- /dev/null
+++ b/.github/workflows/article-api-docs.yml
@@ -0,0 +1,45 @@
+name: 'Check article-api docs'
+
+# **What it does**: Makes sure changes to the article api are documented.
+# **Why we have it**: So what's documented doesn't fall behind
+# **Who does it impact**: Docs engineering, CGS team
+
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - 'src/article-api/middleware/article.ts'
+ - 'src/article-api/middleware/pagelist.ts'
+ # Self-test
+ - .github/workflows/article-api-docs.yml
+
+permissions:
+ contents: read
+
+jobs:
+ check-content-linter-rules-docs:
+ runs-on: ${{ fromJSON('["ubuntu-latest", "ubuntu-20.04-xl"]')[github.repository == 'github/docs-internal'] }}
+ if: github.repository == 'github/docs-internal' || github.repository == 'github/docs'
+ steps:
+ - name: Checkout
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+
+ - uses: ./.github/actions/node-npm-setup
+
+ - name: Check that src/article-api/README.md is up-to-date
+ run: npm run generate-article-api-docs
+
+ - name: Fail if it isn't up-to-date
+ run: |
+ if [ -n "$(git status --porcelain)" ]; then
+ git status
+ git diff
+
+ # Some whitespace for the sake of the message below
+ echo ""
+ echo ""
+
+ echo "src/article-api/README.md is out of date."
+ echo "Please run 'npm run generate-article-api-docs' and commit the changes."
+ exit 1;
+ fi
diff --git a/.github/workflows/first-responder-v2-prs-collect.yml b/.github/workflows/first-responder-v2-prs-collect.yml
index 4cf321de076d..7ab86a2cc7e7 100644
--- a/.github/workflows/first-responder-v2-prs-collect.yml
+++ b/.github/workflows/first-responder-v2-prs-collect.yml
@@ -71,9 +71,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- ISSUE_NUMS=$(echo "${{ github.event.pull_request.body }}" | grep -oE '#[0-9]+' | tr -d '#')
+ ISSUE_NUMS=$(echo "${{ github.event.pull_request.body }}" | grep -oE '(https://github.com/github/docs-content/issues/[0-9]+|#[0-9]+)' | grep -oE '[0-9]+')
for ISSUE_NUM in $ISSUE_NUMS; do
- LABELS=$(gh issue view $ISSUE_NUM --json labels --jq '.labels[].name')
+ LABELS=$(gh issue view $ISSUE_NUM --repo github/docs-content --json labels --jq '.labels[].name')
if echo "$LABELS" | grep -q 'DIY docs'; then
echo "DIY_DOCS_LABEL=true" >> $GITHUB_ENV
break
diff --git a/content/admin/managing-iam/provisioning-user-accounts-with-scim/user-provisioning-with-scim-on-ghes.md b/content/admin/managing-iam/provisioning-user-accounts-with-scim/user-provisioning-with-scim-on-ghes.md
index 2b2ae29316f4..5e6795b0283d 100644
--- a/content/admin/managing-iam/provisioning-user-accounts-with-scim/user-provisioning-with-scim-on-ghes.md
+++ b/content/admin/managing-iam/provisioning-user-accounts-with-scim/user-provisioning-with-scim-on-ghes.md
@@ -71,7 +71,7 @@ When SCIM is enabled, you will no longer be able to delete, suspend, or promote
If you currently use SAML SSO, and you are enabling SCIM, you should be aware of what happens to existing users during SCIM provisioning.
-* When SCIM is enabled, users with SAML-linked identities will **not be able to sign in** until their identities have been provisioned by SCIM.
+* When SCIM is enabled, users with SAML-linked identities will **not be able to sign in** until their identities have been provisioned by SCIM.{% ifversion scim-for-ghes-ga %} You will no longer be able to update the SAML `NameID` of existing users in the site admin dashboard.{% endif %}
* When your instance receives a SCIM request, SCIM identities are matched to existing users by **comparing the `userName` SCIM field with the {% data variables.product.prodname_dotcom %} username**. If a user with a matching username doesn't exist, {% data variables.product.prodname_dotcom %} creates a new user.
* If {% data variables.product.prodname_dotcom %} successfully identifies a user from the IdP, but account details such as email address, first name, or last name don't match, the instance **overwrites the details** with values from the IdP. Any email addresses other than the primary email provisioned by SCIM will also be deleted from the user account.
diff --git a/content/admin/managing-iam/using-saml-for-enterprise-iam/updating-a-users-saml-nameid.md b/content/admin/managing-iam/using-saml-for-enterprise-iam/updating-a-users-saml-nameid.md
index b1d5c4ac9227..44590c7c70d5 100644
--- a/content/admin/managing-iam/using-saml-for-enterprise-iam/updating-a-users-saml-nameid.md
+++ b/content/admin/managing-iam/using-saml-for-enterprise-iam/updating-a-users-saml-nameid.md
@@ -21,6 +21,10 @@ In some situations, you may need to update values associated with a person's acc
To update user SAML `NameID` mappings in bulk, you can use the `ghe-saml-mapping-csv` command. For more information, see [AUTOTITLE](/admin/administering-your-instance/administering-your-instance-from-the-command-line/command-line-utilities#ghe-saml-mapping-csv).
+{% ifversion scim-for-ghes-ga %}
+When SCIM is enabled on your {% data variables.product.prodname_ghe_server %} instance, you cannot update user SAML `NameID` mappings.
+{% endif %}
+
## Updating a user's SAML `NameID`
Enterprise owners can update a user's SAML `NameID` on a {% data variables.product.github %} instance.
diff --git a/content/codespaces/developing-in-a-codespace/opening-an-existing-codespace.md b/content/codespaces/developing-in-a-codespace/opening-an-existing-codespace.md
index e2f6c03d4c04..75593637b5e0 100644
--- a/content/codespaces/developing-in-a-codespace/opening-an-existing-codespace.md
+++ b/content/codespaces/developing-in-a-codespace/opening-an-existing-codespace.md
@@ -62,8 +62,6 @@ You can bookmark the address of this page if you want to get back to it quickly
1. Click **Open in**.
1. Click **Open in APPLICATION**.
- 
-
You can open the codespace in:
* Your browser
* {% data variables.product.prodname_vscode %}
diff --git a/content/codespaces/setting-your-user-preferences/setting-your-default-editor-for-github-codespaces.md b/content/codespaces/setting-your-user-preferences/setting-your-default-editor-for-github-codespaces.md
index 5eb31f1c3b26..b834df27ba2e 100644
--- a/content/codespaces/setting-your-user-preferences/setting-your-default-editor-for-github-codespaces.md
+++ b/content/codespaces/setting-your-user-preferences/setting-your-default-editor-for-github-codespaces.md
@@ -29,8 +29,6 @@ If you want to use {% data variables.product.prodname_vscode %} as your default
{% data reusables.user-settings.codespaces-tab %}
1. Under "Editor preference", select the option you want.
- 
-
* {% data reusables.codespaces.application-installed-locally %}
* If you choose **{% data variables.product.prodname_vscode %}**, {% data variables.product.prodname_github_codespaces %} will automatically open in the desktop application when you next create or open a codespace.
diff --git a/content/issues/tracking-your-work-with-issues/configuring-issues/managing-issue-types-in-an-organization.md b/content/issues/tracking-your-work-with-issues/configuring-issues/managing-issue-types-in-an-organization.md
index 6d7ef3b5e832..480e58786bdb 100644
--- a/content/issues/tracking-your-work-with-issues/configuring-issues/managing-issue-types-in-an-organization.md
+++ b/content/issues/tracking-your-work-with-issues/configuring-issues/managing-issue-types-in-an-organization.md
@@ -25,12 +25,11 @@ When you add an issue type to an issue, the type will be shown on any lists of i
1. Under "Type name", type the name of your new issue type.
1. Under "Description", to help other people understand the purpose of your new issue type, type a description.
1. Under "Color", click on the color you would like for the new issue type.
-1. Optionally, to stop the new issue type being available in public repositories, select **Private repositories only**.
1. Click **Create**.
## Making changes to issue types
-You can change the name, description, color, and public repository visibility of your issue types.
+You can change the name, description, and color of your issue types.
You can also choose to disable or delete an issue type. If you disable an issue type, it will not be shown and it won't be possible to set an issue to that type, but if you later decide to enable the issue type, it will be displayed again on any issues previously set to the issue type. If you delete an issue type, it is permanently removed.
@@ -40,5 +39,5 @@ You can also choose to disable or delete an issue type. If you disable an issue

1. In the menu, click **Edit** and make your changes.
- * To make changes to the type name, description, color, and if the issue type should only appear for private repositories. Then click **Save**.
+ * To make changes to the type name, description, or color, click **Save**.
* To disable or delete the issue type, in the "Danger zone", click **Disable** or **Delete** and follow the prompts.
diff --git a/data/features/scim-for-ghes-ga.yml b/data/features/scim-for-ghes-ga.yml
new file mode 100644
index 000000000000..626d76351645
--- /dev/null
+++ b/data/features/scim-for-ghes-ga.yml
@@ -0,0 +1,5 @@
+# 16433
+# SCIM for GitHub Enterprise Server, GA
+
+versions:
+ ghes: '>=3.17'
diff --git a/data/reusables/codespaces/application-installed-locally.md b/data/reusables/codespaces/application-installed-locally.md
index 2466e8de32d2..955296bbe1c5 100644
--- a/data/reusables/codespaces/application-installed-locally.md
+++ b/data/reusables/codespaces/application-installed-locally.md
@@ -1 +1 @@
-If you choose **{% data variables.product.prodname_vscode %}**, you must make sure you have installed the selected application on your local machine.
+If you choose **{% data variables.product.prodname_vscode %}**, you must make sure you have {% data variables.product.prodname_vscode_shortname %} installed on your local machine.
diff --git a/package.json b/package.json
index cb010b4d989b..3d4c4053713f 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"lint-content": "tsx src/content-linter/scripts/lint-content.js",
"lint-translation": "vitest src/content-linter/tests/lint-files.js",
"liquid-markdown-tables": "tsx src/tools/scripts/liquid-markdown-tables/index.ts",
+ "generate-article-api-docs": "tsx src/article-api/scripts/generate-api-docs.ts",
"generate-code-scanning-query-list": "tsx src/code-scanning/scripts/generate-code-scanning-query-list.ts",
"generate-content-linter-docs": "tsx src/content-linter/scripts/generate-docs.ts",
"move-content": "tsx src/content-render/scripts/move-content.js",
diff --git a/src/article-api/README.md b/src/article-api/README.md
index 3ba432822402..00891b3d31df 100644
--- a/src/article-api/README.md
+++ b/src/article-api/README.md
@@ -22,3 +22,128 @@ The `/api/article` endpoints return information about a page by `pathname`.
For internal folks ask in the Docs Engineering slack channel.
For open source folks, please open a discussion in the public repository.
+
+
+## Reference: API endpoints
+
+### GET /api/article/
+
+Get article metadata and content in a single object. Equivalent to calling `/article/meta` concatenated with `/article/body`.
+
+**Parameters**:
+- **pathname** (string) - Article path (e.g. '/en/get-started/article-name')
+
+**Returns**: (object) - JSON object with article metadata and content (`meta` and `body` keys)
+
+**Throws**:
+- (Error): 403 - If the article body cannot be retrieved. Reason is given in the error message.
+- (Error): 400 - If pathname parameter is invalid.
+- (Error): 404 - If the path is valid, but the page couldn't be resolved.
+
+**Example**:
+```
+❯ curl -s "https://docs.github.com/api/article?pathname=/en/get-started/start-your-journey/about-github-and-git"
+{
+ "meta": {
+ "title": "About GitHub and Git",
+ "intro": "You can use GitHub and Git to collaborate on work.",
+ "product": "Get started"
+ },
+ "body": "## About GitHub\n\nGitHub is a cloud-based platform where you can store, share, and work together with others to write code.\n\nStoring your code in a \"repository\" on GitHub allows you to:\n\n* **Showcase or share** your work.\n [...]"
+}
+```
+
+---
+
+### GET /api/article/body
+
+Get the contents of an article's body.
+
+**Parameters**:
+- **pathname** (string) - Article path (e.g. '/en/get-started/article-name')
+
+**Returns**: (string) - Article body content in markdown format.
+
+**Throws**:
+- (Error): 403 - If the article body cannot be retrieved. Reason is given in the error message.
+- (Error): 400 - If pathname parameter is invalid.
+- (Error): 404 - If the path is valid, but the page couldn't be resolved.
+
+**Example**:
+```
+❯ curl -s https://docs.github.com/api/article/body\?pathname=/en/get-started/start-your-journey/about-github-and-git
+## About GitHub
+
+GitHub is a cloud-based platform where you can store, share, and work together with others to write code.
+
+Storing your code in a "repository" on GitHub allows you to:
+[...]
+```
+
+---
+
+### GET /api/article/meta
+
+Get metadata about an article.
+
+**Parameters**:
+- **pathname** (string) - Article path (e.g. '/en/get-started/article-name')
+
+**Returns**: (object) - JSON object containing article metadata with title, intro, and product information.
+
+**Throws**:
+- (Error): 400 - If pathname parameter is invalid.
+- (Error): 404 - If the path is valid, but the page couldn't be resolved.
+
+**Example**:
+```
+❯ curl -s "https://docs.github.com/api/article/meta?pathname=/en/get-started/start-your-journey/about-github-and-git"
+{
+ "title": "About GitHub and Git",
+ "intro": "You can use GitHub and Git to collaborate on work.",
+ "product": "Get started",
+ "breadcrumbs": [
+ {
+ "href": "/en/get-started",
+ "title": "Get started"
+ },
+ {
+ "href": "/en/get-started/start-your-journey",
+ "title": "Start your journey"
+ },
+ {
+ "href": "/en/get-started/start-your-journey/about-github-and-git",
+ "title": "About GitHub and Git"
+ }
+ ]
+}
+```
+
+---
+
+### GET /api/pagelist/:lang/:productVersion
+
+A list of pages available for a fully qualified path containing the target language and product version.
+
+**Parameters**:
+- **lang** (string) - Path parameter for language code (e.g. 'en')
+- **productVersion** (string) - Path parameter for product version (e.g. 'free-pro-team@latest')
+
+**Returns**: (string) - List of paths matching the language and version
+
+**Throws**:
+- (Error): 400 - If language or version parameters are invalid. Reason is given in the error message.
+
+**Example**:
+```
+❯ curl -s https://docs.github.com/api/pagelist/en/free-pro-team@latest
+/en
+/en/search
+/en/get-started
+/en/get-started/start-your-journey
+/en/get-started/start-your-journey/about-github-and-git
+[...]
+```
+
+---
+
diff --git a/src/article-api/middleware/article.ts b/src/article-api/middleware/article.ts
index 642420b7376c..ca2394ea86ce 100644
--- a/src/article-api/middleware/article.ts
+++ b/src/article-api/middleware/article.ts
@@ -20,6 +20,25 @@ const router = express.Router()
// - pathValidationMiddleware ensures the path is properly structured and handles errors when it's not
// - pageValidationMiddleware fetches the page from the pagelist, returns 404 to the user if not found
+/**
+ * Get article metadata and content in a single object. Equivalent to calling `/article/meta` concatenated with `/article/body`.
+ * @route GET /api/article
+ * @param {string} pathname - Article path (e.g. '/en/get-started/article-name')
+ * @returns {object} JSON object with article metadata and content (`meta` and `body` keys)
+ * @throws {Error} 403 - If the article body cannot be retrieved. Reason is given in the error message.
+ * @throws {Error} 400 - If pathname parameter is invalid.
+ * @throws {Error} 404 - If the path is valid, but the page couldn't be resolved.
+ * @example
+ * ❯ curl -s "https://docs.github.com/api/article?pathname=/en/get-started/start-your-journey/about-github-and-git"
+ * {
+ * "meta": {
+ * "title": "About GitHub and Git",
+ * "intro": "You can use GitHub and Git to collaborate on work.",
+ * "product": "Get started"
+ * },
+ * "body": "## About GitHub\n\nGitHub is a cloud-based platform where you can store, share, and work together with others to write code.\n\nStoring your code in a \"repository\" on GitHub allows you to:\n\n* **Showcase or share** your work.\n [...]"
+ * }
+ */
router.get(
'/',
pathValidationMiddleware as RequestHandler,
@@ -43,6 +62,23 @@ router.get(
}),
)
+/**
+ * Get the contents of an article's body.
+ * @route GET /api/article/body
+ * @param {string} pathname - Article path (e.g. '/en/get-started/article-name')
+ * @returns {string} Article body content in markdown format.
+ * @throws {Error} 403 - If the article body cannot be retrieved. Reason is given in the error message.
+ * @throws {Error} 400 - If pathname parameter is invalid.
+ * @throws {Error} 404 - If the path is valid, but the page couldn't be resolved.
+ * @example
+ * ❯ curl -s https://docs.github.com/api/article/body\?pathname=/en/get-started/start-your-journey/about-github-and-git
+ * ## About GitHub
+ *
+ * GitHub is a cloud-based platform where you can store, share, and work together with others to write code.
+ *
+ * Storing your code in a "repository" on GitHub allows you to:
+ * [...]
+ */
router.get(
'/body',
pathValidationMiddleware as RequestHandler,
@@ -62,6 +98,35 @@ router.get(
}),
)
+/**
+ * Get metadata about an article.
+ * @route GET /api/article/meta
+ * @param {string} pathname - Article path (e.g. '/en/get-started/article-name')
+ * @returns {object} JSON object containing article metadata with title, intro, and product information.
+ * @throws {Error} 400 - If pathname parameter is invalid.
+ * @throws {Error} 404 - If the path is valid, but the page couldn't be resolved.
+ * @example
+ * ❯ curl -s "https://docs.github.com/api/article/meta?pathname=/en/get-started/start-your-journey/about-github-and-git"
+ * {
+ * "title": "About GitHub and Git",
+ * "intro": "You can use GitHub and Git to collaborate on work.",
+ * "product": "Get started",
+ * "breadcrumbs": [
+ * {
+ * "href": "/en/get-started",
+ * "title": "Get started"
+ * },
+ * {
+ * "href": "/en/get-started/start-your-journey",
+ * "title": "Start your journey"
+ * },
+ * {
+ * "href": "/en/get-started/start-your-journey/about-github-and-git",
+ * "title": "About GitHub and Git"
+ * }
+ * ]
+ * }
+ */
router.get(
'/meta',
pathValidationMiddleware as RequestHandler,
diff --git a/src/article-api/middleware/pagelist.ts b/src/article-api/middleware/pagelist.ts
index 4cb6c4613e07..f8f5bd666c4b 100644
--- a/src/article-api/middleware/pagelist.ts
+++ b/src/article-api/middleware/pagelist.ts
@@ -44,7 +44,22 @@ router.get(
}),
)
-// for a fully qualified path with language and product version, we'll serve up the pagelist
+/**
+ * A list of pages available for a fully qualified path containing the target language and product version.
+ * @route GET /api/pagelist
+ * @param {string} lang - Path parameter for language code (e.g. 'en')
+ * @param {string} productVersion - Path parameter for product version (e.g. 'free-pro-team@latest')
+ * @returns {string} List of paths matching the language and version
+ * @throws {Error} 400 - If language or version parameters are invalid. Reason is given in the error message.
+ * @example
+ * ❯ curl -s https://docs.github.com/api/pagelist/en/free-pro-team@latest
+ * /en
+ * /en/search
+ * /en/get-started
+ * /en/get-started/start-your-journey
+ * /en/get-started/start-your-journey/about-github-and-git
+ * [...]
+ */
router.get(
'/:lang/:productVersion',
pagelistValidationMiddleware as RequestHandler,
diff --git a/src/article-api/scripts/generate-api-docs.ts b/src/article-api/scripts/generate-api-docs.ts
new file mode 100644
index 000000000000..bddac9a9c10e
--- /dev/null
+++ b/src/article-api/scripts/generate-api-docs.ts
@@ -0,0 +1,174 @@
+import { writeFileSync, readFileSync, existsSync } from 'fs'
+
+function main({ sources, outputPath }: { sources: string[]; outputPath: string }): void {
+ // Extract API documentation comments from all source files
+ const allDocs = sources.flatMap((sourcePath) => extractApiDocs(sourcePath))
+
+ // Generate markdown
+ const markdown = generateMarkdown(allDocs)
+
+ // Update README
+ updateReadme(outputPath, markdown)
+
+ console.log('API documentation generated successfully!')
+}
+
+// Extract API docs from comments in the file
+function extractApiDocs(file: string): string[] {
+ const apiDocs: any[] = []
+
+ // get the content from the api routes
+ const content = readFileSync(file, 'utf8')
+
+ // Get the router method definitions with JSDOC-style comments
+ const routeRegex =
+ /\/\*\*\s*([\s\S]*?)\s*\*\/\s*router\.(get|post|put|delete)\s*\(\s*['"]([^'"]*)['"]/g
+ let match
+
+ while ((match = routeRegex.exec(content)) !== null) {
+ const commentBlock = match[1]
+ const method = match[2]
+ const path = match[3]
+
+ // The description is first line of the comment
+ const description = commentBlock
+ .trim()
+ .split('\n')[0]
+ .trim()
+ .replace(/^\*\s*/, '')
+
+ // Grab the other elements from the comment
+ // we currently support: params, returns, examples, throws
+ const params = extractParams(commentBlock)
+ const returns = extractReturns(commentBlock)
+ const examples = extractExample(commentBlock)
+ const throws = extractThrows(commentBlock)
+
+ apiDocs.push({
+ method, // GET, POST, etc
+ path: file.includes('article.ts') ? `/api/article${path}` : `/api/pagelist${path}`, // Prepend base path
+ description, // defined in the top of the block comment
+ params, // defined from @params
+ returns, // defined from @returns
+ examples, // defined from @example
+ throws, // defined from @throws
+ })
+ }
+
+ return apiDocs
+}
+
+function extractThrows(commentBlock: string): string[] {
+ const throwsRegex = /@throws\s+{([^}]+)}\s+([^\n]+)/g
+ const throws: string[] = []
+
+ let throwsMatch
+ while ((throwsMatch = throwsRegex.exec(commentBlock)) !== null) {
+ const type = throwsMatch[1]
+ const desc = throwsMatch[2].trim()
+ throws.push(`- (${type}): ${desc}`)
+ }
+
+ return throws
+}
+
+// Extract parameters from comment block
+function extractParams(commentBlock: string): string[] {
+ const paramRegex = /@param\s+{([^}]+)}\s+([^\s]+)\s+([^\n]+)/g
+ const params: string[] = []
+
+ let paramMatch
+ while ((paramMatch = paramRegex.exec(commentBlock)) !== null) {
+ const type = paramMatch[1]
+ const name = paramMatch[2]
+ const desc = paramMatch[3].trim()
+ params.push(`- **${name}** (${type}) ${desc}`)
+ }
+
+ return params
+}
+
+// Extract return info from comment block
+function extractReturns(commentBlock: string): string {
+ const returnMatch = commentBlock.match(/@returns\s+{([^}]+)}\s+([^\n]+)/)
+ if (returnMatch) {
+ const type = returnMatch[1]
+ const desc = returnMatch[2].trim()
+ return `**Returns**: (${type}) - ${desc}`
+ }
+ return ''
+}
+
+// Extract example from comment block
+function extractExample(commentBlock: string): string {
+ const exampleMatch = commentBlock.match(/@example\b([\s\S]*?)(?=\s*\*\s*@|\s*\*\/|$)/)
+ if (exampleMatch) {
+ // Clean up the example text by removing leading asterisks and spaces from each line, preserving tabs
+ return exampleMatch[1]
+ .split('\n')
+ .map((line) => line.replace(/^\s*\*\s?/, ''))
+ .join('\n')
+ .trim()
+ }
+ return ''
+}
+
+// Generate markdown from parsed documentation
+function generateMarkdown(apiDocs: any[]): string {
+ let markdown = '## Reference: API endpoints\n\n'
+
+ apiDocs.forEach((doc) => {
+ markdown += `### ${doc.method.toUpperCase()} ${doc.path}\n\n`
+ markdown += `${doc.description}\n\n`
+
+ if (doc.params.length > 0) {
+ markdown += '**Parameters**:\n'
+ markdown += doc.params.join('\n')
+ markdown += '\n\n'
+ }
+
+ if (doc.returns) {
+ markdown += `${doc.returns}\n\n`
+ }
+
+ if (doc.throws.length > 0) {
+ markdown += '**Throws**:\n'
+ markdown += doc.throws.join('\n')
+ markdown += '\n\n'
+ }
+
+ if (doc.examples) {
+ markdown += `**Example**:\n\`\`\`\n${doc.examples}\n\`\`\`\n\n`
+ }
+
+ markdown += '---\n\n'
+ })
+
+ return markdown
+}
+
+// Update README with generated documentation
+function updateReadme(readmePath: string, markdown: string): void {
+ if (existsSync(readmePath)) {
+ let readme = readFileSync(readmePath, 'utf8')
+
+ const placeholderComment = ``
+
+ // Replace API documentation section, or append to end
+ if (readme.includes(placeholderComment)) {
+ const pattern = new RegExp(placeholderComment + '[\\s\\S]*', 'g')
+ readme = readme.replace(pattern, placeholderComment + '\n' + markdown)
+ } else {
+ readme += '\n' + markdown
+ }
+
+ writeFileSync(readmePath, readme)
+ } else {
+ writeFileSync(readmePath, markdown)
+ }
+}
+
+main({
+ sources: ['src/article-api/middleware/article.ts', 'src/article-api/middleware/pagelist.ts'],
+ outputPath: 'src/article-api/README.md',
+})