diff --git a/.changeset/cold-singers-shake.md b/.changeset/cold-singers-shake.md new file mode 100644 index 00000000..ad90301c --- /dev/null +++ b/.changeset/cold-singers-shake.md @@ -0,0 +1,5 @@ +--- +"mdxts": minor +--- + +Uses `posix.sep` and normalizes `createSource` glob patterns. diff --git a/packages/create-mdxts/src/utils.ts b/packages/create-mdxts/src/utils.ts index b110b646..a8d9b379 100644 --- a/packages/create-mdxts/src/utils.ts +++ b/packages/create-mdxts/src/utils.ts @@ -1,7 +1,7 @@ import { stdin, stdout } from 'node:process' import { createInterface } from 'node:readline/promises' -import chalk from 'chalk' import { sep } from 'node:path' +import chalk from 'chalk' export class Log { static base = 'mdxts: ' diff --git a/packages/mdxts/src/components/CodeBlock/get-tokens.ts b/packages/mdxts/src/components/CodeBlock/get-tokens.ts index 267588f2..9df19533 100644 --- a/packages/mdxts/src/components/CodeBlock/get-tokens.ts +++ b/packages/mdxts/src/components/CodeBlock/get-tokens.ts @@ -2,7 +2,7 @@ import type { bundledLanguages, bundledThemes } from 'shiki/bundle/web' import type { SourceFile, Diagnostic, ts } from 'ts-morph' import { Node, SyntaxKind } from 'ts-morph' import { getDiagnosticMessageText } from '@tsxmod/utils' -import { join, sep } from 'node:path' +import { join, posix } from 'node:path' import { findRoot } from '@manypkg/find-root' import chalk from 'chalk' @@ -447,7 +447,7 @@ function throwDiagnosticErrors( tokens: Token[][], sourcePath?: string | false ) { - const workingDirectory = join(process.cwd(), 'mdxts', sep) + const workingDirectory = join(process.cwd(), 'mdxts', posix.sep) const filePath = sourceFile.getFilePath().replace(workingDirectory, '') const errorMessages = diagnostics.map((diagnostic) => { const message = getDiagnosticMessageText(diagnostic.getMessageText()) diff --git a/packages/mdxts/src/components/CodeBlock/parse-source-text-metadata.ts b/packages/mdxts/src/components/CodeBlock/parse-source-text-metadata.ts index 1ef8a492..8146e352 100644 --- a/packages/mdxts/src/components/CodeBlock/parse-source-text-metadata.ts +++ b/packages/mdxts/src/components/CodeBlock/parse-source-text-metadata.ts @@ -1,5 +1,5 @@ import crypto from 'node:crypto' -import { join, sep, isAbsolute } from 'node:path' +import { join, posix, isAbsolute } from 'node:path' import { readFile } from 'node:fs/promises' import type { SourceFile } from 'ts-morph' import { format, resolveConfig } from 'prettier' @@ -206,7 +206,7 @@ export async function parseSourceTextMetadata({ } const filenameLabel = (filenameProp || filename) - .replace(join('mdxts', sep), '') // Remove mdxts/ prefix + .replace(join('mdxts', posix.sep), '') // Remove mdxts/ prefix .replace(/\d+\./, '') // Remove ordered number prefix return { diff --git a/packages/mdxts/src/index.ts b/packages/mdxts/src/index.ts index ccfb8f52..844bf4fc 100644 --- a/packages/mdxts/src/index.ts +++ b/packages/mdxts/src/index.ts @@ -1,7 +1,7 @@ import parseTitle from 'title' import * as React from 'react' import type { ComponentType } from 'react' -import { basename, dirname, extname, join, resolve, sep } from 'node:path' +import { basename, dirname, extname, join, posix } from 'node:path' import { Feed } from 'feed' import { Project } from 'ts-morph' import { getDiagnosticMessageText } from '@tsxmod/utils' @@ -158,14 +158,6 @@ export function createSource< ) } - /** Convert all modules to absolute paths. */ - allModules = Object.fromEntries( - Object.entries(allModules).map(([pathname, moduleImport]) => [ - resolve(process.cwd(), pathname), - moduleImport, - ]) - ) - const { baseDirectory = '', basePathname = '', @@ -251,7 +243,7 @@ export function createSource< pathIndex++ ) { const currentPath = filteredDataKeys[pathIndex] - const pathParts = currentPath.split(sep).filter(Boolean) + const pathParts = currentPath.split(posix.sep).filter(Boolean) let nodes = tree for ( @@ -260,8 +252,8 @@ export function createSource< pathPartIndex++ ) { const pathname = join( - sep, - pathParts.slice(0, pathPartIndex + 1).join(sep) + posix.sep, + pathParts.slice(0, pathPartIndex + 1).join(posix.sep) ) const segment = pathParts[pathPartIndex] let node = nodes.find((node) => node.segment === segment) @@ -291,7 +283,7 @@ export function createSource< nextPathname.startsWith(pathname) && allFilteredData[nextPathname] !== undefined ) { - node.pathname = join(sep, nextPathname) + node.pathname = join(posix.sep, nextPathname) break } } @@ -311,7 +303,7 @@ export function createSource< const allPaths = filteredDataKeys.map((pathname) => pathname // Split pathname into an array - .split(sep) + .split(posix.sep) // Remove empty strings .filter(Boolean) ) @@ -346,18 +338,18 @@ export function createSource< } let stringPathname = join( - sep, - Array.isArray(pathname) ? pathname.join(sep) : pathname + posix.sep, + Array.isArray(pathname) ? pathname.join(posix.sep) : pathname ) let data = allFilteredData[stringPathname] // If no data was found, try to find it by the base pathname. if (data === undefined && basePathname) { stringPathname = join( - sep, + posix.sep, basePathname, - sep, - Array.isArray(pathname) ? pathname.join(sep) : pathname + posix.sep, + Array.isArray(pathname) ? pathname.join(posix.sep) : pathname ) data = allFilteredData[stringPathname] } @@ -560,8 +552,8 @@ export function mergeSources< const allData = all() const stringPathname = join( - sep, - Array.isArray(pathname) ? pathname.join(sep) : pathname + posix.sep, + Array.isArray(pathname) ? pathname.join(posix.sep) : pathname ) const currentIndex = allData.findIndex( (data) => data.pathname === stringPathname diff --git a/packages/mdxts/src/loader/index.ts b/packages/mdxts/src/loader/index.ts index 06bb0477..5a06020e 100644 --- a/packages/mdxts/src/loader/index.ts +++ b/packages/mdxts/src/loader/index.ts @@ -1,5 +1,5 @@ import * as webpack from 'webpack' -import { dirname, join, relative, resolve, sep } from 'node:path' +import { dirname, join, relative, resolve, posix, sep } from 'node:path' import { glob } from 'fast-glob' import globParent from 'glob-parent' import { Node, Project, SyntaxKind } from 'ts-morph' @@ -72,14 +72,18 @@ export default async function loader( const globPattern = firstArgument.getLiteralText() const globDirectory = globParent(globPattern) const baseGlobPattern = dirname(globPattern) - const isMdxPattern = globPattern.split(sep).at(-1)?.includes('mdx') + const isMdxPattern = globPattern + .split(posix.sep) + .at(-1) + ?.includes('mdx') + const filePatterns = isMdxPattern + ? [globPattern] + : [ + join(baseGlobPattern, sep, '*.examples.{ts,tsx}'), + join(baseGlobPattern, sep, 'examples', sep, '*.{ts,tsx}'), + ] let filePaths = await glob( - isMdxPattern - ? globPattern - : [ - join(baseGlobPattern, sep, '*.examples.{ts,tsx}'), - join(baseGlobPattern, sep, 'examples', sep, '*.{ts,tsx}'), - ], + filePatterns.map((filePath) => filePath.split(sep).join(posix.sep)), { cwd: workingDirectory } ) @@ -88,13 +92,19 @@ export default async function loader( /** Search for MDX files named the same as the source files (e.g. `Button.mdx` for `Button.tsx`) */ if (!isMdxPattern) { - const allSourceFilePaths = await glob(globPattern, { - cwd: workingDirectory, - ignore: ['**/*.examples.(ts|tsx)'], - }) - const allMdxFilePaths = await glob(`${baseGlobPattern}/*.mdx`, { - cwd: workingDirectory, - }) + const allSourceFilePaths = await glob( + globPattern.split(sep).join(posix.sep), + { + cwd: workingDirectory, + ignore: ['**/*.examples.(ts|tsx)'], + } + ) + const allMdxFilePaths = await glob( + join(`${baseGlobPattern}`, sep, `*.mdx`) + .split(sep) + .join(posix.sep), + { cwd: workingDirectory } + ) const allPaths = [...allSourceFilePaths, ...allMdxFilePaths] if (allPaths.length === 0) { @@ -135,7 +145,8 @@ export default async function loader( return isExported }) .forEach((sourceFilePath) => { - const sourceFilename = sourceFilePath.split(sep).pop() ?? '' + const sourceFilename = + sourceFilePath.split(posix.sep).pop() ?? '' const mdxFilePath = sourceFilename.includes('index') ? join(dirname(sourceFilePath), 'README.mdx') : sourceFilePath.replace( @@ -161,11 +172,10 @@ export default async function loader( const relativeFilePath = relative(workingDirectory, filePath) const normalizedRelativePath = relativeFilePath.startsWith('.') ? relativeFilePath - : `.${sep}${relativeFilePath}` + : `.${posix.sep}${relativeFilePath}` return `'${filePath}': () => import('${normalizedRelativePath}')` }) .join(', ')}}` - const argumentCount = createSourceCall.getArguments().length const createSourceCallArguments = [] diff --git a/packages/mdxts/src/mdx-plugins/remark/transform-relative-links.ts b/packages/mdxts/src/mdx-plugins/remark/transform-relative-links.ts index a4596e87..08e05477 100644 --- a/packages/mdxts/src/mdx-plugins/remark/transform-relative-links.ts +++ b/packages/mdxts/src/mdx-plugins/remark/transform-relative-links.ts @@ -1,5 +1,5 @@ import type { Root, Link } from 'mdast' -import { sep } from 'node:path' +import { posix } from 'node:path' /** Reformat all relative links that use ordered numbers and extensions. */ export function transformRelativeLinks() { @@ -11,11 +11,11 @@ export function transformRelativeLinks() { return } - const segments = node.url.split(sep) + const segments = node.url.split(posix.sep) for (let index = 0; index < segments.length; index++) { segments[index] = segments[index].replace(/^\d+\./, '') } - node.url = segments.join(sep).replace(/\.mdx?$/, '') + node.url = segments.join(posix.sep).replace(/\.mdx?$/, '') }) } } diff --git a/packages/mdxts/src/utils/file-path-to-pathname.ts b/packages/mdxts/src/utils/file-path-to-pathname.ts index 0048a488..94312fab 100644 --- a/packages/mdxts/src/utils/file-path-to-pathname.ts +++ b/packages/mdxts/src/utils/file-path-to-pathname.ts @@ -1,4 +1,4 @@ -import { join, resolve, sep } from 'node:path' +import { join, resolve, posix } from 'node:path' import slugify from '@sindresorhus/slugify' /** Converts a file system path to a URL-friendly pathname. */ @@ -12,9 +12,9 @@ export function filePathToPathname( function createPathame(filePath: string) { return basePathname ? basePathname === filePath - ? join(sep, basePathname) - : join(sep, basePathname, filePath) - : join(sep, filePath) + ? join(posix.sep, basePathname) + : join(posix.sep, basePathname, filePath) + : join(posix.sep, filePath) } // Convert relative paths to absolute paths @@ -25,15 +25,22 @@ export function filePathToPathname( const [baseDirectoryPath, baseFilePath] = baseDirectory ? filePath.split(baseDirectory) : ['', filePath] + + if (baseFilePath === undefined) { + throw new Error( + `Cannot determine base path for file path "${filePath}" at base directory "${baseDirectory}".` + ) + } + let parsedFilePath = baseFilePath // Remove leading separator "./" .replace(/^\.\//, '') // Remove leading sorting number "01." - .replace(/\/\d+\./g, sep) + .replace(/\/\d+\./g, posix.sep) // Remove working directory .replace( baseDirectory - ? resolve(process.cwd(), baseDirectoryPath, sep) + ? resolve(process.cwd(), baseDirectoryPath, posix.sep) : process.cwd(), '' ) @@ -42,7 +49,7 @@ export function filePathToPathname( // Remove trailing "/readme" or "/index" .replace(/\/(readme|index)$/i, '') - const segments = parsedFilePath.split(sep) + const segments = parsedFilePath.split(posix.sep) // Remove duplicate segment if last directory name matches file name (e.g. "Button/Button.tsx") if ( @@ -56,7 +63,7 @@ export function filePathToPathname( parsedFilePath = segments .map((segment) => slugify(segment)) .filter(Boolean) - .join(sep) + .join(posix.sep) // Use directory for root index and readme if ( diff --git a/packages/mdxts/src/utils/get-all-data.ts b/packages/mdxts/src/utils/get-all-data.ts index 584c5a09..037e7620 100644 --- a/packages/mdxts/src/utils/get-all-data.ts +++ b/packages/mdxts/src/utils/get-all-data.ts @@ -1,5 +1,5 @@ import parseTitle from 'title' -import { dirname, join, sep } from 'node:path' +import { dirname, join, posix, sep } from 'node:path' import type { ExportedDeclarations, Project } from 'ts-morph' import { SourceFile } from 'ts-morph' import { getSymbolDescription, resolveExpression } from '@tsxmod/utils' @@ -88,10 +88,11 @@ export function getAllData }>({ sort?: (a: ModuleData, b: ModuleData) => number }) { const typeScriptSourceFiles = /ts(x)?/.test(globPattern) - ? project.addSourceFilesAtPaths(globPattern) + ? project.addSourceFilesAtPaths(globPattern.split(sep).join(posix.sep)) : null + const allModulePaths = Object.keys(allModules) const allPaths = [ - ...Object.keys(allModules), + ...allModulePaths, ...(typeScriptSourceFiles?.map((file) => file.getFilePath()) ?? []), ] @@ -113,7 +114,7 @@ export function getAllData }>({ const allPublicPaths = entrySourceFiles .concat(exportedSourceFiles) .map((sourceFile) => sourceFile.getFilePath() as string) - .concat(Object.keys(allModules)) + .concat(allModulePaths) .filter((path) => !path.includes('.examples.tsx')) const allData: Record> = {} const allPublicDeclarations: WeakMap = @@ -137,7 +138,7 @@ export function getAllData }>({ const sourceFileTitle = getSourceFileTitle(sourceFile) const sourcePath = getSourcePath(path) const metadata = getMetadata(sourceFile) - const depth = pathname.split(sep).length - 2 + const depth = pathname.split(posix.sep).length - 2 let title = type === 'md' ? findFirstHeading(sourceFile.getText()) || sourceFileTitle @@ -214,8 +215,8 @@ export function getAllData }>({ ) const isMainExport = packageMetadata?.name ? basePathname - ? pathname === join(sep, basePathname, packageMetadata.name) - : pathname === join(sep, packageMetadata.name) + ? pathname === join(posix.sep, basePathname, packageMetadata.name) + : pathname === join(posix.sep, packageMetadata.name) : false const importDeclarations = sourceFile.getImportDeclarations() const isServerOnly = importDeclarations.some((importDeclaration) => { diff --git a/packages/mdxts/src/utils/get-shared-directory-path.ts b/packages/mdxts/src/utils/get-shared-directory-path.ts index 6a1311b8..9b5c080d 100644 --- a/packages/mdxts/src/utils/get-shared-directory-path.ts +++ b/packages/mdxts/src/utils/get-shared-directory-path.ts @@ -1,8 +1,8 @@ -import { sep } from 'path' +import { posix } from 'path' /** Finds the first shared directory path for a set of paths. */ export function getSharedDirectoryPath(...paths: string[]) { - let pathSegments = paths.map((path) => path.split(sep)) + let pathSegments = paths.map((path) => path.split(posix.sep)) if (pathSegments.length === 0) { throw new Error('mdxts: cannot find common root path for empty array') @@ -29,5 +29,5 @@ export function getSharedDirectoryPath(...paths: string[]) { commonRoot.pop() } - return commonRoot.join(sep) + return commonRoot.join(posix.sep) } diff --git a/packages/mdxts/src/utils/get-source-path.ts b/packages/mdxts/src/utils/get-source-path.ts index 813d0d6c..184fc433 100644 --- a/packages/mdxts/src/utils/get-source-path.ts +++ b/packages/mdxts/src/utils/get-source-path.ts @@ -1,4 +1,4 @@ -import { join, sep } from 'node:path' +import { join, posix } from 'node:path' import { findRootSync } from '@manypkg/find-root' import { getEditorPath } from './get-editor-path' @@ -26,7 +26,7 @@ export function getSourcePath( rootDirectory = findRootSync(process.cwd()).rootDir } - const relativeFilePath = path.replace(join(rootDirectory, sep), '') + const relativeFilePath = path.replace(join(rootDirectory, posix.sep), '') if (gitSource === undefined) { if (!warned.has(relativeFilePath)) {