ts-repo-utils 10.3.2 → 10.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gen-index.d.mts","sourceRoot":"","sources":["../../src/functions/gen-index.mts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"gen-index.d.mts","sourceRoot":"","sources":["../../src/functions/gen-index.mts"],"names":[],"mappings":"AAGA,OAAO,EAML,MAAM,EAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,eAAe,CAAC;AAIvB,+CAA+C;AAC/C,MAAM,MAAM,cAAc,GAAG,YAAY,CAAC;IACxC,kFAAkF;IAClF,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEnC;;;;OAIG;IACH,OAAO,CAAC,EACJ,MAAM,EAAE,GACR,CAAC,CACC,IAAI,EAAE,QAAQ,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,KACC,OAAO,CAAC,CAAC;IAElB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,IAAI,MAAM,EAAE,EAAE,CAAC;IAElC,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,IAAI,MAAM,EAAE,CAAC;IAElC,kEAAkE;IAClE,wBAAwB,CAAC,EAAE,IAAI,MAAM,EAAE,GAAG,MAAM,CAAC;IAEjD,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAAC;AA8BH;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,WACX,cAAc,KACrB,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CA0DpC,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import micromatch from 'micromatch';
|
|
2
2
|
import * as fs from 'node:fs/promises';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import { Result, unknownToString, ISet, pipe, Arr, isString } from 'ts-data-forge';
|
|
4
|
+
import { Result, unknownToString, ISet, pipe, Arr, isString, hasKey } from 'ts-data-forge';
|
|
5
5
|
import { assertPathExists } from './assert-path-exists.mjs';
|
|
6
6
|
import { $ } from './exec-async.mjs';
|
|
7
7
|
|
|
@@ -163,14 +163,31 @@ const shouldExportFile = ({
|
|
|
163
163
|
}
|
|
164
164
|
return true;
|
|
165
165
|
};
|
|
166
|
+
const specifierExtension = (sourceExtension, fallback) => hasKey(emittedExtensions, sourceExtension) ? emittedExtensions[sourceExtension] : fallback;
|
|
167
|
+
const emittedExtensions = {
|
|
168
|
+
".cjs": ".cjs",
|
|
169
|
+
".cts": ".cjs",
|
|
170
|
+
".js": ".js",
|
|
171
|
+
".jsx": ".js",
|
|
172
|
+
".mjs": ".mjs",
|
|
173
|
+
".mts": ".mjs",
|
|
174
|
+
".ts": ".js",
|
|
175
|
+
".tsx": ".js"
|
|
176
|
+
};
|
|
166
177
|
const generateIndexContent = (subDirectories, filesToExport, config) => {
|
|
167
178
|
const exportStatements = [
|
|
168
179
|
...subDirectories.map(
|
|
169
|
-
(subDir) => config.exportStatementExtension === "none" ? `export * from "./${subDir}";` : `export * from "./${subDir}/index${
|
|
180
|
+
(subDir) => config.exportStatementExtension === "none" ? `export * from "./${subDir}";` : `export * from "./${subDir}/index${specifierExtension(
|
|
181
|
+
config.indexFileExtension,
|
|
182
|
+
config.exportStatementExtension
|
|
183
|
+
)}";`
|
|
170
184
|
),
|
|
171
185
|
...filesToExport.map((file) => {
|
|
172
186
|
const fileNameWithoutExt = path.basename(file, path.extname(file));
|
|
173
|
-
return config.exportStatementExtension === "none" ? `export * from "./${fileNameWithoutExt}";` : `export * from "./${fileNameWithoutExt}${
|
|
187
|
+
return config.exportStatementExtension === "none" ? `export * from "./${fileNameWithoutExt}";` : `export * from "./${fileNameWithoutExt}${specifierExtension(
|
|
188
|
+
path.extname(file),
|
|
189
|
+
config.exportStatementExtension
|
|
190
|
+
)}";`;
|
|
174
191
|
})
|
|
175
192
|
];
|
|
176
193
|
return Arr.isEmpty(exportStatements) ? "export {};" : exportStatements.join("\n");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gen-index.mjs","sources":["../../src/functions/gen-index.mts"],"sourcesContent":["import micromatch from 'micromatch';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport {\n Arr,\n ISet,\n isString,\n pipe,\n Result,\n unknownToString,\n} from 'ts-data-forge';\nimport { type DeepReadonly, type StrictOmit } from 'ts-type-forge';\nimport { assertPathExists } from './assert-path-exists.mjs';\nimport { $ } from './exec-async.mjs';\n\n/** Configuration for index file generation. */\nexport type GenIndexConfig = DeepReadonly<{\n /** Target directories to generate index files for (string or array of strings) */\n targetDirectory: string | string[];\n\n /**\n * Glob patterns of files or predicate function to exclude from exports\n * (default: excludes `'**\\/*.{test,spec}.?(c|m)[jt]s?(x)'` and\n * `'**\\/*.d.?(c|m)ts'`)\n */\n exclude?:\n | string[]\n | ((\n args: Readonly<{\n absolutePath: string;\n relativePath: string;\n fileName: string;\n }>,\n ) => boolean);\n\n /** File extensions of source files to export (default: ['.ts', '.tsx']) */\n targetExtensions?: `.${string}`[];\n\n /** File extension of index files to generate (default: '.ts') */\n indexFileExtension?: `.${string}`;\n\n /** File extension to use in export statements (default: '.js') */\n exportStatementExtension?: `.${string}` | 'none';\n\n /** Command to run for formatting generated files (optional) */\n formatCommand?: string;\n\n /** Whether to suppress output during execution (default: false) */\n silent?: boolean;\n\n /** Minimum depth to start generating index files (default: 0) */\n minDepth?: number;\n}>;\n\nconst defaultConfig = {\n exclude: ['**/*.{test,spec}.?(c|m)[jt]s?(x)', '**/*.d.?(c|m)ts'],\n targetExtensions: ['.ts', '.tsx'],\n indexFileExtension: '.ts',\n exportStatementExtension: '.js', // For ESM imports, .mts resolves to .mjs\n silent: false,\n minDepth: 0,\n} as const satisfies Required<\n StrictOmit<GenIndexConfig, 'targetDirectory' | 'formatCommand'>\n>;\n\ntype GenIndexConfigInternal = DeepReadonly<{\n formatCommand: string | undefined;\n targetDirectory: ISet<string>;\n exclude: (\n args: Readonly<{\n absolutePath: string;\n relativePath: string;\n fileName: string;\n }>,\n ) => boolean;\n targetExtensions: ISet<`.${string}`>;\n indexFileExtension: `.${string}`;\n exportStatementExtension: `.${string}` | 'none';\n silent: boolean;\n minDepth: number;\n}>;\n\n/**\n * Generates index.ts files recursively in `config.targetDirectory`.\n *\n * @param config - Configuration for index file generation\n * @throws Error if any step fails.\n */\nexport const genIndex = async (\n config: GenIndexConfig,\n): Promise<Result<undefined, unknown>> => {\n // Merge config with defaults\n const filledConfig: GenIndexConfigInternal = fillConfig(config);\n\n const conditionalEcho = filledConfig.silent ? () => {} : console.info;\n\n conditionalEcho('Starting index file generation...\\n');\n\n // Normalize target directories to array\n const targetDirs =\n typeof config.targetDirectory === 'string'\n ? ([config.targetDirectory] as const)\n : config.targetDirectory;\n\n try {\n // Step 1: Verify target directories exist\n for (const dir of targetDirs) {\n const resolvedDir = path.resolve(dir);\n\n // eslint-disable-next-line no-await-in-loop\n await assertPathExists(resolvedDir, `Target directory: ${dir}`);\n }\n\n // Step 2: Generate index files\n conditionalEcho('Generating index files...');\n\n for (const dir of targetDirs) {\n const resolvedDir = path.resolve(dir);\n\n // eslint-disable-next-line no-await-in-loop\n await generateIndexFileForDir(resolvedDir, filledConfig, undefined, 0);\n }\n\n conditionalEcho('✓ Index files generated\\n');\n\n // Step 3: Format generated files\n if (filledConfig.formatCommand !== undefined) {\n conditionalEcho('Formatting generated files...');\n\n const fmtResult = await $(filledConfig.formatCommand, {\n silent: filledConfig.silent,\n });\n\n if (Result.isErr(fmtResult)) {\n throw new Error(`Formatting failed: ${fmtResult.value.message}`);\n }\n\n conditionalEcho('✓ Formatting completed\\n');\n }\n\n conditionalEcho('✅ Index file generation completed successfully!\\n');\n\n return Result.ok(undefined);\n } catch (error) {\n conditionalEcho(`❌ Index generation failed: ${unknownToString(error)}\\n`);\n\n return Result.err(error);\n }\n};\n\nconst fillConfig = (config: GenIndexConfig): GenIndexConfigInternal => {\n const targetExtensions =\n config.targetExtensions ?? defaultConfig.targetExtensions;\n\n const exportExtension =\n config.exportStatementExtension ?? defaultConfig.exportStatementExtension;\n\n return {\n formatCommand: config.formatCommand,\n targetDirectory: ISet.create(\n isString(config.targetDirectory)\n ? [config.targetDirectory]\n : config.targetDirectory,\n ),\n exclude: pipe(config.exclude).map((exclude) =>\n typeof exclude === 'function'\n ? exclude\n : pipe(\n ISet.create<string>(\n Arr.generate(function* () {\n if (exclude !== undefined && Arr.isArray(exclude)) {\n yield* exclude;\n }\n\n yield* defaultConfig.exclude;\n }),\n ),\n ).map(\n (set) =>\n ({\n relativePath,\n fileName,\n }: Readonly<{\n absolutePath: string;\n relativePath: string;\n fileName: string;\n }>) => {\n for (const pattern of set.values()) {\n if (\n micromatch.isMatch(relativePath, pattern) ||\n micromatch.isMatch(fileName, pattern)\n ) {\n return true;\n }\n }\n\n return false;\n },\n ).value,\n ).value,\n targetExtensions: ISet.create(targetExtensions),\n indexFileExtension:\n config.indexFileExtension ?? defaultConfig.indexFileExtension,\n exportStatementExtension: exportExtension,\n silent: config.silent ?? defaultConfig.silent,\n minDepth: config.minDepth ?? defaultConfig.minDepth,\n };\n};\n\n/**\n * Generates an index.ts file for the given directory. Recursively calls itself\n * for subdirectories.\n *\n * @param dirPath - The absolute path to the directory to process.\n * @param config - The merged configuration object.\n * @param baseDir - The base directory path for calculating relative paths\n * (optional, defaults to dirPath).\n * @param currentDepth - The current depth of recursion.\n * @throws Error if directory processing fails.\n */\nconst generateIndexFileForDir = async (\n dirPath: string,\n config: GenIndexConfigInternal,\n baseDir?: string,\n currentDepth: number = 0,\n): Promise<void> => {\n const conditionalEcho = config.silent ? () => {} : console.info;\n\n try {\n const actualBaseDir = baseDir ?? dirPath;\n\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n const mut_subDirectories: string[] = [];\n\n const mut_filesToExport: string[] = [];\n\n for (const entry of entries) {\n const entryName = entry.name;\n\n const entryPath = path.join(dirPath, entryName);\n\n const relativePath = path.relative(actualBaseDir, entryPath);\n\n if (\n config.exclude({\n absolutePath: entryPath,\n relativePath,\n fileName: entryName,\n })\n ) {\n continue; // Skip excluded directories/files\n }\n\n if (entry.isDirectory()) {\n mut_subDirectories.push(entryName);\n\n // Recursively call for subdirectories first\n // eslint-disable-next-line no-await-in-loop\n await generateIndexFileForDir(\n entryPath,\n config,\n actualBaseDir,\n currentDepth + 1,\n );\n } else if (\n entry.isFile() &&\n shouldExportFile({\n absolutePath: entryPath,\n filePath: relativePath,\n config,\n })\n ) {\n mut_filesToExport.push(entryName);\n }\n }\n\n if (currentDepth >= config.minDepth) {\n const indexContent = generateIndexContent(\n mut_subDirectories,\n mut_filesToExport,\n config,\n );\n\n const indexPath = path.join(dirPath, `index${config.indexFileExtension}`);\n\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n await fs.writeFile(indexPath, indexContent);\n\n conditionalEcho(`Generated: ${path.relative(process.cwd(), indexPath)}`);\n }\n } catch (error) {\n throw new Error(\n `Failed to generate index for directory ${dirPath}: ${unknownToString(error)}`,\n { cause: error },\n );\n }\n};\n\nconst indexRegex = /^index\\.[cm]?[jt]s[x]?$/u;\n\n/**\n * Determines if a file should be exported in the index file. A file is exported\n * if:\n *\n * - It has one of the configured source extensions\n * - It's not an index file itself\n * - It doesn't match any exclusion patterns\n *\n * @param filePath - The relative path to the file from the target directory.\n * @param absolutePath - The absolute path to the file.\n * @param config - The merged configuration object.\n * @returns True if the file should be exported.\n */\nconst shouldExportFile = ({\n absolutePath,\n filePath,\n config,\n}: Readonly<{\n absolutePath: string;\n filePath: string;\n config: GenIndexConfigInternal;\n}>): boolean => {\n const fileName = path.basename(filePath);\n\n const ext = path.extname(fileName);\n\n // Must have the correct source extension\n if (!config.targetExtensions.has(ext)) {\n return false;\n }\n\n // Don't export the index file itself\n if (\n indexRegex.test(fileName) // Matches index.ts, index.mts, index.js, index.tsx\n ) {\n return false;\n }\n\n // Check against exclusion patterns\n if (\n config.exclude({\n absolutePath,\n relativePath: filePath,\n fileName,\n })\n ) {\n return false;\n }\n\n return true;\n};\n\nif (import.meta.vitest !== undefined) {\n describe('index file regex', () => {\n test.each([\n ['index.ts', true],\n ['index.js', true],\n ['index.mts', true],\n ['index.mjs', true],\n ['index.cts', true],\n ['index.cjs', true],\n ['index.tsx', true],\n ['index.jsx', true],\n ['not-index.ts', false],\n ['index.txt', false],\n ] as const)('indexRegex.test($0) to be $1', (fileName, expected) => {\n expect(indexRegex.test(fileName)).toBe(expected);\n });\n });\n}\n\n/**\n * Generates the content for an index file.\n *\n * @param subDirectories - Array of subdirectory names.\n * @param filesToExport - Array of file names to export.\n * @param config - The merged configuration object.\n * @returns The index file content.\n */\nconst generateIndexContent = (\n subDirectories: readonly string[],\n filesToExport: readonly string[],\n config: GenIndexConfigInternal,\n): string => {\n const exportStatements = [\n ...subDirectories.map((subDir) =>\n config.exportStatementExtension === 'none'\n ? `export * from \"./${subDir}\";`\n : `export * from \"./${subDir}/index${config.exportStatementExtension}\";`,\n ),\n ...filesToExport.map((file) => {\n const fileNameWithoutExt = path.basename(file, path.extname(file));\n\n return config.exportStatementExtension === 'none'\n ? `export * from \"./${fileNameWithoutExt}\";`\n : `export * from \"./${fileNameWithoutExt}${config.exportStatementExtension}\";`;\n }),\n ] as const;\n\n return Arr.isEmpty(exportStatements)\n ? 'export {};'\n : exportStatements.join('\\n');\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gen-index.mjs","sources":["../../src/functions/gen-index.mts"],"sourcesContent":["import micromatch from 'micromatch';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport {\n Arr,\n hasKey,\n ISet,\n isString,\n pipe,\n Result,\n unknownToString,\n} from 'ts-data-forge';\nimport {\n type DeepReadonly,\n type ReadonlyRecord,\n type StrictOmit,\n} from 'ts-type-forge';\nimport { assertPathExists } from './assert-path-exists.mjs';\nimport { $ } from './exec-async.mjs';\n\n/** Configuration for index file generation. */\nexport type GenIndexConfig = DeepReadonly<{\n /** Target directories to generate index files for (string or array of strings) */\n targetDirectory: string | string[];\n\n /**\n * Glob patterns of files or predicate function to exclude from exports\n * (default: excludes `'**\\/*.{test,spec}.?(c|m)[jt]s?(x)'` and\n * `'**\\/*.d.?(c|m)ts'`)\n */\n exclude?:\n | string[]\n | ((\n args: Readonly<{\n absolutePath: string;\n relativePath: string;\n fileName: string;\n }>,\n ) => boolean);\n\n /** File extensions of source files to export (default: ['.ts', '.tsx']) */\n targetExtensions?: `.${string}`[];\n\n /** File extension of index files to generate (default: '.ts') */\n indexFileExtension?: `.${string}`;\n\n /** File extension to use in export statements (default: '.js') */\n exportStatementExtension?: `.${string}` | 'none';\n\n /** Command to run for formatting generated files (optional) */\n formatCommand?: string;\n\n /** Whether to suppress output during execution (default: false) */\n silent?: boolean;\n\n /** Minimum depth to start generating index files (default: 0) */\n minDepth?: number;\n}>;\n\nconst defaultConfig = {\n exclude: ['**/*.{test,spec}.?(c|m)[jt]s?(x)', '**/*.d.?(c|m)ts'],\n targetExtensions: ['.ts', '.tsx'],\n indexFileExtension: '.ts',\n exportStatementExtension: '.js', // For ESM imports, .mts resolves to .mjs\n silent: false,\n minDepth: 0,\n} as const satisfies Required<\n StrictOmit<GenIndexConfig, 'targetDirectory' | 'formatCommand'>\n>;\n\ntype GenIndexConfigInternal = DeepReadonly<{\n formatCommand: string | undefined;\n targetDirectory: ISet<string>;\n exclude: (\n args: Readonly<{\n absolutePath: string;\n relativePath: string;\n fileName: string;\n }>,\n ) => boolean;\n targetExtensions: ISet<`.${string}`>;\n indexFileExtension: `.${string}`;\n exportStatementExtension: `.${string}` | 'none';\n silent: boolean;\n minDepth: number;\n}>;\n\n/**\n * Generates index.ts files recursively in `config.targetDirectory`.\n *\n * @param config - Configuration for index file generation\n * @throws Error if any step fails.\n */\nexport const genIndex = async (\n config: GenIndexConfig,\n): Promise<Result<undefined, unknown>> => {\n // Merge config with defaults\n const filledConfig: GenIndexConfigInternal = fillConfig(config);\n\n const conditionalEcho = filledConfig.silent ? () => {} : console.info;\n\n conditionalEcho('Starting index file generation...\\n');\n\n // Normalize target directories to array\n const targetDirs =\n typeof config.targetDirectory === 'string'\n ? ([config.targetDirectory] as const)\n : config.targetDirectory;\n\n try {\n // Step 1: Verify target directories exist\n for (const dir of targetDirs) {\n const resolvedDir = path.resolve(dir);\n\n // eslint-disable-next-line no-await-in-loop\n await assertPathExists(resolvedDir, `Target directory: ${dir}`);\n }\n\n // Step 2: Generate index files\n conditionalEcho('Generating index files...');\n\n for (const dir of targetDirs) {\n const resolvedDir = path.resolve(dir);\n\n // eslint-disable-next-line no-await-in-loop\n await generateIndexFileForDir(resolvedDir, filledConfig, undefined, 0);\n }\n\n conditionalEcho('✓ Index files generated\\n');\n\n // Step 3: Format generated files\n if (filledConfig.formatCommand !== undefined) {\n conditionalEcho('Formatting generated files...');\n\n const fmtResult = await $(filledConfig.formatCommand, {\n silent: filledConfig.silent,\n });\n\n if (Result.isErr(fmtResult)) {\n throw new Error(`Formatting failed: ${fmtResult.value.message}`);\n }\n\n conditionalEcho('✓ Formatting completed\\n');\n }\n\n conditionalEcho('✅ Index file generation completed successfully!\\n');\n\n return Result.ok(undefined);\n } catch (error) {\n conditionalEcho(`❌ Index generation failed: ${unknownToString(error)}\\n`);\n\n return Result.err(error);\n }\n};\n\nconst fillConfig = (config: GenIndexConfig): GenIndexConfigInternal => {\n const targetExtensions =\n config.targetExtensions ?? defaultConfig.targetExtensions;\n\n const exportExtension =\n config.exportStatementExtension ?? defaultConfig.exportStatementExtension;\n\n return {\n formatCommand: config.formatCommand,\n targetDirectory: ISet.create(\n isString(config.targetDirectory)\n ? [config.targetDirectory]\n : config.targetDirectory,\n ),\n exclude: pipe(config.exclude).map((exclude) =>\n typeof exclude === 'function'\n ? exclude\n : pipe(\n ISet.create<string>(\n Arr.generate(function* () {\n if (exclude !== undefined && Arr.isArray(exclude)) {\n yield* exclude;\n }\n\n yield* defaultConfig.exclude;\n }),\n ),\n ).map(\n (set) =>\n ({\n relativePath,\n fileName,\n }: Readonly<{\n absolutePath: string;\n relativePath: string;\n fileName: string;\n }>) => {\n for (const pattern of set.values()) {\n if (\n micromatch.isMatch(relativePath, pattern) ||\n micromatch.isMatch(fileName, pattern)\n ) {\n return true;\n }\n }\n\n return false;\n },\n ).value,\n ).value,\n targetExtensions: ISet.create(targetExtensions),\n indexFileExtension:\n config.indexFileExtension ?? defaultConfig.indexFileExtension,\n exportStatementExtension: exportExtension,\n silent: config.silent ?? defaultConfig.silent,\n minDepth: config.minDepth ?? defaultConfig.minDepth,\n };\n};\n\n/**\n * Generates an index.ts file for the given directory. Recursively calls itself\n * for subdirectories.\n *\n * @param dirPath - The absolute path to the directory to process.\n * @param config - The merged configuration object.\n * @param baseDir - The base directory path for calculating relative paths\n * (optional, defaults to dirPath).\n * @param currentDepth - The current depth of recursion.\n * @throws Error if directory processing fails.\n */\nconst generateIndexFileForDir = async (\n dirPath: string,\n config: GenIndexConfigInternal,\n baseDir?: string,\n currentDepth: number = 0,\n): Promise<void> => {\n const conditionalEcho = config.silent ? () => {} : console.info;\n\n try {\n const actualBaseDir = baseDir ?? dirPath;\n\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n const mut_subDirectories: string[] = [];\n\n const mut_filesToExport: string[] = [];\n\n for (const entry of entries) {\n const entryName = entry.name;\n\n const entryPath = path.join(dirPath, entryName);\n\n const relativePath = path.relative(actualBaseDir, entryPath);\n\n if (\n config.exclude({\n absolutePath: entryPath,\n relativePath,\n fileName: entryName,\n })\n ) {\n continue; // Skip excluded directories/files\n }\n\n if (entry.isDirectory()) {\n mut_subDirectories.push(entryName);\n\n // Recursively call for subdirectories first\n // eslint-disable-next-line no-await-in-loop\n await generateIndexFileForDir(\n entryPath,\n config,\n actualBaseDir,\n currentDepth + 1,\n );\n } else if (\n entry.isFile() &&\n shouldExportFile({\n absolutePath: entryPath,\n filePath: relativePath,\n config,\n })\n ) {\n mut_filesToExport.push(entryName);\n }\n }\n\n if (currentDepth >= config.minDepth) {\n const indexContent = generateIndexContent(\n mut_subDirectories,\n mut_filesToExport,\n config,\n );\n\n const indexPath = path.join(dirPath, `index${config.indexFileExtension}`);\n\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n await fs.writeFile(indexPath, indexContent);\n\n conditionalEcho(`Generated: ${path.relative(process.cwd(), indexPath)}`);\n }\n } catch (error) {\n throw new Error(\n `Failed to generate index for directory ${dirPath}: ${unknownToString(error)}`,\n { cause: error },\n );\n }\n};\n\nconst indexRegex = /^index\\.[cm]?[jt]s[x]?$/u;\n\n/**\n * Determines if a file should be exported in the index file. A file is exported\n * if:\n *\n * - It has one of the configured source extensions\n * - It's not an index file itself\n * - It doesn't match any exclusion patterns\n *\n * @param filePath - The relative path to the file from the target directory.\n * @param absolutePath - The absolute path to the file.\n * @param config - The merged configuration object.\n * @returns True if the file should be exported.\n */\nconst shouldExportFile = ({\n absolutePath,\n filePath,\n config,\n}: Readonly<{\n absolutePath: string;\n filePath: string;\n config: GenIndexConfigInternal;\n}>): boolean => {\n const fileName = path.basename(filePath);\n\n const ext = path.extname(fileName);\n\n // Must have the correct source extension\n if (!config.targetExtensions.has(ext)) {\n return false;\n }\n\n // Don't export the index file itself\n if (\n indexRegex.test(fileName) // Matches index.ts, index.mts, index.js, index.tsx\n ) {\n return false;\n }\n\n // Check against exclusion patterns\n if (\n config.exclude({\n absolutePath,\n relativePath: filePath,\n fileName,\n })\n ) {\n return false;\n }\n\n return true;\n};\n\nif (import.meta.vitest !== undefined) {\n describe('index file regex', () => {\n test.each([\n ['index.ts', true],\n ['index.js', true],\n ['index.mts', true],\n ['index.mjs', true],\n ['index.cts', true],\n ['index.cjs', true],\n ['index.tsx', true],\n ['index.jsx', true],\n ['not-index.ts', false],\n ['index.txt', false],\n ] as const)('indexRegex.test($0) to be $1', (fileName, expected) => {\n expect(indexRegex.test(fileName)).toBe(expected);\n });\n });\n}\n\n/**\n * The extension a module specifier needs for a source file with this\n * extension.\n *\n * TypeScript resolves a specifier by the name the file *emits*, so the answer\n * follows the source's own extension rather than one configured value: `.mts`\n * emits `.mjs`, while `.tsx` — like `.ts` — emits `.js`. A directory holding\n * both therefore needs two different extensions, which is why\n * `exportStatementExtension` cannot decide this on its own; it is the fallback\n * for extensions with no known emit.\n */\nconst specifierExtension = (\n sourceExtension: string,\n fallback: `.${string}`,\n): `.${string}` =>\n hasKey(emittedExtensions, sourceExtension)\n ? emittedExtensions[sourceExtension]\n : fallback;\n\nconst emittedExtensions = {\n '.cjs': '.cjs',\n '.cts': '.cjs',\n '.js': '.js',\n '.jsx': '.js',\n '.mjs': '.mjs',\n '.mts': '.mjs',\n '.ts': '.js',\n '.tsx': '.js',\n} as const satisfies ReadonlyRecord<string, `.${string}`>;\n\nif (import.meta.vitest !== undefined) {\n describe('specifierExtension', () => {\n test.each([\n // A directory holding both needs two different extensions, whatever\n // `exportStatementExtension` says: this is what `--export-ext .mjs`\n // together with `--target-ext .tsx` used to get wrong.\n ['./a.mts', '.mjs' as const, '.mjs'],\n ['./a.tsx', '.mjs' as const, '.js'],\n ['./a.ts', '.mjs' as const, '.js'],\n ['./a.cts', '.mjs' as const, '.cjs'],\n // An extension with no known emit keeps the configured value.\n ['./a.vue', '.mjs' as const, '.mjs'],\n ['./a', '.js' as const, '.js'],\n ] as const)(\n 'specifierExtension($0, $1) to be $2',\n (filePath, fallback, expected) => {\n expect(specifierExtension(path.extname(filePath), fallback)).toBe(\n expected,\n );\n },\n );\n });\n}\n\n/**\n * Generates the content for an index file.\n *\n * @param subDirectories - Array of subdirectory names.\n * @param filesToExport - Array of file names to export.\n * @param config - The merged configuration object.\n * @returns The index file content.\n */\nconst generateIndexContent = (\n subDirectories: readonly string[],\n filesToExport: readonly string[],\n config: GenIndexConfigInternal,\n): string => {\n const exportStatements = [\n ...subDirectories.map((subDir) =>\n config.exportStatementExtension === 'none'\n ? `export * from \"./${subDir}\";`\n : `export * from \"./${subDir}/index${specifierExtension(\n config.indexFileExtension,\n config.exportStatementExtension,\n )}\";`,\n ),\n ...filesToExport.map((file) => {\n const fileNameWithoutExt = path.basename(file, path.extname(file));\n\n return config.exportStatementExtension === 'none'\n ? `export * from \"./${fileNameWithoutExt}\";`\n : `export * from \"./${fileNameWithoutExt}${specifierExtension(\n path.extname(file),\n config.exportStatementExtension,\n )}\";`;\n }),\n ] as const;\n\n return Arr.isEmpty(exportStatements)\n ? 'export {};'\n : exportStatements.join('\\n');\n};\n"],"names":[],"mappings":";;;;;;;AA2DA,MAAM,aAAA,GAAgB;AAAA,EACpB,OAAA,EAAS,CAAC,kCAAA,EAAoC,iBAAiB,CAAA;AAAA,EAC/D,gBAAA,EAAkB,CAAC,KAAA,EAAO,MAAM,CAAA;AAAA,EAChC,kBAAA,EAAoB,KAAA;AAAA,EACpB,wBAAA,EAA0B,KAAA;AAAA;AAAA,EAC1B,MAAA,EAAQ,KAAA;AAAA,EACR,QAAA,EAAU;AACZ,CAAA;AA2BO,MAAM,QAAA,GAAW,OACtB,MAAA,KACwC;AAExC,EAAA,MAAM,YAAA,GAAuC,WAAW,MAAM,CAAA;AAE9D,EAAA,MAAM,eAAA,GAAkB,YAAA,CAAa,MAAA,GAAS,MAAM;AAAA,EAAC,IAAI,OAAA,CAAQ,IAAA;AAEjE,EAAA,eAAA,CAAgB,qCAAqC,CAAA;AAGrD,EAAA,MAAM,UAAA,GACJ,OAAO,MAAA,CAAO,eAAA,KAAoB,WAC7B,CAAC,MAAA,CAAO,eAAe,CAAA,GACxB,MAAA,CAAO,eAAA;AAEb,EAAA,IAAI;AAEF,IAAA,KAAA,MAAW,OAAO,UAAA,EAAY;AAC5B,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAGpC,MAAA,MAAM,gBAAA,CAAiB,WAAA,EAAa,CAAA,kBAAA,EAAqB,GAAG,CAAA,CAAE,CAAA;AAAA,IAChE;AAGA,IAAA,eAAA,CAAgB,2BAA2B,CAAA;AAE3C,IAAA,KAAA,MAAW,OAAO,UAAA,EAAY;AAC5B,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAGpC,MAAA,MAAM,uBAAA,CAAwB,WAAA,EAAa,YAAA,EAAc,KAAA,CAAA,EAAW,CAAC,CAAA;AAAA,IACvE;AAEA,IAAA,eAAA,CAAgB,gCAA2B,CAAA;AAG3C,IAAA,IAAI,YAAA,CAAa,kBAAkB,KAAA,CAAA,EAAW;AAC5C,MAAA,eAAA,CAAgB,+BAA+B,CAAA;AAE/C,MAAA,MAAM,SAAA,GAAY,MAAM,CAAA,CAAE,YAAA,CAAa,aAAA,EAAe;AAAA,QACpD,QAAQ,YAAA,CAAa;AAAA,OACtB,CAAA;AAED,MAAA,IAAI,MAAA,CAAO,KAAA,CAAM,SAAS,CAAA,EAAG;AAC3B,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,SAAA,CAAU,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,MACjE;AAEA,MAAA,eAAA,CAAgB,+BAA0B,CAAA;AAAA,IAC5C;AAEA,IAAA,eAAA,CAAgB,wDAAmD,CAAA;AAEnE,IAAA,OAAO,MAAA,CAAO,GAAG,KAAA,CAAS,CAAA;AAAA,EAC5B,SAAS,KAAA,EAAO;AACd,IAAA,eAAA,CAAgB,CAAA,gCAAA,EAA8B,eAAA,CAAgB,KAAK,CAAC;AAAA,CAAI,CAAA;AAExE,IAAA,OAAO,MAAA,CAAO,IAAI,KAAK,CAAA;AAAA,EACzB;AACF;AAEA,MAAM,UAAA,GAAa,CAAC,MAAA,KAAmD;AACrE,EAAA,MAAM,gBAAA,GACJ,MAAA,CAAO,gBAAA,IAAoB,aAAA,CAAc,gBAAA;AAE3C,EAAA,MAAM,eAAA,GACJ,MAAA,CAAO,wBAAA,IAA4B,aAAA,CAAc,wBAAA;AAEnD,EAAA,OAAO;AAAA,IACL,eAAe,MAAA,CAAO,aAAA;AAAA,IACtB,iBAAiB,IAAA,CAAK,MAAA;AAAA,MACpB,QAAA,CAAS,OAAO,eAAe,CAAA,GAC3B,CAAC,MAAA,CAAO,eAAe,IACvB,MAAA,CAAO;AAAA,KACb;AAAA,IACA,OAAA,EAAS,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA,CAAE,GAAA;AAAA,MAAI,CAAC,OAAA,KACjC,OAAO,OAAA,KAAY,aACf,OAAA,GACA,IAAA;AAAA,QACE,IAAA,CAAK,MAAA;AAAA,UACH,GAAA,CAAI,SAAS,aAAa;AACxB,YAAA,IAAI,OAAA,KAAY,MAAA,IAAa,GAAA,CAAI,OAAA,CAAQ,OAAO,CAAA,EAAG;AACjD,cAAA,OAAO,OAAA;AAAA,YACT;AAEA,YAAA,OAAO,aAAA,CAAc,OAAA;AAAA,UACvB,CAAC;AAAA;AACH,OACF,CAAE,GAAA;AAAA,QACA,CAAC,QACC,CAAC;AAAA,UACC,YAAA;AAAA,UACA;AAAA,SACF,KAIO;AACL,UAAA,KAAA,MAAW,OAAA,IAAW,GAAA,CAAI,MAAA,EAAO,EAAG;AAClC,YAAA,IACE,UAAA,CAAW,QAAQ,YAAA,EAAc,OAAO,KACxC,UAAA,CAAW,OAAA,CAAQ,QAAA,EAAU,OAAO,CAAA,EACpC;AACA,cAAA,OAAO,IAAA;AAAA,YACT;AAAA,UACF;AAEA,UAAA,OAAO,KAAA;AAAA,QACT;AAAA,OACJ,CAAE;AAAA,KACR,CAAE,KAAA;AAAA,IACF,gBAAA,EAAkB,IAAA,CAAK,MAAA,CAAO,gBAAgB,CAAA;AAAA,IAC9C,kBAAA,EACE,MAAA,CAAO,kBAAA,IAAsB,aAAA,CAAc,kBAAA;AAAA,IAC7C,wBAAA,EAA0B,eAAA;AAAA,IAC1B,MAAA,EAAQ,MAAA,CAAO,MAAA,IAAU,aAAA,CAAc,MAAA;AAAA,IACvC,QAAA,EAAU,MAAA,CAAO,QAAA,IAAY,aAAA,CAAc;AAAA,GAC7C;AACF,CAAA;AAaA,MAAM,0BAA0B,OAC9B,OAAA,EACA,MAAA,EACA,OAAA,EACA,eAAuB,CAAA,KACL;AAClB,EAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,MAAA,GAAS,MAAM;AAAA,EAAC,IAAI,OAAA,CAAQ,IAAA;AAE3D,EAAA,IAAI;AACF,IAAA,MAAM,gBAAgB,OAAA,IAAW,OAAA;AAGjC,IAAA,MAAM,OAAA,GAAU,MAAM,EAAA,CAAG,OAAA,CAAQ,SAAS,EAAE,aAAA,EAAe,MAAM,CAAA;AAEjE,IAAA,MAAM,qBAA+B,EAAC;AAEtC,IAAA,MAAM,oBAA8B,EAAC;AAErC,IAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,MAAA,MAAM,YAAY,KAAA,CAAM,IAAA;AAExB,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,SAAS,CAAA;AAE9C,MAAA,MAAM,YAAA,GAAe,IAAA,CAAK,QAAA,CAAS,aAAA,EAAe,SAAS,CAAA;AAE3D,MAAA,IACE,OAAO,OAAA,CAAQ;AAAA,QACb,YAAA,EAAc,SAAA;AAAA,QACd,YAAA;AAAA,QACA,QAAA,EAAU;AAAA,OACX,CAAA,EACD;AACA,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,KAAA,CAAM,aAAY,EAAG;AACvB,QAAA,kBAAA,CAAmB,KAAK,SAAS,CAAA;AAIjC,QAAA,MAAM,uBAAA;AAAA,UACJ,SAAA;AAAA,UACA,MAAA;AAAA,UACA,aAAA;AAAA,UACA,YAAA,GAAe;AAAA,SACjB;AAAA,MACF,CAAA,MAAA,IACE,KAAA,CAAM,MAAA,EAAO,IACb,gBAAA,CAAiB;AAAA,QACf,YAAA,EAAc,SAAA;AAAA,QACd,QAAA,EAAU,YAAA;AAAA,QACV;AAAA,OACD,CAAA,EACD;AACA,QAAA,iBAAA,CAAkB,KAAK,SAAS,CAAA;AAAA,MAClC;AAAA,IACF;AAEA,IAAA,IAAI,YAAA,IAAgB,OAAO,QAAA,EAAU;AACnC,MAAA,MAAM,YAAA,GAAe,oBAAA;AAAA,QACnB,kBAAA;AAAA,QACA,iBAAA;AAAA,QACA;AAAA,OACF;AAEA,MAAA,MAAM,YAAY,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA,KAAA,EAAQ,MAAA,CAAO,kBAAkB,CAAA,CAAE,CAAA;AAGxE,MAAA,MAAM,EAAA,CAAG,SAAA,CAAU,SAAA,EAAW,YAAY,CAAA;AAE1C,MAAA,eAAA,CAAgB,CAAA,WAAA,EAAc,KAAK,QAAA,CAAS,OAAA,CAAQ,KAAI,EAAG,SAAS,CAAC,CAAA,CAAE,CAAA;AAAA,IACzE;AAAA,EACF,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,uCAAA,EAA0C,OAAO,CAAA,EAAA,EAAK,eAAA,CAAgB,KAAK,CAAC,CAAA,CAAA;AAAA,MAC5E,EAAE,OAAO,KAAA;AAAM,KACjB;AAAA,EACF;AACF,CAAA;AAEA,MAAM,UAAA,GAAa,0BAAA;AAenB,MAAM,mBAAmB,CAAC;AAAA,EACxB,YAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,KAIgB;AACd,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA;AAEvC,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AAGjC,EAAA,IAAI,CAAC,MAAA,CAAO,gBAAA,CAAiB,GAAA,CAAI,GAAG,CAAA,EAAG;AACrC,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IACE,UAAA,CAAW,IAAA,CAAK,QAAQ,CAAA,EACxB;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IACE,OAAO,OAAA,CAAQ;AAAA,IACb,YAAA;AAAA,IACA,YAAA,EAAc,QAAA;AAAA,IACd;AAAA,GACD,CAAA,EACD;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,IAAA;AACT,CAAA;AAgCA,MAAM,kBAAA,GAAqB,CACzB,eAAA,EACA,QAAA,KAEA,MAAA,CAAO,mBAAmB,eAAe,CAAA,GACrC,iBAAA,CAAkB,eAAe,CAAA,GACjC,QAAA;AAEN,MAAM,iBAAA,GAAoB;AAAA,EACxB,MAAA,EAAQ,MAAA;AAAA,EACR,MAAA,EAAQ,MAAA;AAAA,EACR,KAAA,EAAO,KAAA;AAAA,EACP,MAAA,EAAQ,KAAA;AAAA,EACR,MAAA,EAAQ,MAAA;AAAA,EACR,MAAA,EAAQ,MAAA;AAAA,EACR,KAAA,EAAO,KAAA;AAAA,EACP,MAAA,EAAQ;AACV,CAAA;AAkCA,MAAM,oBAAA,GAAuB,CAC3B,cAAA,EACA,aAAA,EACA,MAAA,KACW;AACX,EAAA,MAAM,gBAAA,GAAmB;AAAA,IACvB,GAAG,cAAA,CAAe,GAAA;AAAA,MAAI,CAAC,MAAA,KACrB,MAAA,CAAO,wBAAA,KAA6B,MAAA,GAChC,oBAAoB,MAAM,CAAA,EAAA,CAAA,GAC1B,CAAA,iBAAA,EAAoB,MAAM,CAAA,MAAA,EAAS,kBAAA;AAAA,QACjC,MAAA,CAAO,kBAAA;AAAA,QACP,MAAA,CAAO;AAAA,OACR,CAAA,EAAA;AAAA,KACP;AAAA,IACA,GAAG,aAAA,CAAc,GAAA,CAAI,CAAC,IAAA,KAAS;AAC7B,MAAA,MAAM,qBAAqB,IAAA,CAAK,QAAA,CAAS,MAAM,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA;AAEjE,MAAA,OAAO,MAAA,CAAO,6BAA6B,MAAA,GACvC,CAAA,iBAAA,EAAoB,kBAAkB,CAAA,EAAA,CAAA,GACtC,CAAA,iBAAA,EAAoB,kBAAkB,CAAA,EAAG,kBAAA;AAAA,QACvC,IAAA,CAAK,QAAQ,IAAI,CAAA;AAAA,QACjB,MAAA,CAAO;AAAA,OACR,CAAA,EAAA,CAAA;AAAA,IACP,CAAC;AAAA,GACH;AAEA,EAAA,OAAO,IAAI,OAAA,CAAQ,gBAAgB,IAC/B,YAAA,GACA,gBAAA,CAAiB,KAAK,IAAI,CAAA;AAChC,CAAA;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-repo-utils",
|
|
3
|
-
"version": "10.3.
|
|
3
|
+
"version": "10.3.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript"
|
|
@@ -49,33 +49,33 @@
|
|
|
49
49
|
"cmd-ts": "^0.15.0",
|
|
50
50
|
"fast-glob": "^3.3.3",
|
|
51
51
|
"micromatch": "^4.0.8",
|
|
52
|
-
"ts-data-forge": "^14.
|
|
52
|
+
"ts-data-forge": "^14.5.0",
|
|
53
53
|
"ts-type-forge": "^9.2.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@types/node": "26.
|
|
56
|
+
"@types/node": "26.4.0",
|
|
57
57
|
"@vitest/coverage-v8": "4.1.11",
|
|
58
58
|
"@vitest/ui": "4.1.11",
|
|
59
59
|
"cspell": "10.0.1",
|
|
60
60
|
"dedent": "1.7.2",
|
|
61
61
|
"esbuild": "0.28.2",
|
|
62
62
|
"eslint": "10.8.1",
|
|
63
|
-
"eslint-config-typed": "5.
|
|
64
|
-
"eslint-plugin-ts-data-forge": "0.5.
|
|
65
|
-
"eslint-plugin-ts-fortress": "1.0.
|
|
66
|
-
"eslint-plugin-ts-type-forge": "0.7.
|
|
63
|
+
"eslint-config-typed": "5.10.0",
|
|
64
|
+
"eslint-plugin-ts-data-forge": "0.5.1",
|
|
65
|
+
"eslint-plugin-ts-fortress": "1.0.5",
|
|
66
|
+
"eslint-plugin-ts-type-forge": "0.7.1",
|
|
67
67
|
"jiti": "2.7.0",
|
|
68
68
|
"npm-run-all2": "9.0.3",
|
|
69
69
|
"prettier": "3.9.6",
|
|
70
70
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
71
71
|
"prettier-plugin-packagejson": "3.0.2",
|
|
72
|
-
"rollup": "4.62.
|
|
72
|
+
"rollup": "4.62.5",
|
|
73
73
|
"tsx": "4.23.12",
|
|
74
74
|
"typedoc": "0.28.20",
|
|
75
75
|
"typedoc-github-theme": "0.4.0",
|
|
76
76
|
"typescript": "6.0.3",
|
|
77
77
|
"typescript-native": "npm:typescript@7.0.2",
|
|
78
|
-
"vite": "8.2.
|
|
78
|
+
"vite": "8.2.2",
|
|
79
79
|
"vitest": "4.1.11"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
@@ -3,13 +3,18 @@ import * as fs from 'node:fs/promises';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import {
|
|
5
5
|
Arr,
|
|
6
|
+
hasKey,
|
|
6
7
|
ISet,
|
|
7
8
|
isString,
|
|
8
9
|
pipe,
|
|
9
10
|
Result,
|
|
10
11
|
unknownToString,
|
|
11
12
|
} from 'ts-data-forge';
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type DeepReadonly,
|
|
15
|
+
type ReadonlyRecord,
|
|
16
|
+
type StrictOmit,
|
|
17
|
+
} from 'ts-type-forge';
|
|
13
18
|
import { assertPathExists } from './assert-path-exists.mjs';
|
|
14
19
|
import { $ } from './exec-async.mjs';
|
|
15
20
|
|
|
@@ -371,6 +376,60 @@ if (import.meta.vitest !== undefined) {
|
|
|
371
376
|
});
|
|
372
377
|
}
|
|
373
378
|
|
|
379
|
+
/**
|
|
380
|
+
* The extension a module specifier needs for a source file with this
|
|
381
|
+
* extension.
|
|
382
|
+
*
|
|
383
|
+
* TypeScript resolves a specifier by the name the file *emits*, so the answer
|
|
384
|
+
* follows the source's own extension rather than one configured value: `.mts`
|
|
385
|
+
* emits `.mjs`, while `.tsx` — like `.ts` — emits `.js`. A directory holding
|
|
386
|
+
* both therefore needs two different extensions, which is why
|
|
387
|
+
* `exportStatementExtension` cannot decide this on its own; it is the fallback
|
|
388
|
+
* for extensions with no known emit.
|
|
389
|
+
*/
|
|
390
|
+
const specifierExtension = (
|
|
391
|
+
sourceExtension: string,
|
|
392
|
+
fallback: `.${string}`,
|
|
393
|
+
): `.${string}` =>
|
|
394
|
+
hasKey(emittedExtensions, sourceExtension)
|
|
395
|
+
? emittedExtensions[sourceExtension]
|
|
396
|
+
: fallback;
|
|
397
|
+
|
|
398
|
+
const emittedExtensions = {
|
|
399
|
+
'.cjs': '.cjs',
|
|
400
|
+
'.cts': '.cjs',
|
|
401
|
+
'.js': '.js',
|
|
402
|
+
'.jsx': '.js',
|
|
403
|
+
'.mjs': '.mjs',
|
|
404
|
+
'.mts': '.mjs',
|
|
405
|
+
'.ts': '.js',
|
|
406
|
+
'.tsx': '.js',
|
|
407
|
+
} as const satisfies ReadonlyRecord<string, `.${string}`>;
|
|
408
|
+
|
|
409
|
+
if (import.meta.vitest !== undefined) {
|
|
410
|
+
describe('specifierExtension', () => {
|
|
411
|
+
test.each([
|
|
412
|
+
// A directory holding both needs two different extensions, whatever
|
|
413
|
+
// `exportStatementExtension` says: this is what `--export-ext .mjs`
|
|
414
|
+
// together with `--target-ext .tsx` used to get wrong.
|
|
415
|
+
['./a.mts', '.mjs' as const, '.mjs'],
|
|
416
|
+
['./a.tsx', '.mjs' as const, '.js'],
|
|
417
|
+
['./a.ts', '.mjs' as const, '.js'],
|
|
418
|
+
['./a.cts', '.mjs' as const, '.cjs'],
|
|
419
|
+
// An extension with no known emit keeps the configured value.
|
|
420
|
+
['./a.vue', '.mjs' as const, '.mjs'],
|
|
421
|
+
['./a', '.js' as const, '.js'],
|
|
422
|
+
] as const)(
|
|
423
|
+
'specifierExtension($0, $1) to be $2',
|
|
424
|
+
(filePath, fallback, expected) => {
|
|
425
|
+
expect(specifierExtension(path.extname(filePath), fallback)).toBe(
|
|
426
|
+
expected,
|
|
427
|
+
);
|
|
428
|
+
},
|
|
429
|
+
);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
374
433
|
/**
|
|
375
434
|
* Generates the content for an index file.
|
|
376
435
|
*
|
|
@@ -388,14 +447,20 @@ const generateIndexContent = (
|
|
|
388
447
|
...subDirectories.map((subDir) =>
|
|
389
448
|
config.exportStatementExtension === 'none'
|
|
390
449
|
? `export * from "./${subDir}";`
|
|
391
|
-
: `export * from "./${subDir}/index${
|
|
450
|
+
: `export * from "./${subDir}/index${specifierExtension(
|
|
451
|
+
config.indexFileExtension,
|
|
452
|
+
config.exportStatementExtension,
|
|
453
|
+
)}";`,
|
|
392
454
|
),
|
|
393
455
|
...filesToExport.map((file) => {
|
|
394
456
|
const fileNameWithoutExt = path.basename(file, path.extname(file));
|
|
395
457
|
|
|
396
458
|
return config.exportStatementExtension === 'none'
|
|
397
459
|
? `export * from "./${fileNameWithoutExt}";`
|
|
398
|
-
: `export * from "./${fileNameWithoutExt}${
|
|
460
|
+
: `export * from "./${fileNameWithoutExt}${specifierExtension(
|
|
461
|
+
path.extname(file),
|
|
462
|
+
config.exportStatementExtension,
|
|
463
|
+
)}";`;
|
|
399
464
|
}),
|
|
400
465
|
] as const;
|
|
401
466
|
|