Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/actions/node-npm-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ runs:
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: 'package.json'
# Resolve from the Node manifest so CI uses the newest allowed version, not an older cached match.
check-latest: true
package-manager-cache: false
cache: ${{ steps.cache-node_modules.outputs.cache-hit != 'true' && 'npm' || '' }}

Expand Down
13 changes: 12 additions & 1 deletion .github/workflows/changelog-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,22 @@ jobs:
fs.writeFileSync('prompt.txt', userPrompt);
core.setOutput('date_str', dateStr);
# Use a subdirectory: checkout would clear the workspace and the prompt files above.
- name: Check out package.json for the Node version
if: steps.check_parent.outputs.has_parent == 'true' && steps.check_existing.outputs.exists == 'false'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: node-version-source
sparse-checkout: /package.json
sparse-checkout-cone-mode: false
persist-credentials: false

- name: Set up Node.js for Copilot CLI
if: steps.check_parent.outputs.has_parent == 'true' && steps.check_existing.outputs.exists == 'false'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
node-version-file: node-version-source/package.json
check-latest: true

- name: Install GitHub Copilot CLI
if: steps.check_parent.outputs.has_parent == 'true' && steps.check_existing.outputs.exists == 'false'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/package-lock-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'package.json'
check-latest: true
cache: npm

- name: Run check
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ready-for-doc-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'package.json'
check-latest: true
cache: npm

- name: Install dependencies
Expand Down
2 changes: 0 additions & 2 deletions data/release-notes/enterprise-server/3-18/15.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ sections:
When restoring from a backup snapshot, a large number of `mapper_parsing_exception` errors may be displayed.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
In a cluster, the host running restore requires access the storage nodes via their private IPs.
- |
Expand Down
2 changes: 2 additions & 0 deletions data/release-notes/enterprise-server/3-18/16.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ sections:
- |
To avoid data processing delays on instances with a large number of internal message topics, administrators can configure the network timeouts used during topic discovery via `app.github.topic-checker-connect-timeout-sec` and `app.github.topic-checker-socket-timeout-sec`. The default timeout has been increased from 10 to 30 seconds, and values between 1 and 300 seconds are accepted.
known_issues:
- |
After a restart, an instance can remain unavailable with "Starting..." displayed in the web interface. To recover, an administrator can connect using administrative SSH and rerun `ghe-config-apply`. This provides temporary recovery but does not prevent the issue from recurring. This issue is resolved in {% data variables.product.prodname_ghe_server %} 3.21.0 and later. [Updated: 2026-10-30]
- |
During an upgrade of GitHub Enterprise Server, custom firewall rules are removed. If you use custom firewall rules, you must reapply them after upgrading.
- |
Expand Down
2 changes: 0 additions & 2 deletions data/release-notes/enterprise-server/3-19/12.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ sections:
When restoring from a backup snapshot, a large number of `mapper_parsing_exception` errors may be displayed.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
In a cluster, the host running restore requires access the storage nodes via their private IPs.
- |
Expand Down
2 changes: 2 additions & 0 deletions data/release-notes/enterprise-server/3-19/13.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ sections:
- |
To avoid data processing delays on instances with a large number of internal message topics, administrators can configure the network timeouts used during topic discovery via `app.github.topic-checker-connect-timeout-sec` and `app.github.topic-checker-socket-timeout-sec`. The default timeout has been increased from 10 to 30 seconds, and values between 1 and 300 seconds are accepted.
known_issues:
- |
After a restart, an instance can remain unavailable with "Starting..." displayed in the web interface. To recover, an administrator can connect using administrative SSH and rerun `ghe-config-apply`. This provides temporary recovery but does not prevent the issue from recurring. This issue is resolved in {% data variables.product.prodname_ghe_server %} 3.21.0 and later. [Updated: 2026-10-30]
- |
During an upgrade of GitHub Enterprise Server, custom firewall rules are removed. If you use custom firewall rules, you must reapply them after upgrading.
- |
Expand Down
2 changes: 0 additions & 2 deletions data/release-notes/enterprise-server/3-20/8.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ sections:
When restoring data originally backed up from a 3.13 or greater appliance version, the Elasticsearch indices need to be reindexed before some of the data will show up. This happens via a scheduled job that runs on the first Saturday of each month. It can also be forced by running `/usr/local/share/enterprise/ghe-es-search-repair`.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
In a cluster, the host running restore requires access the storage nodes via their private IPs.
- |
Expand Down
2 changes: 2 additions & 0 deletions data/release-notes/enterprise-server/3-20/9.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ sections:
- |
To avoid data processing delays on instances with a large number of internal message topics, administrators can configure the network timeouts used during topic discovery via `app.github.topic-checker-connect-timeout-sec` and `app.github.topic-checker-socket-timeout-sec`. The default timeout has been increased from 10 to 30 seconds, and values between 1 and 300 seconds are accepted.
known_issues:
- |
After a restart, an instance can remain unavailable with "Starting..." displayed in the web interface. To recover, an administrator can connect using administrative SSH and rerun `ghe-config-apply`. This provides temporary recovery but does not prevent the issue from recurring. This issue is resolved in {% data variables.product.prodname_ghe_server %} 3.21.0 and later. [Updated: 2026-10-30]
- |
During an upgrade of GitHub Enterprise Server, custom firewall rules are removed. If you use custom firewall rules, you must reapply them after upgrading.
- |
Expand Down
2 changes: 0 additions & 2 deletions data/release-notes/enterprise-server/3-21/6.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ sections:
When restoring data originally backed up from a 3.13 or greater appliance version, the Elasticsearch indices need to be reindexed before some of the data will show up. This happens via a scheduled job that runs on the first Saturday of each month. It can also be forced by running `/usr/local/share/enterprise/ghe-es-search-repair`.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
In a cluster, the host running restore requires access the storage nodes via their private IPs.
- |
Expand Down
2 changes: 0 additions & 2 deletions data/release-notes/enterprise-server/3-22/1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ sections:
When restoring data originally backed up from a 3.13 or greater appliance version, the Elasticsearch indices need to be reindexed before some of the data will show up. This happens via a scheduled job that runs on the first Saturday of each month. It can also be forced by running `/usr/local/share/enterprise/ghe-es-search-repair`.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
When initializing a new GHES cluster, nodes with the `consul-server` role should be added to the cluster before adding additional nodes. Adding all nodes simultaneously creates a race condition between nomad server registration and nomad client registration.
- |
In a cluster, the host running restore requires access the storage nodes via their private IPs.
- |
Expand Down
11 changes: 9 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ const config: NextConfig = {
quietDeps: true,
silenceDeprecations: ['legacy-js-api', 'import', 'global-builtin', 'color-4-api'],
},
// Don't use automatic Next.js logging in dev unless the log level is `debug` or higher
// Don't use automatic Next.js request logging unless the log level is `debug` or higher.
// Our own request logger already covers it. `false` would also stop `[browser]` console forwarding.
// See `src/observability/logger/README.md` for log levels
logging: getLogLevelNumber() < 3 ? undefined : {},
logging: getLogLevelNumber() < 3 ? { incomingRequests: false } : {},
async rewrites() {
const DEFAULT_VERSION = 'free-pro-team@latest'
return productIds.map((productId) => {
Expand Down Expand Up @@ -101,6 +102,12 @@ const config: NextConfig = {
// the CDN marks the cached content as "fresh".
generateEtags: false,

experimental: {
// getServerSideProps returns rendered Markdown, so many pages exceed the 128 kB default.
// https://nextjs.org/docs/messages/large-page-data
largePageDataBytes: 1024 * 1024, // 1 MB
},

// Disable Next.js's in-memory data cache. We don't use ISR or cached
// fetch — all pages render via Express middleware with getServerSideProps.
// The default 50 MB cache is unnecessary overhead during a memory-leak
Expand Down
13 changes: 7 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@
"strip-ansi": "7.1.0",
"swr": "^2.4.0",
"tcp-port-used": "1.0.2",
"tsx": "^4.21.0",
"tsx": "^4.23.15",
"unified": "^11.0.5",
"unist-util-find": "^3.0.0",
"unist-util-visit": "^5.1.0",
Expand Down
5 changes: 2 additions & 3 deletions src/archives/middleware/archived-enterprise-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,16 +278,15 @@ export default async function archivedEnterpriseVersions(
versionSatisfiesRange(requestedVersion, `>=${firstReleaseStoredInBlobStorage}`) &&
versionSatisfiesRange(requestedVersion, `<=3.9`)
) {
// Fastly sets x-host, and GLB removes x-forwarded-host.
const host = req.get('x-host') || req.get('x-forwarded-host') || req.get('host')
const modifiedBody = body
.replaceAll(
`${OLD_AZURE_BLOB_ENTERPRISE_DIR}/${requestedVersion}/assets/cb-`,
`${ENTERPRISE_GH_PAGES_URL_PREFIX}${requestedVersion}/assets/cb-`,
)
.replaceAll(
`${OLD_AZURE_BLOB_ENTERPRISE_DIR}/${requestedVersion}/`,
`${req.protocol}://${host}/enterprise-server@${requestedVersion}/`,
// Root-relative, so request headers never end up in the page.
`/enterprise-server@${requestedVersion}/`,
)

return res.send(modifiedBody)
Expand Down
17 changes: 17 additions & 0 deletions src/archives/tests/deprecated-enterprise-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ describe('enterprise deprecation', () => {
expect(res.headers['set-cookie']).toBeUndefined()
})

test('does not put request host headers into deprecated Enterprise pages ( 3.2-3.9 )', async () => {
const res = await get(
'/en/enterprise-server@3.5/admin/overview/about-github-enterprise-server',
{
headers: {
'x-host': 'evil.example"><script>alert(1)</script>',
'x-forwarded-host': 'evil.example',
'x-forwarded-proto': 'javascript',
},
},
)
expect(res.statusCode).toBe(200)
expect(res.body).not.toContain('evil.example')
expect(res.body).not.toContain('javascript://')
expect(res.body).toContain('src="/enterprise-server@3.5/_next/')
})

test('handles requests for deprecated Enterprise pages ( <2.13 )', async () => {
expect(enterpriseServerReleases.deprecated.includes('2.12')).toBe(true)
const { $, res } = await getDOM('/enterprise/2.12/user/articles/about-branches')
Expand Down
9 changes: 8 additions & 1 deletion src/frame/lib/create-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const logger = createLogger(import.meta.url)

const isProduction = process.env.NODE_ENV === 'production'

// Development rebuilds the tree on every request, so each missing path warns once until it appears.
const warnedMissingPaths = new Set<string>()

export default async function createTree(
originalPath: string,
rootPath?: string,
Expand Down Expand Up @@ -41,12 +44,16 @@ export default async function createTree(
originalPath === 'content/early-access' ||
originalPath.startsWith('content/early-access/')
) {
logger.warn(msg, { path: originalPath })
if (!warnedMissingPaths.has(originalPath)) {
warnedMissingPaths.add(originalPath)
logger.warn(msg, { path: originalPath })
}
return
}
throw new Error(msg)
}
}
warnedMissingPaths.delete(originalPath)

const relativePath = filepath.replace(`${basePath}/`, '')

Expand Down
1 change: 0 additions & 1 deletion src/frame/middleware/helmet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const DEFAULT_OPTIONS = {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'none'"],
prefetchSrc: ["'self'"],
// Safari local development needs ws: for Next.js hot module reloading.
connectSrc: ["'self'", 'https://collector.githubapp.com', isDev && 'ws:'].filter(
Boolean,
Expand Down
6 changes: 5 additions & 1 deletion src/frame/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import features from '@/versions/middleware/features'
import productGroups from './context/product-groups'
import featuredLinks from '@/landings/middleware/featured-links'
import journeyTrack from '@/journeys/middleware/journey-track'
import next from './next'
import next, { renderNextDevEndpoints } from './next'
import renderPage from './render-page'
import assetPreprocessing from '@/assets/middleware/asset-preprocessing'
import archivedAssetRedirects from '@/archives/middleware/archived-asset-redirects'
Expand Down Expand Up @@ -160,6 +160,10 @@ export default function index(app: Express) {
)
}

if (process.env.NODE_ENV === 'development') {
app.use(renderNextDevEndpoints)
}

app.use(shielding)
app.use(handleNextDataPath)

Expand Down
9 changes: 9 additions & 0 deletions src/frame/middleware/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ function renderPageWithNext(req: ExtendedRequest, res: Response, nextFn: NextFun
return nextFn()
}

// Dev overlay endpoints, like /__nextjs_original-stack-frames, read the raw body, so they run before express.json().
export function renderNextDevEndpoints(req: ExtendedRequest, res: Response, nextFn: NextFunction) {
if (req.path.startsWith('/__nextjs_')) {
return nextHandleRequest(req, res)
}

return nextFn()
}

export default renderPageWithNext
2 changes: 1 addition & 1 deletion src/frame/start-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import createApp from './lib/app'
import warmServer from './lib/warm-server'
import { createLogger } from '@/observability/logger'

dotenv.config()
dotenv.config({ quiet: true })

checkNodeVersion()

Expand Down
15 changes: 14 additions & 1 deletion src/frame/tests/create-tree.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import fs from 'fs/promises'

import { describe, expect, test } from 'vitest'
import { describe, expect, test, vi } from 'vitest'

import createTree from '@/frame/lib/create-tree'
import { setCategoryApplicableVersions } from '@/frame/lib/page-data'
import type { Page, UnversionedTree } from '@/types'

const logger = vi.hoisted(() => ({ error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() }))
vi.mock('@/observability/logger', () => ({ createLogger: () => logger }))

const contentDir = 'src/fixtures/fixtures/content'

function flattenPages(tree: UnversionedTree): Page[] {
Expand Down Expand Up @@ -55,6 +58,16 @@ describe('createTree', () => {
expect(reused.length).toBe(declared.length - 1)
})

test('warns once for missing early-access content across rebuilds', async () => {
const missingPath = 'content/early-access/does-not-exist'
logger.warn.mockClear()

expect(await createTree(missingPath)).toBeUndefined()
expect(await createTree(missingPath)).toBeUndefined()

expect(logger.warn).toHaveBeenCalledTimes(1)
})

test('stores the file mtime on the page', async () => {
const tree = (await createTree(contentDir)) as UnversionedTree
const { mtimeMs } = await fs.stat(tree.page.fullPath)
Expand Down
Loading
Loading