ts-repo-utils 10.3.0 → 10.3.2

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":"format.d.mts","sourceRoot":"","sources":["../../src/functions/format.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,EAA6B,MAAM,EAAE,MAAM,eAAe,CAAC;AAWlE;;;;GAIG;AACH,eAAO,MAAM,WAAW,UACf,SAAS,MAAM,EAAE,YACd,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACjD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC,KACD,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,CAAC,CAoI/C,CAAC;AA+DF;;;;GAIG;AACH,eAAO,MAAM,eAAe,aAChB,MAAM,YACN,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;CAClD,CAAC,KACD,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAmCpC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,aACvB,QAAQ,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC,KACD,OAAO,CACR,MAAM,CACJ,SAAS,EACT,aAAa,GAAG,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,OAAO,EAAE,CACnE,CAgEF,CAAC;AA4BF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,SACnB,MAAM,YACF,QAAQ,CAAC;IACjB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACjD;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC,KACD,OAAO,CACR,MAAM,CACJ,SAAS,EACP,aAAa,GACb,QAAQ,CAAC;IACP,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,GACF,SAAS,OAAO,EAAE,CACrB,CA2FF,CAAC"}
1
+ {"version":3,"file":"format.d.mts","sourceRoot":"","sources":["../../src/functions/format.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,EAA6B,MAAM,EAAE,MAAM,eAAe,CAAC;AAWlE;;;;GAIG;AACH,eAAO,MAAM,WAAW,UACf,SAAS,MAAM,EAAE,YACd,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACjD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC,KACD,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,CAAC,CA8I/C,CAAC;AA+DF;;;;GAIG;AACH,eAAO,MAAM,eAAe,aAChB,MAAM,YACN,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;CAClD,CAAC,KACD,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAmCpC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,aACvB,QAAQ,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC,KACD,OAAO,CACR,MAAM,CACJ,SAAS,EACT,aAAa,GAAG,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,OAAO,EAAE,CACnE,CAgEF,CAAC;AA4BF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,SACnB,MAAM,YACF,QAAQ,CAAC;IACjB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACjD;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC,KACD,OAAO,CACR,MAAM,CACJ,SAAS,EACP,aAAa,GACb,QAAQ,CAAC;IACP,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,GACF,SAAS,OAAO,EAAE,CACrB,CA2FF,CAAC"}
@@ -25,36 +25,29 @@ const formatFiles = async (files, options) => {
25
25
  const relativePath = path.relative(gitRoot, filePath);
26
26
  return relativePath.startsWith("..") ? filePath : relativePath;
27
27
  };
28
+ const mut_skipped = { nonExistent: 0, ignored: 0, noParser: 0 };
28
29
  const results = (
29
30
  // NOTE: Using Promise.allSettled to ensure all files are processed even if some fail
30
31
  await Promise.allSettled(
31
32
  files.map(async (filePath) => {
32
33
  try {
33
34
  if (!await pathExists(filePath)) {
34
- conditionalEcho(
35
- `Skipping non-existent file: ${getDisplayPath(filePath)}`
36
- );
35
+ mut_skipped.nonExistent += 1;
37
36
  return Result.ok(void 0);
38
37
  }
39
38
  if (!noIgnore && (options?.ignore ?? defaultIgnoreFn)(filePath)) {
40
- conditionalEcho(
41
- `Skipping ignored file: ${getDisplayPath(filePath)}`
42
- );
39
+ mut_skipped.ignored += 1;
43
40
  return Result.ok(void 0);
44
41
  }
45
42
  const fileInfo = await prettier.getFileInfo(filePath, {
46
43
  ignorePath: ".prettierignore"
47
44
  });
48
45
  if (!noIgnore && fileInfo.ignored) {
49
- conditionalEcho(
50
- `Skipping ignored file: ${getDisplayPath(filePath)}`
51
- );
46
+ mut_skipped.ignored += 1;
52
47
  return Result.ok(void 0);
53
48
  }
54
49
  if (!noIgnore && (options?.ignoreUnknown ?? true) && fileInfo.inferredParser === null) {
55
- conditionalEcho(
56
- `Skipping file (no parser): ${getDisplayPath(filePath)}`
57
- );
50
+ mut_skipped.noParser += 1;
58
51
  return Result.ok(void 0);
59
52
  }
60
53
  const content = await fs.readFile(filePath, "utf8");
@@ -82,6 +75,14 @@ const formatFiles = async (files, options) => {
82
75
  })
83
76
  )
84
77
  );
78
+ const skipReport = [
79
+ mut_skipped.ignored === 0 ? "" : `${mut_skipped.ignored} ignored`,
80
+ mut_skipped.noParser === 0 ? "" : `${mut_skipped.noParser} with no parser`,
81
+ mut_skipped.nonExistent === 0 ? "" : `${mut_skipped.nonExistent} non-existent`
82
+ ].filter((part) => part !== "");
83
+ if (Arr.isNonEmpty(skipReport)) {
84
+ conditionalEcho(`Skipped ${skipReport.join(", ")}.`);
85
+ }
85
86
  if (results.every((r) => r.status === "fulfilled")) {
86
87
  const fulfilled = results.map((r) => r.value);
87
88
  if (fulfilled.every(Result.isOk)) {
@@ -1 +1 @@
1
- {"version":3,"file":"format.mjs","sources":["../../src/functions/format.mts"],"sourcesContent":["import { type ExecException } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as prettier from 'prettier';\nimport { Arr, isNotUndefined, pipe, Result } from 'ts-data-forge';\nimport { pathExists } from './assert-path-exists.mjs';\nimport {\n getDiffFrom,\n getGitRoot,\n getModifiedFiles,\n getStagedFiles,\n getUntrackedFiles,\n} from './diff.mjs';\nimport { glob } from './glob.mjs';\n\n/**\n * Format a list of files using Prettier\n *\n * @param files - Array of file paths to format\n */\nexport const formatFiles = async (\n files: readonly string[],\n options?: Readonly<{\n silent?: boolean;\n ignore?: false | ((filePath: string) => boolean);\n ignoreUnknown?: boolean;\n }>,\n): Promise<Result<undefined, readonly unknown[]>> => {\n const silent = options?.silent ?? false;\n\n const noIgnore = options?.ignore === false;\n\n const conditionalEcho = silent ? () => {} : console.info;\n\n if (Arr.isEmpty(files)) {\n conditionalEcho('No files to format');\n\n return Result.ok(undefined);\n }\n\n conditionalEcho(`Formatting ${files.length} files...`);\n\n // Get git root for display purposes\n const gitRootResult = await getGitRoot({ silent: true });\n\n const gitRoot = Result.isOk(gitRootResult) ? gitRootResult.value : undefined;\n\n // Helper function to get display path (relative to git root if available)\n const getDisplayPath = (filePath: string): string => {\n if (gitRoot === undefined) {\n return filePath;\n }\n\n const relativePath = path.relative(gitRoot, filePath);\n\n return relativePath.startsWith('..') ? filePath : relativePath;\n };\n\n // Format each file\n const results: readonly PromiseSettledResult<Result<undefined, unknown>>[] =\n // NOTE: Using Promise.allSettled to ensure all files are processed even if some fail\n await Promise.allSettled(\n files.map(async (filePath) => {\n try {\n // Check if file exists first\n if (!(await pathExists(filePath))) {\n conditionalEcho(\n `Skipping non-existent file: ${getDisplayPath(filePath)}`,\n );\n\n return Result.ok(undefined);\n }\n\n if (!noIgnore && (options?.ignore ?? defaultIgnoreFn)(filePath)) {\n conditionalEcho(\n `Skipping ignored file: ${getDisplayPath(filePath)}`,\n );\n\n return Result.ok(undefined);\n }\n\n // Check if file is ignored by prettier\n const fileInfo = await prettier.getFileInfo(filePath, {\n ignorePath: '.prettierignore',\n });\n\n if (!noIgnore && fileInfo.ignored) {\n conditionalEcho(\n `Skipping ignored file: ${getDisplayPath(filePath)}`,\n );\n\n return Result.ok(undefined);\n }\n\n if (\n !noIgnore &&\n (options?.ignoreUnknown ?? true) &&\n fileInfo.inferredParser === null\n ) {\n // Silently skip files with no parser\n conditionalEcho(\n `Skipping file (no parser): ${getDisplayPath(filePath)}`,\n );\n\n return Result.ok(undefined);\n }\n\n // Read file content\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const content = await fs.readFile(filePath, 'utf8');\n\n // Resolve prettier config for this file\n const prettierOptions = await prettier.resolveConfig(filePath);\n\n // Format the content\n const formatted = await prettier.format(content, {\n ...prettierOptions,\n filepath: filePath,\n });\n\n // Only write if content changed\n if (formatted === content) {\n conditionalEcho(`Unchanged: ${getDisplayPath(filePath)}`);\n } else {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n await fs.writeFile(filePath, formatted, 'utf8');\n\n conditionalEcho(`Formatted: ${getDisplayPath(filePath)}`);\n }\n\n return Result.ok(undefined);\n } catch (error) {\n if (!silent) {\n console.error(\n `Error formatting ${getDisplayPath(filePath)}:`,\n error,\n );\n }\n\n return Result.err(error);\n }\n }),\n );\n\n if (results.every((r) => r.status === 'fulfilled')) {\n const fulfilled = results.map((r) => r.value);\n\n if (fulfilled.every(Result.isOk)) {\n return Result.ok(undefined);\n }\n\n return Result.err(fulfilled.filter(Result.isErr).map((r) => r.value));\n }\n\n return Result.err(\n results\n .filter((r) => r.status === 'rejected')\n .map((r): unknown => r.reason),\n );\n};\n\nconst defaultIgnoreFn = (filePath: string): boolean => {\n const filename = path.basename(filePath);\n\n return (\n ignoreFiles.has(filename) ||\n filename.startsWith('.env') ||\n ignoreExtensions.some((ext) => filePath.endsWith(ext)) ||\n pipe(filePath.split(path.sep)).map((pathSegments) =>\n pathSegments.some((segment) => ignoreDirs.includes(segment)),\n ).value\n );\n};\n\nconst ignoreFiles: ReadonlySet<string> = new Set([\n '.DS_Store',\n 'package-lock.json',\n 'yarn.lock',\n 'pnpm-lock.yaml',\n 'LICENSE',\n '.prettierignore',\n '.editorconfig',\n '.gitignore',\n '.npmignore',\n '.envrc',\n '.nvmrc',\n '.npmrc',\n]);\n\nconst ignoreExtensions: readonly `.${string}`[] = [\n '.svg',\n '.png',\n '.jpg',\n '.jpeg',\n '.gif',\n '.bmp',\n '.tiff',\n '.bak',\n '.log',\n '.zip',\n '.tar',\n '.gz',\n '.7z',\n '.mp3',\n '.mp4',\n '.avi',\n '.mkv',\n '.tsbuildinfo',\n] as const;\n\nconst ignoreDirs: readonly string[] = [\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n 'out',\n '.cache',\n '.vscode',\n '.yarn',\n '.wireit',\n] as const;\n\n/**\n * Format files matching the given glob pattern using Prettier\n *\n * @param pathGlob - Glob pattern to match files\n */\nexport const formatFilesGlob = async (\n pathGlob: string,\n options?: Readonly<{\n silent?: boolean;\n ignoreUnknown?: boolean;\n ignore?: false | ((filePath: string) => boolean);\n }>,\n): Promise<Result<undefined, unknown>> => {\n const silent = options?.silent ?? false;\n\n const ignoreUnknown = options?.ignoreUnknown ?? true;\n\n const ignore = options?.ignore;\n\n const conditionalEcho = silent ? () => {} : console.info;\n\n // Find all files matching the glob\n const globResult = await glob(pathGlob, {\n absolute: true,\n ignore: ignore === false ? [] : ['**/node_modules/**', '**/.git/**'],\n dot: true,\n });\n\n if (Result.isErr(globResult)) {\n const error = globResult.value;\n\n if (!silent) {\n console.error('Error in formatFiles:', error);\n }\n\n return Result.err(error);\n }\n\n const files = globResult.value;\n\n if (Arr.isEmpty(files)) {\n conditionalEcho('No files found matching pattern:', pathGlob);\n\n return Result.ok(undefined);\n }\n\n return formatFiles(files, { silent, ignoreUnknown, ignore });\n};\n\n/**\n * Format only files that have been changed (git status)\n *\n * @param options - Options for formatting\n * @param options.cwd - If provided, only files within this directory will be\n * formatted. Relative paths are resolved against `process.cwd()`. Defaults to\n * undefined (no filtering; all changed files in the repository are\n * formatted).\n */\nexport const formatUncommittedFiles = async (\n options?: Readonly<{\n untracked?: boolean;\n modified?: boolean;\n staged?: boolean;\n silent?: boolean;\n ignoreUnknown?: boolean;\n ignore?: false | ((filePath: string) => boolean);\n cwd?: string;\n }>,\n): Promise<\n Result<\n undefined,\n ExecException | Readonly<{ message: string }> | readonly unknown[]\n >\n> => {\n const {\n untracked = true,\n modified = true,\n staged = true,\n silent = false,\n ignoreUnknown = true,\n ignore,\n cwd,\n } = options ?? {};\n\n const mut_files: string[] = [];\n\n if (untracked) {\n const untrackedFilesResult = await getUntrackedFiles({ silent });\n\n if (Result.isErr(untrackedFilesResult)) {\n if (!silent) {\n console.error(\n 'Error getting changed files:',\n untrackedFilesResult.value,\n );\n }\n\n return untrackedFilesResult;\n }\n\n mut_files.push(...untrackedFilesResult.value);\n }\n\n if (modified) {\n const diffFilesResult = await getModifiedFiles({ silent });\n\n if (Result.isErr(diffFilesResult)) {\n if (!silent) {\n console.error('Error getting changed files:', diffFilesResult.value);\n }\n\n return diffFilesResult;\n }\n\n mut_files.push(...diffFilesResult.value);\n }\n\n if (staged) {\n const stagedFilesResult = await getStagedFiles({ silent });\n\n if (Result.isErr(stagedFilesResult)) {\n if (!silent) {\n console.error('Error getting changed files:', stagedFilesResult.value);\n }\n\n return stagedFilesResult;\n }\n\n mut_files.push(...stagedFilesResult.value);\n }\n\n return formatFiles(filterFilesByCwd(Arr.uniq(mut_files), cwd), {\n silent,\n ignoreUnknown,\n ignore,\n });\n};\n\n/**\n * Filter a list of file paths to those inside `cwd`. If `cwd` is undefined,\n * returns the original list unchanged. Relative `cwd` is resolved against\n * `process.cwd()`.\n */\nconst filterFilesByCwd = (\n files: readonly string[],\n cwd: string | undefined,\n): readonly string[] => {\n if (cwd === undefined) {\n return files;\n }\n\n const resolvedCwd = path.resolve(cwd);\n\n const cwdWithSep = resolvedCwd.endsWith(path.sep)\n ? resolvedCwd\n : (`${resolvedCwd}${path.sep}` as const);\n\n return files.filter((file) => {\n const absFile = path.isAbsolute(file) ? file : path.resolve(file);\n\n return absFile === resolvedCwd || absFile.startsWith(cwdWithSep);\n });\n};\n\n/**\n * Format only files that differ from the specified base branch or commit\n *\n * @param base - Base branch name or commit hash to compare against (defaults to\n * 'main')\n * @param options - Options for formatting\n * @param options.includeUntracked - Include untracked files in addition to diff\n * files (default is true)\n * @param options.includeStaged - Include staged files in addition to diff files\n * (default is true)\n * @param options.silent - Silent mode to suppress command output (default is\n * false)\n */\nexport const formatDiffFrom = async (\n base: string,\n options?: Readonly<{\n includeUntracked?: boolean;\n includeModified?: boolean;\n includeStaged?: boolean;\n silent?: boolean;\n ignoreUnknown?: boolean;\n ignore?: false | ((filePath: string) => boolean);\n /**\n * If provided, only files within this directory will be formatted.\n * Relative paths are resolved against `process.cwd()`. Defaults to\n * undefined (no filtering; all changed files in the repository are\n * formatted).\n */\n cwd?: string;\n }>,\n): Promise<\n Result<\n undefined,\n | ExecException\n | Readonly<{\n message: string;\n }>\n | readonly unknown[]\n >\n> => {\n // const silent = options?.silent ?? false;\n const {\n silent = false,\n includeUntracked = true,\n includeModified = true,\n includeStaged = true,\n ignoreUnknown = true,\n ignore,\n cwd,\n } = options ?? {};\n\n const conditionalEcho = silent ? () => {} : console.info;\n\n // Get files that differ from base branch/commit (excluding deleted files)\n const diffFromBaseResult = await getDiffFrom(base, {\n silent,\n });\n\n if (Result.isErr(diffFromBaseResult)) {\n if (!silent) {\n console.error('Error getting changed files:', diffFromBaseResult.value);\n }\n\n return diffFromBaseResult;\n }\n\n const diffFiles = diffFromBaseResult.value;\n\n const mut_allFiles: string[] = diffFiles.slice();\n\n // If includeUntracked is true, also get untracked files\n for (const { type, flag, fn } of [\n { type: 'untracked', flag: includeUntracked, fn: getUntrackedFiles },\n { type: 'modified', flag: includeModified, fn: getModifiedFiles },\n { type: 'staged', flag: includeStaged, fn: getStagedFiles },\n ]) {\n if (!flag) {\n continue;\n }\n\n // eslint-disable-next-line no-await-in-loop\n const filesResult = await fn({ silent });\n\n if (Result.isErr(filesResult)) {\n if (!silent) {\n console.error(`Error getting ${type} files:`, filesResult.value);\n }\n\n return filesResult;\n }\n\n const files = filesResult.value;\n\n // Combine and deduplicate files\n mut_allFiles.push(...files);\n }\n\n const allFiles = filterFilesByCwd(Arr.uniq(mut_allFiles), cwd);\n\n if (!silent) {\n const includedFileTypes = [\n includeUntracked ? 'untracked files' : undefined,\n includeModified ? 'modified files' : undefined,\n includeStaged ? 'staged files' : undefined,\n ].filter(isNotUndefined);\n\n const message = [\n `Formatting files that differ from ${base}`,\n includedFileTypes\n .map((s, i) =>\n i !== includedFileTypes.length - 1 ? `, ${s}` : ` and ${s}`,\n )\n .join(''),\n ].join('');\n\n conditionalEcho(`${message}:`, allFiles);\n }\n\n if (Arr.isEmpty(allFiles)) {\n conditionalEcho('No files to format');\n\n return Result.ok(undefined);\n }\n\n return formatFiles(allFiles, {\n silent,\n ignoreUnknown,\n ignore,\n });\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"format.mjs","sources":["../../src/functions/format.mts"],"sourcesContent":["import { type ExecException } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as prettier from 'prettier';\nimport { Arr, isNotUndefined, pipe, Result } from 'ts-data-forge';\nimport { pathExists } from './assert-path-exists.mjs';\nimport {\n getDiffFrom,\n getGitRoot,\n getModifiedFiles,\n getStagedFiles,\n getUntrackedFiles,\n} from './diff.mjs';\nimport { glob } from './glob.mjs';\n\n/**\n * Format a list of files using Prettier\n *\n * @param files - Array of file paths to format\n */\nexport const formatFiles = async (\n files: readonly string[],\n options?: Readonly<{\n silent?: boolean;\n ignore?: false | ((filePath: string) => boolean);\n ignoreUnknown?: boolean;\n }>,\n): Promise<Result<undefined, readonly unknown[]>> => {\n const silent = options?.silent ?? false;\n\n const noIgnore = options?.ignore === false;\n\n const conditionalEcho = silent ? () => {} : console.info;\n\n if (Arr.isEmpty(files)) {\n conditionalEcho('No files to format');\n\n return Result.ok(undefined);\n }\n\n conditionalEcho(`Formatting ${files.length} files...`);\n\n // Get git root for display purposes\n const gitRootResult = await getGitRoot({ silent: true });\n\n const gitRoot = Result.isOk(gitRootResult) ? gitRootResult.value : undefined;\n\n // Helper function to get display path (relative to git root if available)\n const getDisplayPath = (filePath: string): string => {\n if (gitRoot === undefined) {\n return filePath;\n }\n\n const relativePath = path.relative(gitRoot, filePath);\n\n return relativePath.startsWith('..') ? filePath : relativePath;\n };\n\n // Skips are counted rather than named. A repository can hand this function\n // thousands of files it will not touch — a subtree formatted by another tool,\n // or a first diff that contains a whole imported directory — and a line each\n // is both unreadable and enough output to overflow the stdout buffer of a\n // parent process capturing this run.\n const mut_skipped = { nonExistent: 0, ignored: 0, noParser: 0 };\n\n // Format each file\n const results: readonly PromiseSettledResult<Result<undefined, unknown>>[] =\n // NOTE: Using Promise.allSettled to ensure all files are processed even if some fail\n await Promise.allSettled(\n files.map(async (filePath) => {\n try {\n // Check if file exists first\n if (!(await pathExists(filePath))) {\n mut_skipped.nonExistent += 1;\n\n return Result.ok(undefined);\n }\n\n if (!noIgnore && (options?.ignore ?? defaultIgnoreFn)(filePath)) {\n mut_skipped.ignored += 1;\n\n return Result.ok(undefined);\n }\n\n // Check if file is ignored by prettier\n const fileInfo = await prettier.getFileInfo(filePath, {\n ignorePath: '.prettierignore',\n });\n\n if (!noIgnore && fileInfo.ignored) {\n mut_skipped.ignored += 1;\n\n return Result.ok(undefined);\n }\n\n if (\n !noIgnore &&\n (options?.ignoreUnknown ?? true) &&\n fileInfo.inferredParser === null\n ) {\n mut_skipped.noParser += 1;\n\n return Result.ok(undefined);\n }\n\n // Read file content\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const content = await fs.readFile(filePath, 'utf8');\n\n // Resolve prettier config for this file\n const prettierOptions = await prettier.resolveConfig(filePath);\n\n // Format the content\n const formatted = await prettier.format(content, {\n ...prettierOptions,\n filepath: filePath,\n });\n\n // Only write if content changed\n if (formatted === content) {\n conditionalEcho(`Unchanged: ${getDisplayPath(filePath)}`);\n } else {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n await fs.writeFile(filePath, formatted, 'utf8');\n\n conditionalEcho(`Formatted: ${getDisplayPath(filePath)}`);\n }\n\n return Result.ok(undefined);\n } catch (error) {\n if (!silent) {\n console.error(\n `Error formatting ${getDisplayPath(filePath)}:`,\n error,\n );\n }\n\n return Result.err(error);\n }\n }),\n );\n\n const skipReport = [\n mut_skipped.ignored === 0 ? '' : `${mut_skipped.ignored} ignored`,\n mut_skipped.noParser === 0 ? '' : `${mut_skipped.noParser} with no parser`,\n mut_skipped.nonExistent === 0\n ? ''\n : `${mut_skipped.nonExistent} non-existent`,\n ].filter((part) => part !== '');\n\n if (Arr.isNonEmpty(skipReport)) {\n conditionalEcho(`Skipped ${skipReport.join(', ')}.`);\n }\n\n if (results.every((r) => r.status === 'fulfilled')) {\n const fulfilled = results.map((r) => r.value);\n\n if (fulfilled.every(Result.isOk)) {\n return Result.ok(undefined);\n }\n\n return Result.err(fulfilled.filter(Result.isErr).map((r) => r.value));\n }\n\n return Result.err(\n results\n .filter((r) => r.status === 'rejected')\n .map((r): unknown => r.reason),\n );\n};\n\nconst defaultIgnoreFn = (filePath: string): boolean => {\n const filename = path.basename(filePath);\n\n return (\n ignoreFiles.has(filename) ||\n filename.startsWith('.env') ||\n ignoreExtensions.some((ext) => filePath.endsWith(ext)) ||\n pipe(filePath.split(path.sep)).map((pathSegments) =>\n pathSegments.some((segment) => ignoreDirs.includes(segment)),\n ).value\n );\n};\n\nconst ignoreFiles: ReadonlySet<string> = new Set([\n '.DS_Store',\n 'package-lock.json',\n 'yarn.lock',\n 'pnpm-lock.yaml',\n 'LICENSE',\n '.prettierignore',\n '.editorconfig',\n '.gitignore',\n '.npmignore',\n '.envrc',\n '.nvmrc',\n '.npmrc',\n]);\n\nconst ignoreExtensions: readonly `.${string}`[] = [\n '.svg',\n '.png',\n '.jpg',\n '.jpeg',\n '.gif',\n '.bmp',\n '.tiff',\n '.bak',\n '.log',\n '.zip',\n '.tar',\n '.gz',\n '.7z',\n '.mp3',\n '.mp4',\n '.avi',\n '.mkv',\n '.tsbuildinfo',\n] as const;\n\nconst ignoreDirs: readonly string[] = [\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n 'out',\n '.cache',\n '.vscode',\n '.yarn',\n '.wireit',\n] as const;\n\n/**\n * Format files matching the given glob pattern using Prettier\n *\n * @param pathGlob - Glob pattern to match files\n */\nexport const formatFilesGlob = async (\n pathGlob: string,\n options?: Readonly<{\n silent?: boolean;\n ignoreUnknown?: boolean;\n ignore?: false | ((filePath: string) => boolean);\n }>,\n): Promise<Result<undefined, unknown>> => {\n const silent = options?.silent ?? false;\n\n const ignoreUnknown = options?.ignoreUnknown ?? true;\n\n const ignore = options?.ignore;\n\n const conditionalEcho = silent ? () => {} : console.info;\n\n // Find all files matching the glob\n const globResult = await glob(pathGlob, {\n absolute: true,\n ignore: ignore === false ? [] : ['**/node_modules/**', '**/.git/**'],\n dot: true,\n });\n\n if (Result.isErr(globResult)) {\n const error = globResult.value;\n\n if (!silent) {\n console.error('Error in formatFiles:', error);\n }\n\n return Result.err(error);\n }\n\n const files = globResult.value;\n\n if (Arr.isEmpty(files)) {\n conditionalEcho('No files found matching pattern:', pathGlob);\n\n return Result.ok(undefined);\n }\n\n return formatFiles(files, { silent, ignoreUnknown, ignore });\n};\n\n/**\n * Format only files that have been changed (git status)\n *\n * @param options - Options for formatting\n * @param options.cwd - If provided, only files within this directory will be\n * formatted. Relative paths are resolved against `process.cwd()`. Defaults to\n * undefined (no filtering; all changed files in the repository are\n * formatted).\n */\nexport const formatUncommittedFiles = async (\n options?: Readonly<{\n untracked?: boolean;\n modified?: boolean;\n staged?: boolean;\n silent?: boolean;\n ignoreUnknown?: boolean;\n ignore?: false | ((filePath: string) => boolean);\n cwd?: string;\n }>,\n): Promise<\n Result<\n undefined,\n ExecException | Readonly<{ message: string }> | readonly unknown[]\n >\n> => {\n const {\n untracked = true,\n modified = true,\n staged = true,\n silent = false,\n ignoreUnknown = true,\n ignore,\n cwd,\n } = options ?? {};\n\n const mut_files: string[] = [];\n\n if (untracked) {\n const untrackedFilesResult = await getUntrackedFiles({ silent });\n\n if (Result.isErr(untrackedFilesResult)) {\n if (!silent) {\n console.error(\n 'Error getting changed files:',\n untrackedFilesResult.value,\n );\n }\n\n return untrackedFilesResult;\n }\n\n mut_files.push(...untrackedFilesResult.value);\n }\n\n if (modified) {\n const diffFilesResult = await getModifiedFiles({ silent });\n\n if (Result.isErr(diffFilesResult)) {\n if (!silent) {\n console.error('Error getting changed files:', diffFilesResult.value);\n }\n\n return diffFilesResult;\n }\n\n mut_files.push(...diffFilesResult.value);\n }\n\n if (staged) {\n const stagedFilesResult = await getStagedFiles({ silent });\n\n if (Result.isErr(stagedFilesResult)) {\n if (!silent) {\n console.error('Error getting changed files:', stagedFilesResult.value);\n }\n\n return stagedFilesResult;\n }\n\n mut_files.push(...stagedFilesResult.value);\n }\n\n return formatFiles(filterFilesByCwd(Arr.uniq(mut_files), cwd), {\n silent,\n ignoreUnknown,\n ignore,\n });\n};\n\n/**\n * Filter a list of file paths to those inside `cwd`. If `cwd` is undefined,\n * returns the original list unchanged. Relative `cwd` is resolved against\n * `process.cwd()`.\n */\nconst filterFilesByCwd = (\n files: readonly string[],\n cwd: string | undefined,\n): readonly string[] => {\n if (cwd === undefined) {\n return files;\n }\n\n const resolvedCwd = path.resolve(cwd);\n\n const cwdWithSep = resolvedCwd.endsWith(path.sep)\n ? resolvedCwd\n : (`${resolvedCwd}${path.sep}` as const);\n\n return files.filter((file) => {\n const absFile = path.isAbsolute(file) ? file : path.resolve(file);\n\n return absFile === resolvedCwd || absFile.startsWith(cwdWithSep);\n });\n};\n\n/**\n * Format only files that differ from the specified base branch or commit\n *\n * @param base - Base branch name or commit hash to compare against (defaults to\n * 'main')\n * @param options - Options for formatting\n * @param options.includeUntracked - Include untracked files in addition to diff\n * files (default is true)\n * @param options.includeStaged - Include staged files in addition to diff files\n * (default is true)\n * @param options.silent - Silent mode to suppress command output (default is\n * false)\n */\nexport const formatDiffFrom = async (\n base: string,\n options?: Readonly<{\n includeUntracked?: boolean;\n includeModified?: boolean;\n includeStaged?: boolean;\n silent?: boolean;\n ignoreUnknown?: boolean;\n ignore?: false | ((filePath: string) => boolean);\n /**\n * If provided, only files within this directory will be formatted.\n * Relative paths are resolved against `process.cwd()`. Defaults to\n * undefined (no filtering; all changed files in the repository are\n * formatted).\n */\n cwd?: string;\n }>,\n): Promise<\n Result<\n undefined,\n | ExecException\n | Readonly<{\n message: string;\n }>\n | readonly unknown[]\n >\n> => {\n // const silent = options?.silent ?? false;\n const {\n silent = false,\n includeUntracked = true,\n includeModified = true,\n includeStaged = true,\n ignoreUnknown = true,\n ignore,\n cwd,\n } = options ?? {};\n\n const conditionalEcho = silent ? () => {} : console.info;\n\n // Get files that differ from base branch/commit (excluding deleted files)\n const diffFromBaseResult = await getDiffFrom(base, {\n silent,\n });\n\n if (Result.isErr(diffFromBaseResult)) {\n if (!silent) {\n console.error('Error getting changed files:', diffFromBaseResult.value);\n }\n\n return diffFromBaseResult;\n }\n\n const diffFiles = diffFromBaseResult.value;\n\n const mut_allFiles: string[] = diffFiles.slice();\n\n // If includeUntracked is true, also get untracked files\n for (const { type, flag, fn } of [\n { type: 'untracked', flag: includeUntracked, fn: getUntrackedFiles },\n { type: 'modified', flag: includeModified, fn: getModifiedFiles },\n { type: 'staged', flag: includeStaged, fn: getStagedFiles },\n ]) {\n if (!flag) {\n continue;\n }\n\n // eslint-disable-next-line no-await-in-loop\n const filesResult = await fn({ silent });\n\n if (Result.isErr(filesResult)) {\n if (!silent) {\n console.error(`Error getting ${type} files:`, filesResult.value);\n }\n\n return filesResult;\n }\n\n const files = filesResult.value;\n\n // Combine and deduplicate files\n mut_allFiles.push(...files);\n }\n\n const allFiles = filterFilesByCwd(Arr.uniq(mut_allFiles), cwd);\n\n if (!silent) {\n const includedFileTypes = [\n includeUntracked ? 'untracked files' : undefined,\n includeModified ? 'modified files' : undefined,\n includeStaged ? 'staged files' : undefined,\n ].filter(isNotUndefined);\n\n const message = [\n `Formatting files that differ from ${base}`,\n includedFileTypes\n .map((s, i) =>\n i !== includedFileTypes.length - 1 ? `, ${s}` : ` and ${s}`,\n )\n .join(''),\n ].join('');\n\n conditionalEcho(`${message}:`, allFiles);\n }\n\n if (Arr.isEmpty(allFiles)) {\n conditionalEcho('No files to format');\n\n return Result.ok(undefined);\n }\n\n return formatFiles(allFiles, {\n silent,\n ignoreUnknown,\n ignore,\n });\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,EAKL,MAAM,EAEP,MAAM,eAAe,CAAC;AACvB,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,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 } 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
  }
@@ -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 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 +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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-repo-utils",
3
- "version": "10.3.0",
3
+ "version": "10.3.2",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
@@ -50,16 +50,20 @@
50
50
  "fast-glob": "^3.3.3",
51
51
  "micromatch": "^4.0.8",
52
52
  "ts-data-forge": "^14.4.0",
53
- "ts-type-forge": "^9.2.1"
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.3.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.9.5",
64
+ "eslint-plugin-ts-data-forge": "0.5.0",
65
+ "eslint-plugin-ts-fortress": "1.0.4",
66
+ "eslint-plugin-ts-type-forge": "0.7.0",
63
67
  "jiti": "2.7.0",
64
68
  "npm-run-all2": "9.0.3",
65
69
  "prettier": "3.9.6",
@@ -72,11 +76,7 @@
72
76
  "typescript": "6.0.3",
73
77
  "typescript-native": "npm:typescript@7.0.2",
74
78
  "vite": "8.2.1",
75
- "vitest": "4.1.10",
76
- "eslint-plugin-ts-data-forge": "0.4.8",
77
- "eslint-config-typed": "5.9.3",
78
- "eslint-plugin-ts-fortress": "1.0.4",
79
- "eslint-plugin-ts-type-forge": "0.6.6"
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
 
@@ -56,6 +56,13 @@ export const formatFiles = async (
56
56
  return relativePath.startsWith('..') ? filePath : relativePath;
57
57
  };
58
58
 
59
+ // Skips are counted rather than named. A repository can hand this function
60
+ // thousands of files it will not touch — a subtree formatted by another tool,
61
+ // or a first diff that contains a whole imported directory — and a line each
62
+ // is both unreadable and enough output to overflow the stdout buffer of a
63
+ // parent process capturing this run.
64
+ const mut_skipped = { nonExistent: 0, ignored: 0, noParser: 0 };
65
+
59
66
  // Format each file
60
67
  const results: readonly PromiseSettledResult<Result<undefined, unknown>>[] =
61
68
  // NOTE: Using Promise.allSettled to ensure all files are processed even if some fail
@@ -64,17 +71,13 @@ export const formatFiles = async (
64
71
  try {
65
72
  // Check if file exists first
66
73
  if (!(await pathExists(filePath))) {
67
- conditionalEcho(
68
- `Skipping non-existent file: ${getDisplayPath(filePath)}`,
69
- );
74
+ mut_skipped.nonExistent += 1;
70
75
 
71
76
  return Result.ok(undefined);
72
77
  }
73
78
 
74
79
  if (!noIgnore && (options?.ignore ?? defaultIgnoreFn)(filePath)) {
75
- conditionalEcho(
76
- `Skipping ignored file: ${getDisplayPath(filePath)}`,
77
- );
80
+ mut_skipped.ignored += 1;
78
81
 
79
82
  return Result.ok(undefined);
80
83
  }
@@ -85,9 +88,7 @@ export const formatFiles = async (
85
88
  });
86
89
 
87
90
  if (!noIgnore && fileInfo.ignored) {
88
- conditionalEcho(
89
- `Skipping ignored file: ${getDisplayPath(filePath)}`,
90
- );
91
+ mut_skipped.ignored += 1;
91
92
 
92
93
  return Result.ok(undefined);
93
94
  }
@@ -97,10 +98,7 @@ export const formatFiles = async (
97
98
  (options?.ignoreUnknown ?? true) &&
98
99
  fileInfo.inferredParser === null
99
100
  ) {
100
- // Silently skip files with no parser
101
- conditionalEcho(
102
- `Skipping file (no parser): ${getDisplayPath(filePath)}`,
103
- );
101
+ mut_skipped.noParser += 1;
104
102
 
105
103
  return Result.ok(undefined);
106
104
  }
@@ -142,6 +140,18 @@ export const formatFiles = async (
142
140
  }),
143
141
  );
144
142
 
143
+ const skipReport = [
144
+ mut_skipped.ignored === 0 ? '' : `${mut_skipped.ignored} ignored`,
145
+ mut_skipped.noParser === 0 ? '' : `${mut_skipped.noParser} with no parser`,
146
+ mut_skipped.nonExistent === 0
147
+ ? ''
148
+ : `${mut_skipped.nonExistent} non-existent`,
149
+ ].filter((part) => part !== '');
150
+
151
+ if (Arr.isNonEmpty(skipReport)) {
152
+ conditionalEcho(`Skipped ${skipReport.join(', ')}.`);
153
+ }
154
+
145
155
  if (results.every((r) => r.status === 'fulfilled')) {
146
156
  const fulfilled = results.map((r) => r.value);
147
157
 
@@ -1,7 +1,14 @@
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';
4
+ import {
5
+ Arr,
6
+ ISet,
7
+ isString,
8
+ pipe,
9
+ Result,
10
+ unknownToString,
11
+ } from 'ts-data-forge';
5
12
  import { type DeepReadonly, type StrictOmit } from 'ts-type-forge';
6
13
  import { assertPathExists } from './assert-path-exists.mjs';
7
14
  import { $ } from './exec-async.mjs';
@@ -135,7 +142,7 @@ export const genIndex = async (
135
142
 
136
143
  return Result.ok(undefined);
137
144
  } catch (error) {
138
- conditionalEcho(`❌ Index generation failed: ${String(error)}\n`);
145
+ conditionalEcho(`❌ Index generation failed: ${unknownToString(error)}\n`);
139
146
 
140
147
  return Result.err(error);
141
148
  }
@@ -285,7 +292,7 @@ const generateIndexFileForDir = async (
285
292
  }
286
293
  } catch (error) {
287
294
  throw new Error(
288
- `Failed to generate index for directory ${dirPath}: ${String(error)}`,
295
+ `Failed to generate index for directory ${dirPath}: ${unknownToString(error)}`,
289
296
  { cause: error },
290
297
  );
291
298
  }
@@ -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]