@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
234 lines (226 loc) • 8.97 kB
JavaScript
import pkgJson from 'next/package.json' with { type: 'json' };
import { execSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import * as os from 'node:os';
import { dirname, join } from 'node:path';
import { pathToFileURL } from 'node:url';
// Read Next.js version to handle version-specific config
const nextMajorVersion = parseInt(pkgJson.version.split('.')[0], 10);
/**
* See the docs of the Netlify environment variables:
* https://docs.netlify.com/configure-builds/environment-variables/#build-metadata.
*
* A few comments:
* - process.env.CONTEXT === 'production' means that the branch in Netlify was configured as production.
* For example, the `master` branch of the Core team is considered a `production` build on Netlify based
* on https://app.netlify.com/sites/material-ui/settings/deploys#branches.
* - Each team has different site https://app.netlify.com/teams/mui/sites.
* The following logic must be compatible with all of them.
*/
let DEPLOY_ENV = 'development';
// Same as process.env.PULL_REQUEST_ID
if (process.env.CONTEXT === 'deploy-preview') {
DEPLOY_ENV = 'pull-request';
}
if (process.env.CONTEXT === 'production' || process.env.CONTEXT === 'branch-deploy') {
DEPLOY_ENV = 'production';
}
// The 'master' and 'next' branches are NEVER a production environment. We use these branches for staging.
if ((process.env.CONTEXT === 'production' || process.env.CONTEXT === 'branch-deploy') && (process.env.HEAD === 'master' || process.env.HEAD === 'next')) {
DEPLOY_ENV = 'staging';
}
/**
* ====================================================================================
*/
process.env.DEPLOY_ENV = DEPLOY_ENV;
const SHOW_PRIVATE_PAGES = String(process.env.DEPLOY_ENV !== 'production' && process.env.DEPLOY_ENV !== 'staging');
process.env.SHOW_PRIVATE_PAGES = SHOW_PRIVATE_PAGES;
/**
* URL prefix pointing at the source tree of the currently-deployed commit
* (e.g. `https://github.com/owner/repo/tree/<ref>/`). Used by demo
* factories to rewrite local `file://` URLs gathered at build time into
* hosted Git URLs.
*
* Resolution order:
* - Repository URL: `process.env.REPOSITORY_URL` (set by Netlify), falling
* back to the `repository` field of the nearest ancestor `package.json`.
* - Ref: `process.env.HEAD` (set by Netlify to the source branch name, even
* on deploy-previews where `BRANCH` is the unbrowsable `pull/<id>/head`)
* or `git rev-parse --abbrev-ref HEAD` locally — preferred so links keep
* tracking new commits on the branch. Falls back to
* `process.env.COMMIT_REF` (the deployed SHA) and finally to
* `git rev-parse HEAD`.
*
* Resolves to an empty string when neither source yields a value.
*/
const repoRootDir = findRepoRootDir();
// Stored as a `file://` URL (with exactly one trailing slash) so consumers
// can use it in isomorphic code without depending on Node's `url` module to
// convert from a filesystem path. We normalize via the `URL` object so a repo
// root that already ends in a separator (e.g. `/` or `C:\` at a drive root)
// doesn't produce a double-slash suffix.
const SOURCE_CODE_ROOT_DIR = repoRootDir ? toDirFileUrl(repoRootDir) : '';
const SOURCE_CODE_ROOT_URL = resolveSourceCodeRootUrl(repoRootDir);
process.env.SOURCE_CODE_ROOT_DIR = SOURCE_CODE_ROOT_DIR;
process.env.SOURCE_CODE_ROOT_URL = SOURCE_CODE_ROOT_URL;
function toDirFileUrl(dir) {
const url = pathToFileURL(dir);
if (!url.pathname.endsWith('/')) {
url.pathname = `${url.pathname}/`;
}
return url.href;
}
function resolveSourceCodeRootUrl(rootDir) {
const repositoryUrl = process.env.REPOSITORY_URL ?? (rootDir ? readRepositoryUrlFromPackageJson(rootDir) : undefined);
const headEnv = process.env.HEAD;
const ref = (headEnv && isUsableBranchRef(headEnv) ? headEnv : undefined) ?? readBranchFromGit() ?? process.env.COMMIT_REF ?? readCommitShaFromGit();
if (!repositoryUrl || !ref) {
return '';
}
const repoBase = repositoryUrl.replace(/^git\+/, '').replace(/\.git$/, '').replace(/\/$/, '');
return `${repoBase}/tree/${ref}/`;
}
function findRepoRootDir() {
// Prefer git: `git rev-parse --show-toplevel` returns the absolute path to
// the repository root regardless of which subdirectory we run from.
try {
const top = execSync('git rev-parse --show-toplevel', {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore']
}).trim();
if (top) {
return top;
}
} catch {
// git unavailable or not a repo, fall through to filesystem walk.
}
// Fallback: walk up looking for the root `package.json` (one with a
// `repository` field, indicating it's the project root rather than a
// workspace package).
let dir = process.cwd();
while (true) {
try {
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
if (pkg.repository) {
return dir;
}
} catch {
// package.json missing or unreadable at this level, keep walking up.
}
const parent = dirname(dir);
if (parent === dir) {
return undefined;
}
dir = parent;
}
}
function readRepositoryUrlFromPackageJson(rootDir) {
try {
const pkg = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8'));
const repo = pkg.repository;
return typeof repo === 'string' ? repo : repo?.url;
} catch {
return undefined;
}
}
function readBranchFromGit() {
try {
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore']
}).trim();
return isUsableBranchRef(branch) ? branch : undefined;
} catch {
return undefined;
}
}
/**
* A branch ref is usable as a `tree/<ref>` segment in a GitHub URL when it
* looks like an actual branch name. We reject:
* - empty strings
* - `HEAD` (detached checkout, e.g. on Netlify)
* - `pull/<id>/head` and `pull/<id>/merge` (Netlify deploy-preview refs that
* GitHub doesn't expose as browsable trees)
*/
function isUsableBranchRef(branch) {
if (!branch || branch === 'HEAD') {
return false;
}
if (/^pull\/\d+\/(head|merge)$/.test(branch)) {
return false;
}
return true;
}
function readCommitShaFromGit() {
try {
const sha = execSync('git rev-parse HEAD', {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore']
}).trim();
return sha || undefined;
} catch {
return undefined;
}
}
export function withDeploymentConfig(nextConfig) {
return {
trailingSlash: true,
reactStrictMode: true,
productionBrowserSourceMaps: true,
...nextConfig,
env: {
// production | staging | pull-request | development
DEPLOY_ENV,
SHOW_PRIVATE_PAGES,
...nextConfig.env,
// https://docs.netlify.com/configure-builds/environment-variables/#git-metadata
// reference ID (also known as "SHA" or "hash") of the commit we're building.
COMMIT_REF: process.env.COMMIT_REF,
// ID of the PR and the Deploy Preview it generated (for example, 1211)
PULL_REQUEST_ID: process.env.REVIEW_ID,
// This can be set manually in the .env to see the ads in dev mode.
ENABLE_AD_IN_DEV_MODE: process.env.ENABLE_AD_IN_DEV_MODE,
// URL representing the unique URL for an individual deploy, e.g.
// https://5b243e66dd6a547b4fee73ae--petsof.netlify.app
SITE_DEPLOY_URL: process.env.DEPLOY_URL,
// Name of the site, its Netlify subdomain; for example, material-ui-docs
SITE_NAME: process.env.SITE_NAME,
// URL for the linked Git repository.
REPOSITORY_URL: process.env.REPOSITORY_URL,
// Reference to check out after fetching changes from the Git repository.
// Can be useful for split testing.
BRANCH: process.env.BRANCH,
// URL prefix pointing at the source tree of the currently-deployed
// commit (e.g. `https://github.com/owner/repo/tree/<branch>/`). Derived
// from REPOSITORY_URL/BRANCH with package.json/git fallbacks.
SOURCE_CODE_ROOT_URL,
// `file://` URL (with trailing slash) of the repository root, used to
// translate `import.meta.url` file URLs into paths relative to the repo
// root before applying SOURCE_CODE_ROOT_URL.
SOURCE_CODE_ROOT_DIR,
// For template images
TEMPLATE_IMAGE_URL: ''
},
experimental: {
scrollRestoration: true,
workerThreads: false,
...(process.env.CI ? {
cpus: process.env.NEXT_PARALLELISM ? parseInt(process.env.NEXT_PARALLELISM, 10) : os.availableParallelism()
} : {}),
...nextConfig.experimental
},
...(nextMajorVersion < 16 ? {
// TODO remove this once all our projects are on Next.js 16+
// https://nextjs.org/blog/next-16
eslint: {
ignoreDuringBuilds: true,
...nextConfig.eslint
}
} : {}),
typescript: {
// Motivated by https://github.com/vercel/next.js/issues/7687
ignoreBuildErrors: true,
...nextConfig.typescript
}
};
}