@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
465 lines (461 loc) • 20 kB
JavaScript
import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
import { writeFile, mkdir, access, readFile, constants } from 'fs/promises';
import { dirname, join, relative, basename, parse } from 'path';
/**
* Creates a relative import path from source file to target file
*/
function createRelativePath(fromFile, toFile) {
var relativePath = relative(dirname(fromFile), toFile);
// Ensure the path starts with ./ for relative imports
return relativePath.startsWith('.') ? relativePath : "./".concat(relativePath);
}
/**
* Creates a structured path for generated externals files based on the source file location
* Example: app/components/checkbox/demos/basic/index.ts -> generated/demo-externals/app/components/checkbox/demos/basic.tsx
*/
function createGeneratedFilePath(_x, _x2) {
return _createGeneratedFilePath.apply(this, arguments);
}
/**
* Determines the appropriate build directory for temporary files
* to avoid checking generated files into source control.
*/
function _createGeneratedFilePath() {
_createGeneratedFilePath = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(resourcePath, projectRoot) {
var relativePath, parsedPath, targetFilename, targetDir, parentDir, generatedFilePath, relativeImportPath;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
// Remove the project root from the resource path to get the relative path
relativePath = resourcePath.replace(projectRoot, '').replace(/^\//, ''); // Parse the path to determine the target filename
parsedPath = parse(relativePath);
// If the file is named 'index', use the parent directory name instead
if (parsedPath.name === 'index') {
parentDir = basename(parsedPath.dir);
targetFilename = "".concat(parentDir, ".tsx");
// Remove the last directory level since we're using it as the filename
targetDir = dirname(parsedPath.dir);
} else {
targetFilename = "".concat(parsedPath.name, ".tsx");
targetDir = parsedPath.dir;
}
// Create the generated file path
generatedFilePath = join(projectRoot, 'generated', 'demo-externals', targetDir, targetFilename); // Create relative import path from source file to generated file
relativeImportPath = createRelativePath(resourcePath, generatedFilePath);
return _context.abrupt("return", {
filePath: generatedFilePath,
relativePath: relativeImportPath
});
case 6:
case "end":
return _context.stop();
}
}, _callee);
}));
return _createGeneratedFilePath.apply(this, arguments);
}
function getBuildDirectory(_x3) {
return _getBuildDirectory.apply(this, arguments);
}
/**
* Finds the project root by looking for 'app' directory or common project markers
*/
function _getBuildDirectory() {
_getBuildDirectory = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee3(resourcePath) {
var projectRoot, buildDirs, _tryDirectories, result, fallbackDir;
return _regeneratorRuntime().wrap(function _callee3$(_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
_context3.next = 2;
return findProjectRoot(resourcePath);
case 2:
projectRoot = _context3.sent;
// Common build directory patterns, in order of preference
buildDirs = ['.next/cache/externals',
// Next.js cache directory
'.turbo/cache/externals',
// Turbopack cache directory
'node_modules/.cache/mui-docs-infra/externals',
// Node modules cache
'.cache/mui-docs-infra/externals' // Generic cache directory
]; // Recursive function to try each directory
_tryDirectories = /*#__PURE__*/function () {
var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(dirs) {
var index,
buildDir,
fullPath,
_args2 = arguments;
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
index = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : 0;
if (!(index >= dirs.length)) {
_context2.next = 3;
break;
}
return _context2.abrupt("return", null);
case 3:
buildDir = dirs[index];
fullPath = join(projectRoot, buildDir);
_context2.prev = 5;
_context2.next = 8;
return mkdir(fullPath, {
recursive: true
});
case 8:
return _context2.abrupt("return", fullPath);
case 11:
_context2.prev = 11;
_context2.t0 = _context2["catch"](5);
return _context2.abrupt("return", _tryDirectories(dirs, index + 1));
case 14:
case "end":
return _context2.stop();
}
}, _callee2, null, [[5, 11]]);
}));
return function tryDirectories(_x8) {
return _ref.apply(this, arguments);
};
}(); // Try each directory in sequence
_context3.next = 7;
return _tryDirectories(buildDirs);
case 7:
result = _context3.sent;
if (!result) {
_context3.next = 10;
break;
}
return _context3.abrupt("return", result);
case 10:
// Fallback to .cache directory in project root
fallbackDir = join(projectRoot, '.cache/mui-docs-infra/externals');
_context3.next = 13;
return mkdir(fallbackDir, {
recursive: true
});
case 13:
return _context3.abrupt("return", fallbackDir);
case 14:
case "end":
return _context3.stop();
}
}, _callee3);
}));
return _getBuildDirectory.apply(this, arguments);
}
function findProjectRoot(_x4) {
return _findProjectRoot.apply(this, arguments);
}
/**
* Ensures that the generated demo externals directory is added to .gitignore
* to prevent accidentally committing these generated files.
*/
function _findProjectRoot() {
_findProjectRoot = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee8(startPath) {
var rootDir, pathExists, _searchUpwards, startDir, appDirResult, markersResult;
return _regeneratorRuntime().wrap(function _callee8$(_context8) {
while (1) switch (_context8.prev = _context8.next) {
case 0:
rootDir = '/'; // Unix filesystem root
// Helper function to check if a path exists
pathExists = /*#__PURE__*/function () {
var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee4(path) {
return _regeneratorRuntime().wrap(function _callee4$(_context4) {
while (1) switch (_context4.prev = _context4.next) {
case 0:
_context4.prev = 0;
_context4.next = 3;
return access(path, constants.F_OK);
case 3:
return _context4.abrupt("return", true);
case 6:
_context4.prev = 6;
_context4.t0 = _context4["catch"](0);
return _context4.abrupt("return", false);
case 9:
case "end":
return _context4.stop();
}
}, _callee4, null, [[0, 6]]);
}));
return function pathExists(_x9) {
return _ref2.apply(this, arguments);
};
}(); // Recursive function to search upwards
_searchUpwards = /*#__PURE__*/function () {
var _ref3 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee5(currentDir, searchFn) {
var parentDir;
return _regeneratorRuntime().wrap(function _callee5$(_context5) {
while (1) switch (_context5.prev = _context5.next) {
case 0:
if (!(currentDir === rootDir)) {
_context5.next = 2;
break;
}
return _context5.abrupt("return", null);
case 2:
_context5.next = 4;
return searchFn(currentDir);
case 4:
if (!_context5.sent) {
_context5.next = 6;
break;
}
return _context5.abrupt("return", currentDir);
case 6:
parentDir = dirname(currentDir);
if (!(parentDir === currentDir)) {
_context5.next = 9;
break;
}
return _context5.abrupt("return", null);
case 9:
return _context5.abrupt("return", _searchUpwards(parentDir, searchFn));
case 10:
case "end":
return _context5.stop();
}
}, _callee5);
}));
return function searchUpwards(_x0, _x1) {
return _ref3.apply(this, arguments);
};
}();
startDir = dirname(startPath); // First, try to find the 'app' directory
_context8.next = 6;
return _searchUpwards(startDir, /*#__PURE__*/function () {
var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee6(dir) {
var appPath;
return _regeneratorRuntime().wrap(function _callee6$(_context6) {
while (1) switch (_context6.prev = _context6.next) {
case 0:
appPath = join(dir, 'app');
return _context6.abrupt("return", pathExists(appPath));
case 2:
case "end":
return _context6.stop();
}
}, _callee6);
}));
return function (_x10) {
return _ref4.apply(this, arguments);
};
}());
case 6:
appDirResult = _context8.sent;
if (!appDirResult) {
_context8.next = 9;
break;
}
return _context8.abrupt("return", appDirResult);
case 9:
_context8.next = 11;
return _searchUpwards(startDir, /*#__PURE__*/function () {
var _ref5 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee7(dir) {
var markers, checks;
return _regeneratorRuntime().wrap(function _callee7$(_context7) {
while (1) switch (_context7.prev = _context7.next) {
case 0:
markers = ['package.json', '.git', 'next.config.js', 'next.config.mjs', 'turbo.json']; // Check if any marker exists in this directory
_context7.next = 3;
return Promise.all(markers.map(function (marker) {
return pathExists(join(dir, marker));
}));
case 3:
checks = _context7.sent;
return _context7.abrupt("return", checks.some(function (exists) {
return exists;
}));
case 5:
case "end":
return _context7.stop();
}
}, _callee7);
}));
return function (_x11) {
return _ref5.apply(this, arguments);
};
}());
case 11:
markersResult = _context8.sent;
if (!markersResult) {
_context8.next = 14;
break;
}
return _context8.abrupt("return", markersResult);
case 14:
return _context8.abrupt("return", dirname(startPath));
case 15:
case "end":
return _context8.stop();
}
}, _callee8);
}));
return _findProjectRoot.apply(this, arguments);
}
function ensureGitignoreEntry(_x5) {
return _ensureGitignoreEntry.apply(this, arguments);
}
/**
* Emits an externals provider file using the best available method:
* 1. Use emitFile if available (webpack)
* 2. Fall back to structured filesystem writing to generated/demo-externals/ (turbopack/other bundlers)
*
* @param loaderContext - The loader context with emitFile capability
* @param externalsProviderInfo - The externals provider file information
* @returns The import path to use for the generated file
*/
function _ensureGitignoreEntry() {
_ensureGitignoreEntry = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee9(projectRoot) {
var gitignorePath, entryToAdd, whitelistPattern, whitelistComment, gitignoreContent, needsUpdate, isExplicitlyWhitelisted, gitignoreExists, lines, hasIgnoreEntry, newContent;
return _regeneratorRuntime().wrap(function _callee9$(_context9) {
while (1) switch (_context9.prev = _context9.next) {
case 0:
gitignorePath = join(projectRoot, '.gitignore');
entryToAdd = '/generated/demo-externals';
whitelistPattern = '!/generated/demo-externals';
whitelistComment = '# mui-docs-infra: allow generated demo externals';
gitignoreContent = '';
needsUpdate = false;
isExplicitlyWhitelisted = false; // Check if .gitignore exists and read it
gitignoreExists = false;
_context9.prev = 8;
_context9.next = 11;
return access(gitignorePath, constants.F_OK);
case 11:
gitignoreExists = true;
_context9.next = 17;
break;
case 14:
_context9.prev = 14;
_context9.t0 = _context9["catch"](8);
// File doesn't exist
gitignoreExists = false;
case 17:
if (!gitignoreExists) {
_context9.next = 32;
break;
}
_context9.prev = 18;
_context9.next = 21;
return readFile(gitignorePath, 'utf8');
case 21:
gitignoreContent = _context9.sent;
// Check if the user has explicitly whitelisted the directory
lines = gitignoreContent.split('\n');
isExplicitlyWhitelisted = lines.some(function (line) {
var trimmed = line.trim();
return trimmed === whitelistPattern || trimmed === whitelistComment || trimmed.includes('mui-docs-infra: allow generated demo externals');
});
if (!isExplicitlyWhitelisted) {
// Check if the ignore entry already exists
hasIgnoreEntry = lines.some(function (line) {
var trimmed = line.trim();
return trimmed === entryToAdd || trimmed === 'generated/demo-externals' || trimmed === '/generated/demo-externals/' || trimmed === 'generated/demo-externals/';
});
if (!hasIgnoreEntry) {
needsUpdate = true;
}
}
_context9.next = 30;
break;
case 27:
_context9.prev = 27;
_context9.t1 = _context9["catch"](18);
// If we can't read the file, we'll create a new one (unless explicitly whitelisted)
if (!isExplicitlyWhitelisted) {
needsUpdate = true;
}
case 30:
_context9.next = 33;
break;
case 32:
// No .gitignore exists, we need to create one
needsUpdate = true;
case 33:
if (!(needsUpdate && !isExplicitlyWhitelisted)) {
_context9.next = 44;
break;
}
// Add the entry to .gitignore
newContent = gitignoreContent ? "".concat(gitignoreContent.endsWith('\n') ? '' : '\n').concat(entryToAdd, "\n") : "".concat(entryToAdd, "\n");
_context9.prev = 35;
_context9.next = 38;
return writeFile(gitignorePath, gitignoreContent + newContent);
case 38:
console.warn("[mui-docs-infra] Added '".concat(entryToAdd, "' to .gitignore to prevent committing generated externals files. ") + "If you want to commit these files, add '".concat(whitelistPattern, "' or '").concat(whitelistComment, "' to your .gitignore."));
_context9.next = 44;
break;
case 41:
_context9.prev = 41;
_context9.t2 = _context9["catch"](35);
console.warn("[mui-docs-infra] Could not update .gitignore file at ".concat(gitignorePath, ". ") + "Consider manually adding '".concat(entryToAdd, "' to prevent committing generated files. Error: ").concat(_context9.t2));
case 44:
case "end":
return _context9.stop();
}
}, _callee9, null, [[8, 14], [18, 27], [35, 41]]);
}));
return _ensureGitignoreEntry.apply(this, arguments);
}
export function emitExternalsProvider(_x6, _x7) {
return _emitExternalsProvider.apply(this, arguments);
}
// Export helper functions for testing
function _emitExternalsProvider() {
_emitExternalsProvider = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee0(loaderContext, externalsProviderInfo) {
var fileName, projectRoot, _yield$createGenerate, generatedFilePath, relativePath;
return _regeneratorRuntime().wrap(function _callee0$(_context0) {
while (1) switch (_context0.prev = _context0.next) {
case 0:
if (!loaderContext.emitFile) {
_context0.next = 4;
break;
}
// Extract just the filename from the full path
fileName = externalsProviderInfo.fileName.split('/').pop() || externalsProviderInfo.fileName;
loaderContext.emitFile(fileName, externalsProviderInfo.content);
// Return the relative path for import
return _context0.abrupt("return", externalsProviderInfo.relativePath);
case 4:
_context0.next = 6;
return findProjectRoot(loaderContext.resourcePath);
case 6:
projectRoot = _context0.sent;
_context0.next = 9;
return createGeneratedFilePath(loaderContext.resourcePath, projectRoot);
case 9:
_yield$createGenerate = _context0.sent;
generatedFilePath = _yield$createGenerate.filePath;
relativePath = _yield$createGenerate.relativePath;
_context0.next = 14;
return mkdir(dirname(generatedFilePath), {
recursive: true
});
case 14:
_context0.next = 16;
return ensureGitignoreEntry(projectRoot);
case 16:
_context0.next = 18;
return writeFile(generatedFilePath, externalsProviderInfo.content);
case 18:
// Add dependency so bundler watches for changes
loaderContext.addDependency(generatedFilePath);
return _context0.abrupt("return", relativePath);
case 20:
case "end":
return _context0.stop();
}
}, _callee0);
}));
return _emitExternalsProvider.apply(this, arguments);
}
export var testHelpers = {
createRelativePath: createRelativePath,
createGeneratedFilePath: createGeneratedFilePath,
getBuildDirectory: getBuildDirectory,
findProjectRoot: findProjectRoot,
ensureGitignoreEntry: ensureGitignoreEntry
};