ts-repo-utils 10.3.1 → 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":"assert-ext.d.mts","sourceRoot":"","sources":["../../src/functions/assert-ext.mts"],"names":[],"mappings":"AACA,OAAO,EAA4B,MAAM,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAKlD,sDAAsD;AACtD,MAAM,MAAM,cAAc,GAAG,YAAY,CAAC;IACxC,6DAA6D;IAC7D,WAAW,EAAE;QACX,8BAA8B;QAC9B,IAAI,EAAE,MAAM,CAAC;QAEb;;;WAGG;QACH,SAAS,EAAE,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,EAAE,CAAC;QAEzC;;WAEG;QACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;KAC3B,EAAE,CAAC;CACL,CAAC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,WACX,cAAc,KACrB,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAoC1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;uBAzEZ,MAAM;;;;wBAqFd,CAAC"}
1
+ {"version":3,"file":"assert-ext.d.mts","sourceRoot":"","sources":["../../src/functions/assert-ext.mts"],"names":[],"mappings":"AACA,OAAO,EAIL,MAAM,EAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAKlD,sDAAsD;AACtD,MAAM,MAAM,cAAc,GAAG,YAAY,CAAC;IACxC,6DAA6D;IAC7D,WAAW,EAAE;QACX,8BAA8B;QAC9B,IAAI,EAAE,MAAM,CAAC;QAEb;;;WAGG;QACH,SAAS,EAAE,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,EAAE,CAAC;QAEzC;;WAEG;QACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;KAC3B,EAAE,CAAC;CACL,CAAC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,WACX,cAAc,KACrB,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAsC1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;uBA3EZ,MAAM;;;;wBAuFd,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import * as path from 'node:path';
2
- import { isString, Arr, Result } from 'ts-data-forge';
2
+ import { isString, unknownToString, Arr, Result } from 'ts-data-forge';
3
3
  import { assertPathExists } from './assert-path-exists.mjs';
4
4
  import { createResultAssert } from './create-result-assert.mjs';
5
5
  import { glob } from './glob.mjs';
@@ -14,7 +14,9 @@ const checkExt = async (config) => {
14
14
  ignorePatterns
15
15
  );
16
16
  } catch (error) {
17
- console.error(`Failed to check directory ${dir}: ${String(error)}`);
17
+ console.error(
18
+ `Failed to check directory ${dir}: ${unknownToString(error)}`
19
+ );
18
20
  return [];
19
21
  }
20
22
  })
@@ -55,7 +57,7 @@ const getFilesWithIncorrectExtension = async (dir, expectedExtensions, ignorePat
55
57
  });
56
58
  if (Result.isErr(filesResult)) {
57
59
  throw new Error(
58
- `Failed to glob files in ${dir}: ${String(filesResult.value)}`
60
+ `Failed to glob files in ${dir}: ${unknownToString(filesResult.value)}`
59
61
  );
60
62
  }
61
63
  const files = filesResult.value;
@@ -1 +1 @@
1
- {"version":3,"file":"assert-ext.mjs","sources":["../../src/functions/assert-ext.mts"],"sourcesContent":["import * as path from 'node:path';\nimport { Arr, type IMap, isString, Result } from 'ts-data-forge';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport { assertPathExists } from './assert-path-exists.mjs';\nimport { createResultAssert } from './create-result-assert.mjs';\nimport { glob } from './glob.mjs';\n\n/** Configuration for directory extension checking. */\nexport type CheckExtConfig = DeepReadonly<{\n /** Array of directory paths and their expected extensions */\n directories: {\n /** Directory path to check */\n path: string;\n\n /**\n * Expected file extension(s) (including the dot). Can be a single extension\n * or an array of valid extensions\n */\n extension: `.${string}` | `.${string}`[];\n\n /**\n * Optional glob patterns to ignore (defaults to ['tsconfig.json'])\n */\n ignorePatterns?: string[];\n }[];\n}>;\n\nexport type CheckExtError = Readonly<{\n message: string;\n files: readonly string[];\n}>;\n\n/**\n * Validates that all files in specified directories have the correct\n * extensions.\n *\n * @param config - Configuration specifying directories and expected extensions.\n * @returns Result.ok when all files pass, otherwise Result.err with details.\n */\nexport const checkExt = async (\n config: CheckExtConfig,\n): Promise<Result<undefined, CheckExtError>> => {\n // Check all directories in parallel\n const results = await Promise.all(\n config.directories.map(async ({ path: dir, extension, ignorePatterns }) => {\n try {\n return await getFilesWithIncorrectExtension(\n dir,\n isString(extension) ? [extension] : extension,\n ignorePatterns,\n );\n } catch (error) {\n console.error(`Failed to check directory ${dir}: ${String(error)}`);\n\n return [];\n }\n }),\n );\n\n // Collect all incorrect files\n const allIncorrectFiles: readonly string[] = results.flat();\n\n if (Arr.isEmpty(allIncorrectFiles)) {\n return Result.ok(undefined);\n }\n\n const message = [\n 'Files with incorrect extensions found:',\n ...allIncorrectFiles.map((file) => ` - ${file}`),\n '',\n describeExpectedExtensions(config),\n ].join('\\n');\n\n return Result.err({\n message,\n files: allIncorrectFiles,\n });\n};\n\n/**\n * Validates that all files in specified directories have the correct\n * extensions. Exits with code 1 if any files have incorrect extensions.\n *\n * @param config - Configuration specifying directories and expected extensions.\n */\nexport const assertExt = createResultAssert<\n CheckExtConfig,\n undefined,\n CheckExtError\n>({\n run: checkExt,\n onError: (error) => {\n console.info(error.message);\n },\n onSuccess: () => {\n console.info('✓ All files have correct extensions');\n },\n});\n\n/**\n * Checks if all files in a directory have the expected extension.\n *\n * @param dir - The directory to check.\n * @param expectedExtensions - The expected file extensions.\n * @param ignorePatterns - Optional glob patterns to ignore.\n * @returns Array of files with incorrect extensions.\n */\nconst getFilesWithIncorrectExtension = async (\n dir: string,\n expectedExtensions: readonly string[],\n ignorePatterns?: readonly string[],\n): Promise<readonly string[]> => {\n await assertPathExists(dir, 'Directory');\n\n const defaultIgnorePatterns = ['tsconfig.json'] as const;\n\n const finalIgnorePatterns = ignorePatterns ?? defaultIgnorePatterns;\n\n // Convert relative patterns to absolute paths for the glob ignore option\n const absoluteIgnorePatterns = finalIgnorePatterns.map((pattern) =>\n path.isAbsolute(pattern) ? pattern : `${dir}/${pattern}`,\n );\n\n const filesResult = await glob(`${dir}/**/*`, {\n ignore: absoluteIgnorePatterns,\n });\n\n if (Result.isErr(filesResult)) {\n throw new Error(\n `Failed to glob files in ${dir}: ${String(filesResult.value)}`,\n );\n }\n\n const files = filesResult.value;\n\n return files.filter((file) =>\n expectedExtensions.every((ext) => !file.endsWith(ext)),\n );\n};\n\nconst describeExpectedExtensions = (config: CheckExtConfig): string => {\n // Group directories by extension for a cleaner message\n const extensionGroups: IMap<\n string,\n readonly Readonly<{\n relativePath: string;\n extKey: string;\n }>[]\n > = Arr.groupBy(\n config.directories.map(({ path: dirPath, extension }) => {\n const relativePath = path.relative(process.cwd(), dirPath);\n\n const extKey = isString(extension) ? extension : extension.join(' or ');\n\n return {\n relativePath,\n extKey,\n };\n }),\n ({ extKey }) => extKey,\n );\n\n // Generate message parts for each extension\n const messageParts = Array.from(extensionGroups.entries(), ([ext, dirs]) => {\n const dirList = Arr.isFixedLengthArray(1, dirs)\n ? dirs[0].relativePath\n : dirs.map((d) => d.relativePath).join(', ');\n\n return `${dirList} should have ${ext} extension`;\n });\n\n return `All files in ${messageParts.join(' and ')}.`;\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assert-ext.mjs","sources":["../../src/functions/assert-ext.mts"],"sourcesContent":["import * as path from 'node:path';\nimport {\n Arr,\n type IMap,\n isString,\n Result,\n unknownToString,\n} from 'ts-data-forge';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport { assertPathExists } from './assert-path-exists.mjs';\nimport { createResultAssert } from './create-result-assert.mjs';\nimport { glob } from './glob.mjs';\n\n/** Configuration for directory extension checking. */\nexport type CheckExtConfig = DeepReadonly<{\n /** Array of directory paths and their expected extensions */\n directories: {\n /** Directory path to check */\n path: string;\n\n /**\n * Expected file extension(s) (including the dot). Can be a single extension\n * or an array of valid extensions\n */\n extension: `.${string}` | `.${string}`[];\n\n /**\n * Optional glob patterns to ignore (defaults to ['tsconfig.json'])\n */\n ignorePatterns?: string[];\n }[];\n}>;\n\nexport type CheckExtError = Readonly<{\n message: string;\n files: readonly string[];\n}>;\n\n/**\n * Validates that all files in specified directories have the correct\n * extensions.\n *\n * @param config - Configuration specifying directories and expected extensions.\n * @returns Result.ok when all files pass, otherwise Result.err with details.\n */\nexport const checkExt = async (\n config: CheckExtConfig,\n): Promise<Result<undefined, CheckExtError>> => {\n // Check all directories in parallel\n const results = await Promise.all(\n config.directories.map(async ({ path: dir, extension, ignorePatterns }) => {\n try {\n return await getFilesWithIncorrectExtension(\n dir,\n isString(extension) ? [extension] : extension,\n ignorePatterns,\n );\n } catch (error) {\n console.error(\n `Failed to check directory ${dir}: ${unknownToString(error)}`,\n );\n\n return [];\n }\n }),\n );\n\n // Collect all incorrect files\n const allIncorrectFiles: readonly string[] = results.flat();\n\n if (Arr.isEmpty(allIncorrectFiles)) {\n return Result.ok(undefined);\n }\n\n const message = [\n 'Files with incorrect extensions found:',\n ...allIncorrectFiles.map((file) => ` - ${file}`),\n '',\n describeExpectedExtensions(config),\n ].join('\\n');\n\n return Result.err({\n message,\n files: allIncorrectFiles,\n });\n};\n\n/**\n * Validates that all files in specified directories have the correct\n * extensions. Exits with code 1 if any files have incorrect extensions.\n *\n * @param config - Configuration specifying directories and expected extensions.\n */\nexport const assertExt = createResultAssert<\n CheckExtConfig,\n undefined,\n CheckExtError\n>({\n run: checkExt,\n onError: (error) => {\n console.info(error.message);\n },\n onSuccess: () => {\n console.info('✓ All files have correct extensions');\n },\n});\n\n/**\n * Checks if all files in a directory have the expected extension.\n *\n * @param dir - The directory to check.\n * @param expectedExtensions - The expected file extensions.\n * @param ignorePatterns - Optional glob patterns to ignore.\n * @returns Array of files with incorrect extensions.\n */\nconst getFilesWithIncorrectExtension = async (\n dir: string,\n expectedExtensions: readonly string[],\n ignorePatterns?: readonly string[],\n): Promise<readonly string[]> => {\n await assertPathExists(dir, 'Directory');\n\n const defaultIgnorePatterns = ['tsconfig.json'] as const;\n\n const finalIgnorePatterns = ignorePatterns ?? defaultIgnorePatterns;\n\n // Convert relative patterns to absolute paths for the glob ignore option\n const absoluteIgnorePatterns = finalIgnorePatterns.map((pattern) =>\n path.isAbsolute(pattern) ? pattern : `${dir}/${pattern}`,\n );\n\n const filesResult = await glob(`${dir}/**/*`, {\n ignore: absoluteIgnorePatterns,\n });\n\n if (Result.isErr(filesResult)) {\n throw new Error(\n `Failed to glob files in ${dir}: ${unknownToString(filesResult.value)}`,\n );\n }\n\n const files = filesResult.value;\n\n return files.filter((file) =>\n expectedExtensions.every((ext) => !file.endsWith(ext)),\n );\n};\n\nconst describeExpectedExtensions = (config: CheckExtConfig): string => {\n // Group directories by extension for a cleaner message\n const extensionGroups: IMap<\n string,\n readonly Readonly<{\n relativePath: string;\n extKey: string;\n }>[]\n > = Arr.groupBy(\n config.directories.map(({ path: dirPath, extension }) => {\n const relativePath = path.relative(process.cwd(), dirPath);\n\n const extKey = isString(extension) ? extension : extension.join(' or ');\n\n return {\n relativePath,\n extKey,\n };\n }),\n ({ extKey }) => extKey,\n );\n\n // Generate message parts for each extension\n const messageParts = Array.from(extensionGroups.entries(), ([ext, dirs]) => {\n const dirList = Arr.isFixedLengthArray(1, dirs)\n ? dirs[0].relativePath\n : dirs.map((d) => d.relativePath).join(', ');\n\n return `${dirList} should have ${ext} extension`;\n });\n\n return `All files in ${messageParts.join(' and ')}.`;\n};\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gen-index.d.mts","sourceRoot":"","sources":["../../src/functions/gen-index.mts"],"names":[],"mappings":"AAGA,OAAO,EAA6B,MAAM,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,KAAK,YAAY,EAAmB,MAAM,eAAe,CAAC;AAInE,+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
+ {"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, 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
 
@@ -44,7 +44,7 @@ const genIndex = async (config) => {
44
44
  conditionalEcho("\u2705 Index file generation completed successfully!\n");
45
45
  return Result.ok(void 0);
46
46
  } catch (error) {
47
- conditionalEcho(`\u274C Index generation failed: ${String(error)}
47
+ conditionalEcho(`\u274C Index generation failed: ${unknownToString(error)}
48
48
  `);
49
49
  return Result.err(error);
50
50
  }
@@ -135,7 +135,7 @@ const generateIndexFileForDir = async (dirPath, config, baseDir, currentDepth =
135
135
  }
136
136
  } catch (error) {
137
137
  throw new Error(
138
- `Failed to generate index for directory ${dirPath}: ${String(error)}`,
138
+ `Failed to generate index for directory ${dirPath}: ${unknownToString(error)}`,
139
139
  { cause: error }
140
140
  );
141
141
  }
@@ -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${config.exportStatementExtension}";`
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}${config.exportStatementExtension}";`;
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 { Arr, ISet, isString, pipe, Result } 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: ${String(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}: ${String(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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"execute-parallel.d.mts","sourceRoot":"","sources":["../../../src/functions/workspace-utils/execute-parallel.mts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,aAChB,SAAS,OAAO,EAAE,cAChB,MAAM,gBACL,MAAM,KAClB,OAAO,CAAC,SAAS,QAAQ,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,EAAE,CAoEnE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,aACd,SAAS,OAAO,EAAE,cAChB,MAAM,gBACL,MAAM,KAClB,OAAO,CAAC,IAAI,CA8Dd,CAAC"}
1
+ {"version":3,"file":"execute-parallel.d.mts","sourceRoot":"","sources":["../../../src/functions/workspace-utils/execute-parallel.mts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,aAChB,SAAS,OAAO,EAAE,cAChB,MAAM,gBACL,MAAM,KAClB,OAAO,CAAC,SAAS,QAAQ,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,EAAE,CAoEnE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,aACd,SAAS,OAAO,EAAE,cAChB,MAAM,gBACL,MAAM,KAClB,OAAO,CAAC,IAAI,CAgEd,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { isError } from '@sindresorhus/is';
2
2
  import { spawn } from 'node:child_process';
3
- import { Result, Arr, pipe, createPromise, isRecord, hasKey, isNotUndefined } from 'ts-data-forge';
3
+ import { Result, Arr, unknownToString, pipe, createPromise, isRecord, hasKey, isNotUndefined } from 'ts-data-forge';
4
4
 
5
5
  const executeParallel = async (packages, scriptName, concurrency = 3) => {
6
6
  const mut_resultPromises = [];
@@ -71,7 +71,7 @@ Executing ${scriptName} in ${mut_stages.length} stages (fail-fast mode)...
71
71
  `\u2705 Stage ${i + 1} completed successfully (${results.length}/${stage.length} packages)`
72
72
  );
73
73
  } catch (error) {
74
- const errorMessage = isError(error) ? error.message : String(error);
74
+ const errorMessage = isError(error) ? error.message : unknownToString(error);
75
75
  console.error(`
76
76
  \u274C Stage ${i + 1} failed (fail-fast):`);
77
77
  console.error(errorMessage);
@@ -1 +1 @@
1
- {"version":3,"file":"execute-parallel.mjs","sources":["../../../src/functions/workspace-utils/execute-parallel.mts"],"sourcesContent":["/* eslint-disable require-atomic-updates */\nimport { isError } from '@sindresorhus/is';\nimport { spawn } from 'node:child_process';\nimport {\n Arr,\n createPromise,\n hasKey,\n isNotUndefined,\n isRecord,\n pipe,\n Result,\n} from 'ts-data-forge';\nimport { type Package } from './types.mjs';\n\nconst DEBUG = false as boolean;\n\n/**\n * Executes a npm script across multiple packages in parallel with a concurrency\n * limit. Uses fail-fast behavior - stops execution immediately when any package\n * fails.\n *\n * @param packages - Array of Package objects to execute the script in\n * @param scriptName - The name of the npm script to execute\n * @param concurrency - Maximum number of packages to process simultaneously\n * (default: 3)\n * @returns A promise that resolves to an array of execution results, or rejects\n * immediately on first failure\n */\nexport const executeParallel = async (\n packages: readonly Package[],\n scriptName: string,\n concurrency: number = 3,\n): Promise<readonly Readonly<{ code?: number; skipped?: boolean }>[]> => {\n const mut_resultPromises: Promise<\n Readonly<{ code?: number; skipped?: boolean }>\n >[] = [];\n\n const mut_executing = new Set<Promise<unknown>>();\n\n let mut_failed = false as boolean;\n\n for (const pkg of packages) {\n // Stop starting new processes if any has failed\n if (mut_failed) {\n break;\n }\n\n // eslint-disable-next-line no-loop-func\n const promise = executeScript(pkg, scriptName).then((result) => {\n // Check for failure immediately\n if (Result.isErr(result)) {\n mut_failed = true;\n\n throw result.value; // Throw the error to trigger fail-fast\n }\n\n return result.value; // Return the unwrapped value for success\n });\n\n mut_resultPromises.push(promise);\n\n const wrappedPromise = promise\n // eslint-disable-next-line no-loop-func\n .catch((error: unknown) => {\n mut_failed = true;\n\n throw error; // Re-throw to ensure fail-fast propagation\n })\n .finally(() => {\n mut_executing.delete(wrappedPromise);\n\n if (DEBUG) {\n console.debug('executing size', mut_executing.size);\n }\n });\n\n mut_executing.add(wrappedPromise);\n\n if (DEBUG) {\n console.debug('executing size', mut_executing.size);\n }\n\n // If we reach concurrency limit, wait for one to finish\n if (mut_executing.size >= concurrency) {\n try {\n // eslint-disable-next-line no-await-in-loop\n await Promise.race(mut_executing);\n } catch (error) {\n // If any process fails, cancel remaining processes and throw immediately\n // eslint-disable-next-line no-useless-assignment\n mut_failed = true;\n\n throw error;\n }\n }\n }\n\n // Wait for all started processes to complete\n // This will throw immediately if any process fails (fail-fast)\n return Promise.all(mut_resultPromises);\n};\n\n/**\n * Executes a npm script across packages in dependency order stages. Packages\n * are grouped into stages where each stage contains packages whose dependencies\n * have been completed in previous stages. Uses fail-fast behavior - stops\n * execution immediately when any package fails.\n *\n * @param packages - Array of Package objects to execute the script in\n * @param scriptName - The name of the npm script to execute\n * @param concurrency - Maximum number of packages to process simultaneously\n * within each stage (default: 3)\n * @returns A promise that resolves when all stages are complete, or rejects\n * immediately on first failure\n */\nexport const executeStages = async (\n packages: readonly Package[],\n scriptName: string,\n concurrency: number = 3,\n): Promise<void> => {\n const dependencyGraph = buildDependencyGraph(packages);\n\n const sorted = topologicalSortPackages(packages, dependencyGraph);\n\n const mut_stages: (readonly Package[])[] = [];\n\n const mut_completed = new Set<string>();\n\n while (mut_completed.size < sorted.length) {\n const stage = sorted.filter(\n (pkg) =>\n !mut_completed.has(pkg.name) &&\n (dependencyGraph.get(pkg.name) ?? []).every((dep) =>\n mut_completed.has(dep),\n ),\n );\n\n if (Arr.isEmpty(stage)) {\n throw new Error('Circular dependency detected');\n }\n\n mut_stages.push(stage);\n\n for (const pkg of stage) {\n mut_completed.add(pkg.name);\n }\n }\n\n console.info(\n `\\nExecuting ${scriptName} in ${mut_stages.length} stages (fail-fast mode)...\\n`,\n );\n\n for (const [i, stage] of mut_stages.entries()) {\n if (!Arr.isNonEmpty(stage)) {\n continue;\n }\n\n console.info(`Stage ${i + 1}: ${stage.map((p) => p.name).join(', ')}`);\n\n try {\n // eslint-disable-next-line no-await-in-loop\n const results = await executeParallel(stage, scriptName, concurrency);\n\n // Log successful completion of the stage\n // All results are successful because executeParallel throws on any failure\n console.info(\n `✅ Stage ${i + 1} completed successfully (${results.length}/${stage.length} packages)`,\n );\n } catch (error) {\n // executeParallel will throw immediately on any failure (fail-fast)\n const errorMessage = isError(error) ? error.message : String(error);\n\n console.error(`\\n❌ Stage ${i + 1} failed (fail-fast):`);\n\n console.error(errorMessage);\n\n throw new Error(`Stage ${i + 1} failed: ${errorMessage}`, {\n cause: error,\n });\n }\n }\n};\n\n/**\n * Executes a npm script in a specific package directory.\n *\n * @param pkg - The package object containing path and metadata\n * @param scriptName - The name of the npm script to execute\n * @param options - Configuration options\n * @param options.prefix - Whether to prefix output with package name (default:\n * true)\n * @returns A promise that resolves to execution result with exit code or\n * skipped flag\n */\nconst executeScript = (\n pkg: Package,\n scriptName: string,\n { prefix = true }: Readonly<{ prefix?: boolean }> = {},\n): Promise<Result<Readonly<{ code?: number; skipped?: boolean }>, Error>> =>\n pipe(\n createPromise(\n (\n resolve: (\n value: Readonly<{ code?: number; skipped?: boolean }>,\n ) => void,\n reject: (reason: unknown) => void,\n ) => {\n const packageJsonScripts =\n isRecord(pkg.packageJson) && isRecord(pkg.packageJson['scripts'])\n ? pkg.packageJson['scripts']\n : ({} as const);\n\n const hasScript = hasKey(packageJsonScripts, scriptName);\n\n if (!hasScript) {\n resolve({ skipped: true });\n\n return;\n }\n\n const prefixStr = prefix ? (`[${pkg.name}] ` as const) : '';\n\n const proc = spawn('npm', ['run', scriptName], {\n cwd: pkg.path,\n shell: true,\n stdio: 'pipe',\n });\n\n proc.stdout.on('data', (data: Readonly<Buffer>) => {\n process.stdout.write(prefixStr + data.toString());\n });\n\n proc.stderr.on('data', (data: Readonly<Buffer>) => {\n process.stderr.write(prefixStr + data.toString());\n });\n\n proc.on('close', (code: number | null) => {\n if (DEBUG) {\n console.debug(`${pkg.name} process closed with code: ${code}`);\n }\n\n if (code === 0 || code === null) {\n resolve({ code: code ?? 0 });\n } else {\n reject(new Error(`${pkg.name} exited with code ${code}`));\n }\n });\n\n proc.on('error', reject);\n },\n ),\n ).map((result) =>\n result.then(\n Result.mapErr((err) => {\n const errorMessage: string = isError(err)\n ? err.message\n : isRecord(err) && hasKey(err, 'message')\n ? (err.message?.toString() ?? 'Unknown error message')\n : 'Unknown error';\n\n console.error(`\\nError in ${pkg.name}:`, errorMessage);\n\n return isError(err) ? err : new Error(errorMessage);\n }),\n ),\n ).value;\n\n/**\n * Performs a topological sort on packages based on their dependencies, ensuring\n * dependencies are ordered before their dependents.\n *\n * @param packages - Array of Package objects to sort\n * @returns An array of packages in dependency order (dependencies first)\n */\nconst topologicalSortPackages = (\n packages: readonly Package[],\n dependencyGraph: ReadonlyMap<string, readonly string[]>,\n): readonly Package[] => {\n const mut_visited = new Set<string>();\n\n const mut_result: string[] = [];\n\n const packageMap = new Map(packages.map((p) => [p.name, p]));\n\n const visit = (pkgName: string): void => {\n if (mut_visited.has(pkgName)) return;\n\n mut_visited.add(pkgName);\n\n const deps = dependencyGraph.get(pkgName) ?? [];\n\n for (const dep of deps) {\n visit(dep);\n }\n\n mut_result.push(pkgName);\n };\n\n for (const pkg of packages) {\n visit(pkg.name);\n }\n\n return mut_result\n .map((pkgName) => packageMap.get(pkgName))\n .filter(isNotUndefined);\n};\n\n/**\n * Builds a dependency graph from the given packages, mapping each package name\n * to its internal workspace dependencies.\n *\n * @param packages - Array of Package objects to analyze\n * @returns A readonly map where keys are package names and values are arrays of\n * their dependency package names\n */\nconst buildDependencyGraph = (\n packages: readonly Package[],\n): ReadonlyMap<string, readonly string[]> => {\n const packageMap = new Map(packages.map((p) => [p.name, p]));\n\n const mut_graph = new Map<string, readonly string[]>();\n\n for (const pkg of packages) {\n const deps = Object.keys(pkg.dependencies).filter((depName) =>\n packageMap.has(depName),\n );\n\n mut_graph.set(pkg.name, deps);\n }\n\n return mut_graph;\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"execute-parallel.mjs","sources":["../../../src/functions/workspace-utils/execute-parallel.mts"],"sourcesContent":["/* eslint-disable require-atomic-updates */\nimport { isError } from '@sindresorhus/is';\nimport { spawn } from 'node:child_process';\nimport {\n Arr,\n createPromise,\n hasKey,\n isNotUndefined,\n isRecord,\n pipe,\n Result,\n unknownToString,\n} from 'ts-data-forge';\nimport { type Package } from './types.mjs';\n\nconst DEBUG = false as boolean;\n\n/**\n * Executes a npm script across multiple packages in parallel with a concurrency\n * limit. Uses fail-fast behavior - stops execution immediately when any package\n * fails.\n *\n * @param packages - Array of Package objects to execute the script in\n * @param scriptName - The name of the npm script to execute\n * @param concurrency - Maximum number of packages to process simultaneously\n * (default: 3)\n * @returns A promise that resolves to an array of execution results, or rejects\n * immediately on first failure\n */\nexport const executeParallel = async (\n packages: readonly Package[],\n scriptName: string,\n concurrency: number = 3,\n): Promise<readonly Readonly<{ code?: number; skipped?: boolean }>[]> => {\n const mut_resultPromises: Promise<\n Readonly<{ code?: number; skipped?: boolean }>\n >[] = [];\n\n const mut_executing = new Set<Promise<unknown>>();\n\n let mut_failed = false as boolean;\n\n for (const pkg of packages) {\n // Stop starting new processes if any has failed\n if (mut_failed) {\n break;\n }\n\n // eslint-disable-next-line no-loop-func\n const promise = executeScript(pkg, scriptName).then((result) => {\n // Check for failure immediately\n if (Result.isErr(result)) {\n mut_failed = true;\n\n throw result.value; // Throw the error to trigger fail-fast\n }\n\n return result.value; // Return the unwrapped value for success\n });\n\n mut_resultPromises.push(promise);\n\n const wrappedPromise = promise\n // eslint-disable-next-line no-loop-func\n .catch((error: unknown) => {\n mut_failed = true;\n\n throw error; // Re-throw to ensure fail-fast propagation\n })\n .finally(() => {\n mut_executing.delete(wrappedPromise);\n\n if (DEBUG) {\n console.debug('executing size', mut_executing.size);\n }\n });\n\n mut_executing.add(wrappedPromise);\n\n if (DEBUG) {\n console.debug('executing size', mut_executing.size);\n }\n\n // If we reach concurrency limit, wait for one to finish\n if (mut_executing.size >= concurrency) {\n try {\n // eslint-disable-next-line no-await-in-loop\n await Promise.race(mut_executing);\n } catch (error) {\n // If any process fails, cancel remaining processes and throw immediately\n // eslint-disable-next-line no-useless-assignment\n mut_failed = true;\n\n throw error;\n }\n }\n }\n\n // Wait for all started processes to complete\n // This will throw immediately if any process fails (fail-fast)\n return Promise.all(mut_resultPromises);\n};\n\n/**\n * Executes a npm script across packages in dependency order stages. Packages\n * are grouped into stages where each stage contains packages whose dependencies\n * have been completed in previous stages. Uses fail-fast behavior - stops\n * execution immediately when any package fails.\n *\n * @param packages - Array of Package objects to execute the script in\n * @param scriptName - The name of the npm script to execute\n * @param concurrency - Maximum number of packages to process simultaneously\n * within each stage (default: 3)\n * @returns A promise that resolves when all stages are complete, or rejects\n * immediately on first failure\n */\nexport const executeStages = async (\n packages: readonly Package[],\n scriptName: string,\n concurrency: number = 3,\n): Promise<void> => {\n const dependencyGraph = buildDependencyGraph(packages);\n\n const sorted = topologicalSortPackages(packages, dependencyGraph);\n\n const mut_stages: (readonly Package[])[] = [];\n\n const mut_completed = new Set<string>();\n\n while (mut_completed.size < sorted.length) {\n const stage = sorted.filter(\n (pkg) =>\n !mut_completed.has(pkg.name) &&\n (dependencyGraph.get(pkg.name) ?? []).every((dep) =>\n mut_completed.has(dep),\n ),\n );\n\n if (Arr.isEmpty(stage)) {\n throw new Error('Circular dependency detected');\n }\n\n mut_stages.push(stage);\n\n for (const pkg of stage) {\n mut_completed.add(pkg.name);\n }\n }\n\n console.info(\n `\\nExecuting ${scriptName} in ${mut_stages.length} stages (fail-fast mode)...\\n`,\n );\n\n for (const [i, stage] of mut_stages.entries()) {\n if (!Arr.isNonEmpty(stage)) {\n continue;\n }\n\n console.info(`Stage ${i + 1}: ${stage.map((p) => p.name).join(', ')}`);\n\n try {\n // eslint-disable-next-line no-await-in-loop\n const results = await executeParallel(stage, scriptName, concurrency);\n\n // Log successful completion of the stage\n // All results are successful because executeParallel throws on any failure\n console.info(\n `✅ Stage ${i + 1} completed successfully (${results.length}/${stage.length} packages)`,\n );\n } catch (error) {\n // executeParallel will throw immediately on any failure (fail-fast)\n const errorMessage = isError(error)\n ? error.message\n : unknownToString(error);\n\n console.error(`\\n❌ Stage ${i + 1} failed (fail-fast):`);\n\n console.error(errorMessage);\n\n throw new Error(`Stage ${i + 1} failed: ${errorMessage}`, {\n cause: error,\n });\n }\n }\n};\n\n/**\n * Executes a npm script in a specific package directory.\n *\n * @param pkg - The package object containing path and metadata\n * @param scriptName - The name of the npm script to execute\n * @param options - Configuration options\n * @param options.prefix - Whether to prefix output with package name (default:\n * true)\n * @returns A promise that resolves to execution result with exit code or\n * skipped flag\n */\nconst executeScript = (\n pkg: Package,\n scriptName: string,\n { prefix = true }: Readonly<{ prefix?: boolean }> = {},\n): Promise<Result<Readonly<{ code?: number; skipped?: boolean }>, Error>> =>\n pipe(\n createPromise(\n (\n resolve: (\n value: Readonly<{ code?: number; skipped?: boolean }>,\n ) => void,\n reject: (reason: unknown) => void,\n ) => {\n const packageJsonScripts =\n isRecord(pkg.packageJson) && isRecord(pkg.packageJson['scripts'])\n ? pkg.packageJson['scripts']\n : ({} as const);\n\n const hasScript = hasKey(packageJsonScripts, scriptName);\n\n if (!hasScript) {\n resolve({ skipped: true });\n\n return;\n }\n\n const prefixStr = prefix ? (`[${pkg.name}] ` as const) : '';\n\n const proc = spawn('npm', ['run', scriptName], {\n cwd: pkg.path,\n shell: true,\n stdio: 'pipe',\n });\n\n proc.stdout.on('data', (data: Readonly<Buffer>) => {\n process.stdout.write(prefixStr + data.toString());\n });\n\n proc.stderr.on('data', (data: Readonly<Buffer>) => {\n process.stderr.write(prefixStr + data.toString());\n });\n\n proc.on('close', (code: number | null) => {\n if (DEBUG) {\n console.debug(`${pkg.name} process closed with code: ${code}`);\n }\n\n if (code === 0 || code === null) {\n resolve({ code: code ?? 0 });\n } else {\n reject(new Error(`${pkg.name} exited with code ${code}`));\n }\n });\n\n proc.on('error', reject);\n },\n ),\n ).map((result) =>\n result.then(\n Result.mapErr((err) => {\n const errorMessage: string = isError(err)\n ? err.message\n : isRecord(err) && hasKey(err, 'message')\n ? (err.message?.toString() ?? 'Unknown error message')\n : 'Unknown error';\n\n console.error(`\\nError in ${pkg.name}:`, errorMessage);\n\n return isError(err) ? err : new Error(errorMessage);\n }),\n ),\n ).value;\n\n/**\n * Performs a topological sort on packages based on their dependencies, ensuring\n * dependencies are ordered before their dependents.\n *\n * @param packages - Array of Package objects to sort\n * @returns An array of packages in dependency order (dependencies first)\n */\nconst topologicalSortPackages = (\n packages: readonly Package[],\n dependencyGraph: ReadonlyMap<string, readonly string[]>,\n): readonly Package[] => {\n const mut_visited = new Set<string>();\n\n const mut_result: string[] = [];\n\n const packageMap = new Map(packages.map((p) => [p.name, p]));\n\n const visit = (pkgName: string): void => {\n if (mut_visited.has(pkgName)) return;\n\n mut_visited.add(pkgName);\n\n const deps = dependencyGraph.get(pkgName) ?? [];\n\n for (const dep of deps) {\n visit(dep);\n }\n\n mut_result.push(pkgName);\n };\n\n for (const pkg of packages) {\n visit(pkg.name);\n }\n\n return mut_result\n .map((pkgName) => packageMap.get(pkgName))\n .filter(isNotUndefined);\n};\n\n/**\n * Builds a dependency graph from the given packages, mapping each package name\n * to its internal workspace dependencies.\n *\n * @param packages - Array of Package objects to analyze\n * @returns A readonly map where keys are package names and values are arrays of\n * their dependency package names\n */\nconst buildDependencyGraph = (\n packages: readonly Package[],\n): ReadonlyMap<string, readonly string[]> => {\n const packageMap = new Map(packages.map((p) => [p.name, p]));\n\n const mut_graph = new Map<string, readonly string[]>();\n\n for (const pkg of packages) {\n const deps = Object.keys(pkg.dependencies).filter((depName) =>\n packageMap.has(depName),\n );\n\n mut_graph.set(pkg.name, deps);\n }\n\n return mut_graph;\n};\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-workspace-packages.d.mts","sourceRoot":"","sources":["../../../src/functions/workspace-utils/get-workspace-packages.mts"],"names":[],"mappings":"AAcA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,uBACX,MAAM,qBACR,SAAS,eAAe,EAAE,KAC3C,OAAO,CAAC,SAAS,OAAO,EAAE,CAqE5B,CAAC"}
1
+ {"version":3,"file":"get-workspace-packages.d.mts","sourceRoot":"","sources":["../../../src/functions/workspace-utils/get-workspace-packages.mts"],"names":[],"mappings":"AAcA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,uBACX,MAAM,qBACR,SAAS,eAAe,EAAE,KAC3C,OAAO,CAAC,SAAS,OAAO,EAAE,CAgE5B,CAAC"}
@@ -41,11 +41,7 @@ const getWorkspacePackages = async (rootPackageJsonDir, dependencyFields = defau
41
41
  name: getStrFromJsonValue(packageJson, "name"),
42
42
  path: path.dirname(packagePath),
43
43
  packageJson,
44
- dependencies: Object.fromEntries(
45
- dependencyFields.flatMap(
46
- (field) => Object.entries(getKeyValueRecordFromJsonValue(packageJson, field))
47
- )
48
- )
44
+ dependencies: mergeDependencyFields(packageJson, dependencyFields)
49
45
  }));
50
46
  return packageInfos;
51
47
  });
@@ -53,6 +49,11 @@ const getWorkspacePackages = async (rootPackageJsonDir, dependencyFields = defau
53
49
  const finalPackages = allPackageArrays.flat();
54
50
  return finalPackages;
55
51
  };
52
+ const mergeDependencyFields = (packageJson, dependencyFields) => Object.fromEntries(
53
+ dependencyFields.flatMap(
54
+ (field) => Object.entries(getKeyValueRecordFromJsonValue(packageJson, field))
55
+ )
56
+ );
56
57
  const getStrFromJsonValue = (value, key) => isRecord(value) && hasKey(value, key) && isString(value[key]) ? value[key] : "";
57
58
  const getStrArrayFromJsonValue = (value, key) => isRecord(value) && hasKey(value, key) && Arr.isArray(value[key]) && value[key].every(isString) ? value[key] : [];
58
59
  const getKeyValueRecordFromJsonValue = (value, key) => {
@@ -1 +1 @@
1
- {"version":3,"file":"get-workspace-packages.mjs","sources":["../../../src/functions/workspace-utils/get-workspace-packages.mts"],"sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport {\n Arr,\n hasKey,\n isNotUndefined,\n isRecord,\n isString,\n Json,\n Obj,\n Result,\n} from 'ts-data-forge';\nimport { type JsonValue, type ReadonlyRecord } from 'ts-type-forge';\nimport { glob } from '../glob.mjs';\nimport {\n defaultDependencyFields,\n type DependencyField,\n type Package,\n} from './types.mjs';\n\n/**\n * Retrieves all workspace packages from a monorepo based on the workspace\n * patterns defined in the root package.json file.\n *\n * @param rootPackageJsonDir - The directory containing the root package.json\n * file\n * @param dependencyFields - Which `package.json` fields contribute to each\n * package's `dependencies` map. Defaults to\n * {@link defaultDependencyFields}.\n * @returns A promise that resolves to an array of Package objects containing\n * package metadata\n */\nexport const getWorkspacePackages = async (\n rootPackageJsonDir: string,\n dependencyFields: readonly DependencyField[] = defaultDependencyFields,\n): Promise<readonly Package[]> => {\n // Read root package.json\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const rootPackageJson: JsonValue = JSON.parse(\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n await fs.readFile(path.join(rootPackageJsonDir, 'package.json'), 'utf8'),\n );\n\n const workspacePatterns: readonly string[] = getStrArrayFromJsonValue(\n rootPackageJson,\n 'workspaces',\n );\n\n const packagePromises = workspacePatterns.map(async (pattern) => {\n const globResult = await glob(pattern, {\n cwd: rootPackageJsonDir,\n ignore: ['**/node_modules/**'],\n onlyDirectories: true,\n absolute: true,\n });\n\n if (Result.isErr(globResult)) {\n return [];\n }\n\n const matches = globResult.value;\n\n const packageJsonList: readonly (\n readonly [string, JsonValue] | undefined\n )[] = await Promise.all(\n matches.map(async (match) => {\n const maybePackagePath = path.join(match, 'package.json');\n\n const result = await Result.fromPromise(\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n fs.readFile(maybePackagePath, 'utf8'),\n );\n\n if (Result.isErr(result)) return undefined;\n\n const parsed = Json.parse(result.value);\n\n if (Result.isErr(parsed)) return undefined;\n\n return [maybePackagePath, parsed.value] as const;\n }),\n );\n\n const packageInfos: readonly Package[] = packageJsonList\n .filter(isNotUndefined)\n .map(([packagePath, packageJson]) => ({\n name: getStrFromJsonValue(packageJson, 'name'),\n path: path.dirname(packagePath),\n packageJson,\n dependencies: Object.fromEntries(\n dependencyFields.flatMap((field) =>\n Object.entries(getKeyValueRecordFromJsonValue(packageJson, field)),\n ),\n ),\n }));\n\n return packageInfos;\n });\n\n const allPackageArrays = await Promise.all(packagePromises);\n\n const finalPackages = allPackageArrays.flat();\n\n return finalPackages;\n};\n\nconst getStrFromJsonValue = (value: JsonValue, key: string): string =>\n isRecord(value) && hasKey(value, key) && isString(value[key])\n ? value[key]\n : '';\n\nconst getStrArrayFromJsonValue = (\n value: JsonValue,\n key: string,\n): readonly string[] =>\n isRecord(value) &&\n hasKey(value, key) &&\n Arr.isArray(value[key]) &&\n value[key].every(isString)\n ? value[key]\n : ([] as const);\n\nconst getKeyValueRecordFromJsonValue = (\n value: JsonValue,\n key: string,\n): ReadonlyRecord<string, string> => {\n if (!isRecord(value) || !hasKey(value, key)) {\n return {};\n }\n\n const obj = value[key];\n\n if (!isRecord(obj)) {\n return {};\n }\n\n return Obj.filter(obj, isString);\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"get-workspace-packages.mjs","sources":["../../../src/functions/workspace-utils/get-workspace-packages.mts"],"sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport {\n Arr,\n hasKey,\n isNotUndefined,\n isRecord,\n isString,\n Json,\n Obj,\n Result,\n} from 'ts-data-forge';\nimport { type JsonValue, type ReadonlyRecord } from 'ts-type-forge';\nimport { glob } from '../glob.mjs';\nimport {\n defaultDependencyFields,\n type DependencyField,\n type Package,\n} from './types.mjs';\n\n/**\n * Retrieves all workspace packages from a monorepo based on the workspace\n * patterns defined in the root package.json file.\n *\n * @param rootPackageJsonDir - The directory containing the root package.json\n * file\n * @param dependencyFields - Which `package.json` fields contribute to each\n * package's `dependencies` map. Defaults to\n * {@link defaultDependencyFields}.\n * @returns A promise that resolves to an array of Package objects containing\n * package metadata\n */\nexport const getWorkspacePackages = async (\n rootPackageJsonDir: string,\n dependencyFields: readonly DependencyField[] = defaultDependencyFields,\n): Promise<readonly Package[]> => {\n // Read root package.json\n const rootPackageJson: JsonValue = JSON.parse(\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n await fs.readFile(path.join(rootPackageJsonDir, 'package.json'), 'utf8'),\n );\n\n const workspacePatterns: readonly string[] = getStrArrayFromJsonValue(\n rootPackageJson,\n 'workspaces',\n );\n\n const packagePromises = workspacePatterns.map(async (pattern) => {\n const globResult = await glob(pattern, {\n cwd: rootPackageJsonDir,\n ignore: ['**/node_modules/**'],\n onlyDirectories: true,\n absolute: true,\n });\n\n if (Result.isErr(globResult)) {\n return [];\n }\n\n const matches = globResult.value;\n\n const packageJsonList: readonly (\n readonly [string, JsonValue] | undefined\n )[] = await Promise.all(\n matches.map(async (match) => {\n const maybePackagePath = path.join(match, 'package.json');\n\n const result = await Result.fromPromise(\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n fs.readFile(maybePackagePath, 'utf8'),\n );\n\n if (Result.isErr(result)) return undefined;\n\n const parsed = Json.parse(result.value);\n\n if (Result.isErr(parsed)) return undefined;\n\n return [maybePackagePath, parsed.value] as const;\n }),\n );\n\n const packageInfos: readonly Package[] = packageJsonList\n .filter(isNotUndefined)\n .map(([packagePath, packageJson]) => ({\n name: getStrFromJsonValue(packageJson, 'name'),\n path: path.dirname(packagePath),\n packageJson,\n dependencies: mergeDependencyFields(packageJson, dependencyFields),\n }));\n\n return packageInfos;\n });\n\n const allPackageArrays = await Promise.all(packagePromises);\n\n const finalPackages = allPackageArrays.flat();\n\n return finalPackages;\n};\n\n/** Merges the named `package.json` fields into a single dependency map. */\nconst mergeDependencyFields = (\n packageJson: JsonValue,\n dependencyFields: readonly DependencyField[],\n): ReadonlyRecord<string, string> =>\n Object.fromEntries(\n dependencyFields.flatMap((field) =>\n Object.entries(getKeyValueRecordFromJsonValue(packageJson, field)),\n ),\n );\n\nconst getStrFromJsonValue = (value: JsonValue, key: string): string =>\n isRecord(value) && hasKey(value, key) && isString(value[key])\n ? value[key]\n : '';\n\nconst getStrArrayFromJsonValue = (\n value: JsonValue,\n key: string,\n): readonly string[] =>\n isRecord(value) &&\n hasKey(value, key) &&\n Arr.isArray(value[key]) &&\n value[key].every(isString)\n ? value[key]\n : ([] as const);\n\nconst getKeyValueRecordFromJsonValue = (\n value: JsonValue,\n key: string,\n): ReadonlyRecord<string, string> => {\n if (!isRecord(value) || !hasKey(value, key)) {\n return {};\n }\n\n const obj = value[key];\n\n if (!isRecord(obj)) {\n return {};\n }\n\n return Obj.filter(obj, isString);\n};\n"],"names":[],"mappings":";;;;;;AAgCO,MAAM,oBAAA,GAAuB,OAClC,kBAAA,EACA,gBAAA,GAA+C,uBAAA,KACf;AAEhC,EAAA,MAAM,kBAA6B,IAAA,CAAK,KAAA;AAAA;AAAA,IAEtC,MAAM,GAAG,QAAA,CAAS,IAAA,CAAK,KAAK,kBAAA,EAAoB,cAAc,GAAG,MAAM;AAAA,GACzE;AAEA,EAAA,MAAM,iBAAA,GAAuC,wBAAA;AAAA,IAC3C,eAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,eAAA,GAAkB,iBAAA,CAAkB,GAAA,CAAI,OAAO,OAAA,KAAY;AAC/D,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,OAAA,EAAS;AAAA,MACrC,GAAA,EAAK,kBAAA;AAAA,MACL,MAAA,EAAQ,CAAC,oBAAoB,CAAA;AAAA,MAC7B,eAAA,EAAiB,IAAA;AAAA,MACjB,QAAA,EAAU;AAAA,KACX,CAAA;AAED,IAAA,IAAI,MAAA,CAAO,KAAA,CAAM,UAAU,CAAA,EAAG;AAC5B,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,MAAM,UAAU,UAAA,CAAW,KAAA;AAE3B,IAAA,MAAM,eAAA,GAEA,MAAM,OAAA,CAAQ,GAAA;AAAA,MAClB,OAAA,CAAQ,GAAA,CAAI,OAAO,KAAA,KAAU;AAC3B,QAAA,MAAM,gBAAA,GAAmB,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,cAAc,CAAA;AAExD,QAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,WAAA;AAAA;AAAA,UAE1B,EAAA,CAAG,QAAA,CAAS,gBAAA,EAAkB,MAAM;AAAA,SACtC;AAEA,QAAA,IAAI,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA,EAAG,OAAO,MAAA;AAEjC,QAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,KAAK,CAAA;AAEtC,QAAA,IAAI,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA,EAAG,OAAO,MAAA;AAEjC,QAAA,OAAO,CAAC,gBAAA,EAAkB,MAAA,CAAO,KAAK,CAAA;AAAA,MACxC,CAAC;AAAA,KACH;AAEA,IAAA,MAAM,YAAA,GAAmC,eAAA,CACtC,MAAA,CAAO,cAAc,CAAA,CACrB,IAAI,CAAC,CAAC,WAAA,EAAa,WAAW,CAAA,MAAO;AAAA,MACpC,IAAA,EAAM,mBAAA,CAAoB,WAAA,EAAa,MAAM,CAAA;AAAA,MAC7C,IAAA,EAAM,IAAA,CAAK,OAAA,CAAQ,WAAW,CAAA;AAAA,MAC9B,WAAA;AAAA,MACA,YAAA,EAAc,qBAAA,CAAsB,WAAA,EAAa,gBAAgB;AAAA,KACnE,CAAE,CAAA;AAEJ,IAAA,OAAO,YAAA;AAAA,EACT,CAAC,CAAA;AAED,EAAA,MAAM,gBAAA,GAAmB,MAAM,OAAA,CAAQ,GAAA,CAAI,eAAe,CAAA;AAE1D,EAAA,MAAM,aAAA,GAAgB,iBAAiB,IAAA,EAAK;AAE5C,EAAA,OAAO,aAAA;AACT;AAGA,MAAM,qBAAA,GAAwB,CAC5B,WAAA,EACA,gBAAA,KAEA,MAAA,CAAO,WAAA;AAAA,EACL,gBAAA,CAAiB,OAAA;AAAA,IAAQ,CAAC,KAAA,KACxB,MAAA,CAAO,QAAQ,8BAAA,CAA+B,WAAA,EAAa,KAAK,CAAC;AAAA;AAErE,CAAA;AAEF,MAAM,sBAAsB,CAAC,KAAA,EAAkB,QAC7C,QAAA,CAAS,KAAK,KAAK,MAAA,CAAO,KAAA,EAAO,GAAG,CAAA,IAAK,SAAS,KAAA,CAAM,GAAG,CAAC,CAAA,GACxD,KAAA,CAAM,GAAG,CAAA,GACT,EAAA;AAEN,MAAM,wBAAA,GAA2B,CAC/B,KAAA,EACA,GAAA,KAEA,QAAA,CAAS,KAAK,CAAA,IACd,MAAA,CAAO,KAAA,EAAO,GAAG,CAAA,IACjB,GAAA,CAAI,OAAA,CAAQ,MAAM,GAAG,CAAC,CAAA,IACtB,KAAA,CAAM,GAAG,CAAA,CAAE,KAAA,CAAM,QAAQ,CAAA,GACrB,KAAA,CAAM,GAAG,CAAA,GACR,EAAC;AAER,MAAM,8BAAA,GAAiC,CACrC,KAAA,EACA,GAAA,KACmC;AACnC,EAAA,IAAI,CAAC,SAAS,KAAK,CAAA,IAAK,CAAC,MAAA,CAAO,KAAA,EAAO,GAAG,CAAA,EAAG;AAC3C,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,GAAA,GAAM,MAAM,GAAG,CAAA;AAErB,EAAA,IAAI,CAAC,QAAA,CAAS,GAAG,CAAA,EAAG;AAClB,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,OAAO,GAAA,CAAI,MAAA,CAAO,GAAA,EAAK,QAAQ,CAAA;AACjC,CAAA;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-repo-utils",
3
- "version": "10.3.1",
3
+ "version": "10.3.3",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
@@ -49,34 +49,34 @@
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.4.0",
53
- "ts-type-forge": "^9.2.1"
52
+ "ts-data-forge": "^14.5.0",
53
+ "ts-type-forge": "^9.2.2"
54
54
  },
55
55
  "devDependencies": {
56
- "@types/node": "26.2.0",
57
- "@vitest/coverage-v8": "4.1.10",
58
- "@vitest/ui": "4.1.10",
56
+ "@types/node": "26.4.0",
57
+ "@vitest/coverage-v8": "4.1.11",
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.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",
63
67
  "jiti": "2.7.0",
64
68
  "npm-run-all2": "9.0.3",
65
69
  "prettier": "3.9.6",
66
70
  "prettier-plugin-organize-imports": "4.3.0",
67
71
  "prettier-plugin-packagejson": "3.0.2",
68
- "rollup": "4.62.4",
72
+ "rollup": "4.62.5",
69
73
  "tsx": "4.23.12",
70
74
  "typedoc": "0.28.20",
71
75
  "typedoc-github-theme": "0.4.0",
72
76
  "typescript": "6.0.3",
73
77
  "typescript-native": "npm:typescript@7.0.2",
74
- "vite": "8.2.1",
75
- "vitest": "4.1.10",
76
- "eslint-config-typed": "5.9.4",
77
- "eslint-plugin-ts-data-forge": "0.4.8",
78
- "eslint-plugin-ts-fortress": "1.0.4",
79
- "eslint-plugin-ts-type-forge": "0.6.6"
78
+ "vite": "8.2.2",
79
+ "vitest": "4.1.11"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "prettier": ">=3.0.0"
@@ -97,15 +97,9 @@
97
97
  "build:min": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts --skip-check",
98
98
  "check:ext": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-ext.mts",
99
99
  "check-all": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-all.mts",
100
- "codemod": "run-s codemod:uncommitted:as-const fmt codemod:uncommitted:readonly fmt",
101
- "codemod:diff": "run-s codemod:diff:as-const fmt codemod:diff:readonly fmt",
102
- "codemod:diff:as-const": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/append-as-const.mts --diff-from origin/main '{src,scripts,samples}/**/*.mts'",
103
- "codemod:diff:readonly": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/convert-to-readonly.mts --diff-from origin/main '{src,scripts,samples}/**/*.mts'",
104
- "codemod:full": "run-s codemod:full:as-const fmt codemod:full:readonly fmt",
105
- "codemod:full:as-const": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/append-as-const.mts '{src,scripts,samples}/**/*.mts'",
106
- "codemod:full:readonly": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/convert-to-readonly.mts '{src,scripts,samples}/**/*.mts'",
107
- "codemod:uncommitted:as-const": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/append-as-const.mts --uncommitted '{src,scripts,samples}/**/*.mts'",
108
- "codemod:uncommitted:readonly": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/convert-to-readonly.mts --uncommitted '{src,scripts,samples}/**/*.mts'",
100
+ "codemod": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/ts-codemod.mts --uncommitted --transformer append-as-const --transformer convert-to-readonly '{src,scripts,samples}/**/*.mts' && pnpm run fmt",
101
+ "codemod:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/ts-codemod.mts --diff-from origin/main --transformer append-as-const --transformer convert-to-readonly '{src,scripts,samples}/**/*.mts' && pnpm run fmt",
102
+ "codemod:full": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-codemod-cli/src/cmd/ts-codemod.mts --transformer append-as-const --transformer convert-to-readonly '{src,scripts,samples}/**/*.mts' && pnpm run fmt",
109
103
  "cspell": "cspell \"**\" --gitignore --gitignore-root ../../ --no-progress",
110
104
  "doc": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/gen-docs.mts",
111
105
  "doc:embed": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples.mts",
@@ -1,5 +1,11 @@
1
1
  import * as path from 'node:path';
2
- import { Arr, type IMap, isString, Result } from 'ts-data-forge';
2
+ import {
3
+ Arr,
4
+ type IMap,
5
+ isString,
6
+ Result,
7
+ unknownToString,
8
+ } from 'ts-data-forge';
3
9
  import { type DeepReadonly } from 'ts-type-forge';
4
10
  import { assertPathExists } from './assert-path-exists.mjs';
5
11
  import { createResultAssert } from './create-result-assert.mjs';
@@ -50,7 +56,9 @@ export const checkExt = async (
50
56
  ignorePatterns,
51
57
  );
52
58
  } catch (error) {
53
- console.error(`Failed to check directory ${dir}: ${String(error)}`);
59
+ console.error(
60
+ `Failed to check directory ${dir}: ${unknownToString(error)}`,
61
+ );
54
62
 
55
63
  return [];
56
64
  }
@@ -127,7 +135,7 @@ const getFilesWithIncorrectExtension = async (
127
135
 
128
136
  if (Result.isErr(filesResult)) {
129
137
  throw new Error(
130
- `Failed to glob files in ${dir}: ${String(filesResult.value)}`,
138
+ `Failed to glob files in ${dir}: ${unknownToString(filesResult.value)}`,
131
139
  );
132
140
  }
133
141
 
@@ -1,8 +1,20 @@
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 { Arr, ISet, isString, pipe, Result } from 'ts-data-forge';
5
- import { type DeepReadonly, type StrictOmit } from 'ts-type-forge';
4
+ import {
5
+ Arr,
6
+ hasKey,
7
+ ISet,
8
+ isString,
9
+ pipe,
10
+ Result,
11
+ unknownToString,
12
+ } from 'ts-data-forge';
13
+ import {
14
+ type DeepReadonly,
15
+ type ReadonlyRecord,
16
+ type StrictOmit,
17
+ } from 'ts-type-forge';
6
18
  import { assertPathExists } from './assert-path-exists.mjs';
7
19
  import { $ } from './exec-async.mjs';
8
20
 
@@ -135,7 +147,7 @@ export const genIndex = async (
135
147
 
136
148
  return Result.ok(undefined);
137
149
  } catch (error) {
138
- conditionalEcho(`❌ Index generation failed: ${String(error)}\n`);
150
+ conditionalEcho(`❌ Index generation failed: ${unknownToString(error)}\n`);
139
151
 
140
152
  return Result.err(error);
141
153
  }
@@ -285,7 +297,7 @@ const generateIndexFileForDir = async (
285
297
  }
286
298
  } catch (error) {
287
299
  throw new Error(
288
- `Failed to generate index for directory ${dirPath}: ${String(error)}`,
300
+ `Failed to generate index for directory ${dirPath}: ${unknownToString(error)}`,
289
301
  { cause: error },
290
302
  );
291
303
  }
@@ -364,6 +376,60 @@ if (import.meta.vitest !== undefined) {
364
376
  });
365
377
  }
366
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
+
367
433
  /**
368
434
  * Generates the content for an index file.
369
435
  *
@@ -381,14 +447,20 @@ const generateIndexContent = (
381
447
  ...subDirectories.map((subDir) =>
382
448
  config.exportStatementExtension === 'none'
383
449
  ? `export * from "./${subDir}";`
384
- : `export * from "./${subDir}/index${config.exportStatementExtension}";`,
450
+ : `export * from "./${subDir}/index${specifierExtension(
451
+ config.indexFileExtension,
452
+ config.exportStatementExtension,
453
+ )}";`,
385
454
  ),
386
455
  ...filesToExport.map((file) => {
387
456
  const fileNameWithoutExt = path.basename(file, path.extname(file));
388
457
 
389
458
  return config.exportStatementExtension === 'none'
390
459
  ? `export * from "./${fileNameWithoutExt}";`
391
- : `export * from "./${fileNameWithoutExt}${config.exportStatementExtension}";`;
460
+ : `export * from "./${fileNameWithoutExt}${specifierExtension(
461
+ path.extname(file),
462
+ config.exportStatementExtension,
463
+ )}";`;
392
464
  }),
393
465
  ] as const;
394
466
 
@@ -9,6 +9,7 @@ import {
9
9
  isRecord,
10
10
  pipe,
11
11
  Result,
12
+ unknownToString,
12
13
  } from 'ts-data-forge';
13
14
  import { type Package } from './types.mjs';
14
15
 
@@ -168,7 +169,9 @@ export const executeStages = async (
168
169
  );
169
170
  } catch (error) {
170
171
  // executeParallel will throw immediately on any failure (fail-fast)
171
- const errorMessage = isError(error) ? error.message : String(error);
172
+ const errorMessage = isError(error)
173
+ ? error.message
174
+ : unknownToString(error);
172
175
 
173
176
  console.error(`\n❌ Stage ${i + 1} failed (fail-fast):`);
174
177
 
@@ -35,7 +35,6 @@ export const getWorkspacePackages = async (
35
35
  dependencyFields: readonly DependencyField[] = defaultDependencyFields,
36
36
  ): Promise<readonly Package[]> => {
37
37
  // Read root package.json
38
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
39
38
  const rootPackageJson: JsonValue = JSON.parse(
40
39
  // eslint-disable-next-line security/detect-non-literal-fs-filename
41
40
  await fs.readFile(path.join(rootPackageJsonDir, 'package.json'), 'utf8'),
@@ -87,11 +86,7 @@ export const getWorkspacePackages = async (
87
86
  name: getStrFromJsonValue(packageJson, 'name'),
88
87
  path: path.dirname(packagePath),
89
88
  packageJson,
90
- dependencies: Object.fromEntries(
91
- dependencyFields.flatMap((field) =>
92
- Object.entries(getKeyValueRecordFromJsonValue(packageJson, field)),
93
- ),
94
- ),
89
+ dependencies: mergeDependencyFields(packageJson, dependencyFields),
95
90
  }));
96
91
 
97
92
  return packageInfos;
@@ -104,6 +99,17 @@ export const getWorkspacePackages = async (
104
99
  return finalPackages;
105
100
  };
106
101
 
102
+ /** Merges the named `package.json` fields into a single dependency map. */
103
+ const mergeDependencyFields = (
104
+ packageJson: JsonValue,
105
+ dependencyFields: readonly DependencyField[],
106
+ ): ReadonlyRecord<string, string> =>
107
+ Object.fromEntries(
108
+ dependencyFields.flatMap((field) =>
109
+ Object.entries(getKeyValueRecordFromJsonValue(packageJson, field)),
110
+ ),
111
+ );
112
+
107
113
  const getStrFromJsonValue = (value: JsonValue, key: string): string =>
108
114
  isRecord(value) && hasKey(value, key) && isString(value[key])
109
115
  ? value[key]