UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

465 lines (461 loc) 20 kB
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 };