@reliverse/pathkit 1.1.0 → 1.1.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.
package/README.md CHANGED
@@ -6,13 +6,13 @@
6
6
 
7
7
  ## Key Features
8
8
 
9
- - 🔹 **drop in** and replace `node:path` and `unjs/pathe` instantly.
10
- - ➕ **`unjs/pathe` on steroids** – alias resolution, import parsing, and more.
11
- - 🌀 **always `/`** – posix separators 100% of the time (buh‑bye `\\`).
12
- - ⚙️ **node.js api compatible** – familiar methods, no learning curve.
13
- - 🚀 **modern & fast** – typescript, pure esm, bun & node‑ready.
14
- - 🧠 **predictable & testable** – deterministic output across windows / macos / linux.
15
- - 🧼 **no dependencies** – just the path api + a couple of cool utilities.
9
+ - 🔹 **drop in** and replace `node:path` and `unjs/pathe` instantly
10
+ - ➕ **`unjs/pathe` on steroids** – alias resolution, import parsing, and more
11
+ - 🌀 **always `/`** – posix separators 100% of the time (buh‑bye `\\`)
12
+ - ⚙️ **node.js api compatible** – familiar methods, no learning curve
13
+ - 🚀 **modern & fast** – typescript, pure esm, bun & node‑ready
14
+ - 🧠 **predictable & testable** – deterministic output across windows / macos / linux
15
+ - 🧼 **no dependencies** – just better path api + couple of cool utilities = [4.2 kB](https://bundlephobia.com/package/@reliverse/pathkit@latest)
16
16
 
17
17
  ## Installation
18
18
 
@@ -24,18 +24,18 @@ bun add @reliverse/pathkit
24
24
  **Migrate**:
25
25
 
26
26
  ```bash
27
- bun rm pathe
28
27
  # soon:
29
28
  # bun add -D @reliverse/dler
29
+ # bun dler migrate --lib path-to-pathkit
30
30
  # bun dler migrate --lib pathe-to-pathkit
31
31
  ```
32
32
 
33
- ### `pathe` vs `pathkit`
33
+ ### `unjs/pathe` vs `@reliverse/pathkit`
34
34
 
35
35
  | Package | What you get | When to use |
36
36
  |---------|--------------|-------------|
37
- | **`@reliverse/pathe`** | Core path API (POSIX everywhere) | You only need a drop‑in for `node:path` |
38
- | **`@reliverse/pathkit`** | Everything in `pathe` **+** advanced utilities | You need alias resolution, import transforms, etc. |
37
+ | **`pathe`** | Path API only (with POSIX everywhere) | You only need a drop‑in for `node:path` |
38
+ | **`pathkit`** | Everything in `pathe` **+** advanced utilities | You need alias resolution, import transforms, etc. |
39
39
 
40
40
  ## Why Pathkit? — The Problem with Native Paths
41
41
 
@@ -196,13 +196,11 @@ console.log(reverseResolveAlias("/src/utils", aliases)); // "@/utils"
196
196
  ```ts
197
197
  import {
198
198
  filename, // Strip extension
199
- extractPackageName, // Get package name from import
200
199
  normalizeQuotes, // Standardize quote style
201
200
  matchesGlob // Test glob patterns
202
201
  } from "@reliverse/pathkit";
203
202
 
204
203
  console.log(filename("/path/component.vue")); // "component"
205
- console.log(extractPackageName("@scope/pkg")); // "@scope/pkg"
206
204
  console.log(normalizeQuotes("import 'pkg'")); // 'import "pkg"'
207
205
  console.log(matchesGlob("file.ts", "**/*.ts")); // true
208
206
  ```
package/bin/mod.d.ts CHANGED
@@ -130,6 +130,68 @@ declare function convertImportsExt({ targetDir, extFrom, extTo, }: {
130
130
  to: string;
131
131
  }[];
132
132
  }[]>;
133
+ /**
134
+ * strips a specified number of segments from the beginning of a path
135
+ * @param path - The path to strip segments from
136
+ * @param count - Number of segments to strip (default: 1)
137
+ * @param alias - Optional alias to preserve (e.g. "@", "~")
138
+ * @returns The path with the specified number of segments removed
139
+ */
140
+ declare function stripPathSegments(path: string, count?: number, alias?: string): string;
141
+ /**
142
+ * recursively processes files in a directory to strip path segments from their contents
143
+ * @param targetDir - The directory to process
144
+ * @param segmentsToStrip - Number of segments to strip from paths
145
+ * @param alias - Optional alias to preserve (e.g. "@", "~")
146
+ * @param extensionsToProcess - Array of file extensions to process (default: EXTENSIONS)
147
+ * @returns Array of processed files and their changes
148
+ */
149
+ declare function stripPathSegmentsInDirectory({ targetDir, segmentsToStrip, alias, extensionsToProcess, }: {
150
+ targetDir: string;
151
+ segmentsToStrip: number;
152
+ alias?: string;
153
+ extensionsToProcess?: string[];
154
+ }): Promise<{
155
+ file: string;
156
+ changes: {
157
+ from: string;
158
+ to: string;
159
+ }[];
160
+ }[]>;
161
+ /**
162
+ * attaches path segments to an existing path
163
+ * @param path - The base path to attach segments to
164
+ * @param segments - The segments to attach
165
+ * @param options - Configuration options
166
+ * @returns The path with segments attached
167
+ */
168
+ declare function attachPathSegments(path: string, segments: string | string[], options?: {
169
+ position?: "before" | "after";
170
+ normalize?: boolean;
171
+ ensureSlash?: boolean;
172
+ preserveRoot?: boolean;
173
+ preserveAlias?: string;
174
+ }): string;
175
+ /**
176
+ * recursively processes files in a directory to attach path segments to import statements
177
+ * @param targetDir - The directory to process
178
+ * @param segments - The segments to attach
179
+ * @param options - Configuration options for path segment attachment
180
+ * @param extensionsToProcess - Array of file extensions to process (default: EXTENSIONS)
181
+ * @returns Array of processed files and their changes
182
+ */
183
+ declare function attachPathSegmentsInDirectory({ targetDir, segments, options, extensionsToProcess, }: {
184
+ targetDir: string;
185
+ segments: string | string[];
186
+ options?: Parameters<typeof attachPathSegments>[2];
187
+ extensionsToProcess?: string[];
188
+ }): Promise<{
189
+ file: string;
190
+ changes: {
191
+ from: string;
192
+ to: string;
193
+ }[];
194
+ }[]>;
133
195
  declare const _pathBase: {
134
196
  sep: string;
135
197
  normalize: (path: string) => string;
@@ -166,5 +228,5 @@ declare const path: PlatformPath & {
166
228
  declare const win32: PlatformPath;
167
229
  declare const delimiter: string;
168
230
  export type { PlatformPath, PathExtFilter, ImportExtType };
169
- export { _pathBase as posix, win32, basename, delimiter, dirname, extname, filename, format, isAbsolute, join, normalize, parse, relative, resolve, sep, toNamespacedPath, normalizeAliases, resolveAlias, reverseResolveAlias, normalizeWindowsPath, cleanDirs, copyDir, convertStringAliasRelative, convertImportsAliasToRelative, convertImportsExt, };
231
+ export { _pathBase as posix, win32, basename, delimiter, dirname, extname, filename, format, isAbsolute, join, normalize, parse, relative, resolve, sep, toNamespacedPath, normalizeAliases, resolveAlias, reverseResolveAlias, normalizeWindowsPath, cleanDirs, copyDir, convertStringAliasRelative, convertImportsAliasToRelative, convertImportsExt, stripPathSegments, stripPathSegmentsInDirectory, attachPathSegments, attachPathSegmentsInDirectory, };
170
232
  export default path;
package/bin/mod.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
+ const log = (msg) => console.log(`\x1B[2m${msg}\x1B[0m`);
3
+ const logInternal = (msg) => console.log(`\x1B[36;2m${msg}\x1B[0m`);
2
4
  const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
3
5
  const SLASH = "/";
4
6
  const BACK_SLASH = "\\";
@@ -13,13 +15,12 @@ const IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
13
15
  const ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
14
16
  const PATH_ROOT_RE = /^[/\\]|^[a-zA-Z]:[/\\]/;
15
17
  const IMPORT_REGEX = /(?:import\s+(?:[\s\S]*?)\s+from\s+|import\s*\(\s*)\s*(['"])((?:@)[^'"]+)\1/g;
16
- const log = (msg) => console.log(`\x1B[2m${msg}\x1B[0m`);
17
18
  async function cleanDirs(dirs) {
18
19
  await Promise.all(
19
20
  dirs.map(async (d) => {
20
21
  try {
21
22
  await fs.rm(d, { recursive: true, force: true });
22
- log(`\u2713 cleaned: ${d}`);
23
+ logInternal(`\u2713 cleaned: ${d}`);
23
24
  } catch (error) {
24
25
  log(
25
26
  `\u2717 error cleaning ${d}: ${error instanceof Error ? error.message : String(error)}`
@@ -29,7 +30,7 @@ async function cleanDirs(dirs) {
29
30
  );
30
31
  }
31
32
  async function copyDir(src, dest) {
32
- log(`\u2713 copying: ${src} \u2192 ${dest}`);
33
+ logInternal(`\u2713 copying: ${src} \u2192 ${dest}`);
33
34
  try {
34
35
  await fs.mkdir(dest, { recursive: true });
35
36
  const entries = await fs.readdir(src, { withFileTypes: true });
@@ -41,7 +42,7 @@ async function copyDir(src, dest) {
41
42
  return copyDir(srcPath, destPath);
42
43
  }
43
44
  await fs.copyFile(srcPath, destPath);
44
- log(` copied: ${srcPath} \u2192 ${destPath}`);
45
+ logInternal(` copied: ${srcPath} \u2192 ${destPath}`);
45
46
  })
46
47
  );
47
48
  } catch (error) {
@@ -376,6 +377,11 @@ function reverseResolveAlias(path2, aliases) {
376
377
  return matches.sort((a, b) => b.length - a.length);
377
378
  }
378
379
  const findAliasMatch = (importPath, paths) => {
380
+ const firstPathKey = Object.keys(paths)[0];
381
+ const baseAlias = firstPathKey.replace("/*", "");
382
+ if (importPath.startsWith("@") && !importPath.startsWith(baseAlias)) {
383
+ return null;
384
+ }
379
385
  if (paths[importPath]?.[0]) {
380
386
  return {
381
387
  key: importPath,
@@ -447,6 +453,10 @@ async function convertStringAliasRelative({
447
453
  pathPattern,
448
454
  targetDir
449
455
  }) {
456
+ const baseAlias = pathPattern.replace("/*", "");
457
+ if (importPath.startsWith("@") && !importPath.startsWith(baseAlias)) {
458
+ return importPath;
459
+ }
450
460
  const paths = { [pathPattern]: ["./*"] };
451
461
  const importerDir = dirname(importerFile);
452
462
  const match = findAliasMatch(importPath, paths);
@@ -478,9 +488,16 @@ async function processFile(filePath, aliasToReplace, targetDir, pathExtFilter) {
478
488
  const changes = [];
479
489
  const matches = Array.from(content.matchAll(IMPORT_REGEX));
480
490
  const normalizedAlias = aliasToReplace.endsWith("/*") ? aliasToReplace : `${aliasToReplace}/*`;
491
+ const baseAlias = aliasToReplace.replace("/*", "");
481
492
  for (const match of matches) {
482
493
  const originalQuote = match[1];
483
494
  const importPath = match[2];
495
+ if (!importPath.startsWith(baseAlias) && !importPath.startsWith("@")) {
496
+ continue;
497
+ }
498
+ if (importPath.startsWith("@") && !importPath.startsWith(baseAlias)) {
499
+ continue;
500
+ }
484
501
  const importExt = extname(importPath);
485
502
  const shouldProcess = pathExtFilter === "js" && importExt === ".js" || pathExtFilter === "ts" && importExt === ".ts" || pathExtFilter === "none" && importExt === "" || pathExtFilter === "js-ts-none";
486
503
  if (!shouldProcess) continue;
@@ -499,7 +516,7 @@ async function processFile(filePath, aliasToReplace, targetDir, pathExtFilter) {
499
516
  }
500
517
  if (content !== updated) {
501
518
  await fs.writeFile(filePath, updated);
502
- log(`\u2713 processed: ${filePath}`);
519
+ logInternal(`\u2713 processed: ${filePath}`);
503
520
  }
504
521
  return changes;
505
522
  }
@@ -517,8 +534,7 @@ async function processAllFiles({
517
534
  entries.map(async (entry) => {
518
535
  const fullPath = join(srcDir, entry.name);
519
536
  if (entry.isDirectory()) {
520
- if (entry.name === "node_modules" || entry.name.startsWith("."))
521
- return;
537
+ if (entry.name === "node_modules") return;
522
538
  const subdirResults = await processAllFiles({
523
539
  srcDir: fullPath,
524
540
  aliasToReplace,
@@ -537,6 +553,8 @@ async function processAllFiles({
537
553
  if (changes.length > 0) {
538
554
  results.push({ file: fullPath, changes });
539
555
  }
556
+ } else {
557
+ logInternal(` - skipping non-matching file: ${entry.name}`);
540
558
  }
541
559
  })
542
560
  );
@@ -559,7 +577,7 @@ async function convertImportsAliasToRelative({
559
577
  `Converting aliased imports starting with '${aliasToReplace}' to relative paths in "${targetDir}"...`
560
578
  );
561
579
  log(` (Assuming "${normalizedAlias}" resolves relative to "${targetDir}")`);
562
- log(` (Using extension mode: ${pathExtFilter})`);
580
+ logInternal(` (Using extension mode: ${pathExtFilter})`);
563
581
  const results = await processAllFiles({
564
582
  srcDir: targetDir,
565
583
  aliasToReplace: normalizedAlias,
@@ -586,6 +604,9 @@ async function convertImportsExt({
586
604
  extFrom,
587
605
  extTo
588
606
  }) {
607
+ logInternal(
608
+ `Converting import extensions from '${extFrom}' to '${extTo}' in "${targetDir}"...`
609
+ );
589
610
  const fromExtStr = extFrom === "none" ? "" : `.${extFrom}`;
590
611
  const toExtStr = extTo === "none" ? "" : `.${extTo}`;
591
612
  const importRegex = new RegExp(
@@ -599,9 +620,7 @@ async function convertImportsExt({
599
620
  entries.map(async (entry) => {
600
621
  const fullPath = join(targetDir, entry.name);
601
622
  if (entry.isDirectory()) {
602
- if (entry.name === "node_modules" || entry.name.startsWith(".")) {
603
- return;
604
- }
623
+ if (entry.name === "node_modules") return;
605
624
  const subdirResults = await convertImportsExt({
606
625
  targetDir: fullPath,
607
626
  extFrom,
@@ -632,11 +651,13 @@ async function convertImportsExt({
632
651
  }
633
652
  if (content !== updated) {
634
653
  await fs.writeFile(fullPath, updated);
635
- log(`\u2713 processed: ${fullPath}`);
654
+ logInternal(`\u2713 processed: ${fullPath}`);
636
655
  if (changes.length > 0) {
637
656
  results.push({ file: fullPath, changes });
638
657
  }
639
658
  }
659
+ } else {
660
+ logInternal(` - skipping non-matching file: ${entry.name}`);
640
661
  }
641
662
  })
642
663
  );
@@ -651,6 +672,265 @@ async function convertImportsExt({
651
672
  }
652
673
  } else {
653
674
  }
675
+ logInternal("Extension conversion complete.");
676
+ return results;
677
+ } catch (error) {
678
+ log(
679
+ `error processing directory ${targetDir}: ${error instanceof Error ? error.message : String(error)}`
680
+ );
681
+ return [];
682
+ }
683
+ }
684
+ function stripPathSegments(path2, count = 1, alias = "") {
685
+ if (typeof path2 !== "string" || path2.length === 0) return path2;
686
+ if (count <= 0) return path2;
687
+ logInternal(`[stripPathSegments] Processing path: ${path2}`);
688
+ logInternal(` - count: ${count}, alias: ${alias}`);
689
+ const normalizedPath = normalizeWindowsPath(path2);
690
+ logInternal(` - normalized: ${normalizedPath}`);
691
+ const parsed = parse(normalizedPath);
692
+ logInternal(` - parsed: ${JSON.stringify(parsed)}`);
693
+ let pathSegments = [];
694
+ if (parsed.dir && parsed.dir !== parsed.root) {
695
+ let dirRelativeToRoot = parsed.dir;
696
+ if (parsed.root && parsed.dir.startsWith(parsed.root)) {
697
+ dirRelativeToRoot = parsed.dir.substring(parsed.root.length);
698
+ }
699
+ pathSegments.push(...dirRelativeToRoot.split(SLASH));
700
+ }
701
+ if (parsed.base) {
702
+ pathSegments.push(parsed.base);
703
+ }
704
+ pathSegments = pathSegments.filter(Boolean);
705
+ logInternal(` - initial segments: ${JSON.stringify(pathSegments)}`);
706
+ const leadingPreservedSegments = [];
707
+ if (alias && pathSegments.length > 0 && pathSegments[0].startsWith(alias)) {
708
+ const preserved = pathSegments.shift();
709
+ leadingPreservedSegments.push(preserved);
710
+ logInternal(` - preserved alias segment: ${preserved}`);
711
+ }
712
+ while (pathSegments.length > 0 && (pathSegments[0] === DOT || pathSegments[0] === DOUBLE_DOT)) {
713
+ const preserved = pathSegments.shift();
714
+ leadingPreservedSegments.push(preserved);
715
+ logInternal(` - preserved relative segment: ${preserved}`);
716
+ }
717
+ const numToStrip = Math.min(count, pathSegments.length);
718
+ const remainingBodySegments = pathSegments.slice(numToStrip);
719
+ logInternal(
720
+ ` - stripping ${numToStrip} segments from: ${JSON.stringify(pathSegments)}`
721
+ );
722
+ logInternal(
723
+ ` - remaining body segments: ${JSON.stringify(remainingBodySegments)}`
724
+ );
725
+ const pathRoot = parsed.root;
726
+ const effectiveSegments = [
727
+ ...leadingPreservedSegments,
728
+ ...remainingBodySegments
729
+ ];
730
+ logInternal(` - effective segments: ${JSON.stringify(effectiveSegments)}`);
731
+ let result;
732
+ if (effectiveSegments.length === 0) {
733
+ result = normalize(pathRoot || DOT);
734
+ } else if (pathRoot) {
735
+ result = join(pathRoot, ...effectiveSegments);
736
+ } else {
737
+ result = join(...effectiveSegments);
738
+ }
739
+ logInternal(` - final result: ${result}`);
740
+ return result;
741
+ }
742
+ async function stripPathSegmentsInDirectory({
743
+ targetDir,
744
+ segmentsToStrip,
745
+ alias = "",
746
+ extensionsToProcess = EXTENSIONS
747
+ }) {
748
+ log(`[stripPathSegmentsInDirectory] Processing directory: ${targetDir}`);
749
+ log(` - segmentsToStrip: ${segmentsToStrip}, alias: ${alias}`);
750
+ logInternal(` - extensions: ${JSON.stringify(extensionsToProcess)}`);
751
+ try {
752
+ const entries = await fs.readdir(targetDir, { withFileTypes: true });
753
+ const results = [];
754
+ await Promise.all(
755
+ entries.map(async (entry) => {
756
+ const fullPath = join(targetDir, entry.name);
757
+ if (entry.isDirectory()) {
758
+ if (entry.name === "node_modules") return;
759
+ logInternal(` - recursing into directory: ${entry.name}`);
760
+ const subdirResults = await stripPathSegmentsInDirectory({
761
+ targetDir: fullPath,
762
+ segmentsToStrip,
763
+ alias,
764
+ extensionsToProcess
765
+ });
766
+ results.push(...subdirResults);
767
+ } else if (extensionsToProcess.includes(extname(entry.name))) {
768
+ logInternal(` Processing file: ${entry.name}`);
769
+ const content = await fs.readFile(fullPath, "utf-8");
770
+ let updated = content;
771
+ const changes = [];
772
+ const matches = Array.from(content.matchAll(IMPORT_REGEX));
773
+ logInternal(` - found ${matches.length} import statements`);
774
+ for (const match of matches) {
775
+ const originalQuote = match[1];
776
+ const importPath = match[2];
777
+ if (!importPath.includes(SLASH)) {
778
+ logInternal(` - skipping non-path import: ${importPath}`);
779
+ continue;
780
+ }
781
+ if (importPath.startsWith("@") && !importPath.startsWith(alias.replace("/*", ""))) {
782
+ logInternal(` - skipping npm package import: ${importPath}`);
783
+ continue;
784
+ }
785
+ logInternal(` Processing import: ${importPath}`);
786
+ const strippedPath = stripPathSegments(
787
+ importPath,
788
+ segmentsToStrip,
789
+ alias
790
+ );
791
+ if (importPath === strippedPath) {
792
+ logInternal(" - no changes needed");
793
+ continue;
794
+ }
795
+ changes.push({ from: importPath, to: strippedPath });
796
+ logInternal(` - transformed: ${importPath} \u2192 ${strippedPath}`);
797
+ const searchStr = `${originalQuote}${importPath}${originalQuote}`;
798
+ const replaceStr = `${originalQuote}${strippedPath}${originalQuote}`;
799
+ updated = replaceAllInString(updated, searchStr, replaceStr);
800
+ }
801
+ if (content !== updated) {
802
+ await fs.writeFile(fullPath, updated);
803
+ logInternal(" \u2713 wrote changes to file");
804
+ if (changes.length > 0) {
805
+ results.push({ file: fullPath, changes });
806
+ }
807
+ } else {
808
+ logInternal(" - no changes made to file");
809
+ }
810
+ } else {
811
+ logInternal(` - skipping non-matching file: ${entry.name}`);
812
+ }
813
+ })
814
+ );
815
+ if (results.length > 0) {
816
+ log("[stripPathSegmentsInDirectory] Summary of changes:");
817
+ for (const { file, changes } of results) {
818
+ const displayPath = relative(targetDir, file) || basename(file);
819
+ log(` in ${displayPath}:`);
820
+ for (const { from, to } of changes) {
821
+ log(` - ${from} \u2192 ${to}`);
822
+ }
823
+ }
824
+ } else {
825
+ logInternal(" No changes were made in any files");
826
+ }
827
+ return results;
828
+ } catch (error) {
829
+ log(
830
+ `error processing directory ${targetDir}: ${error instanceof Error ? error.message : String(error)}`
831
+ );
832
+ return [];
833
+ }
834
+ }
835
+ function attachPathSegments(path2, segments, options = {}) {
836
+ if (typeof path2 !== "string" || path2.length === 0) return path2;
837
+ const {
838
+ position = "after",
839
+ normalize: shouldNormalize = true,
840
+ ensureSlash = true,
841
+ preserveRoot = true,
842
+ preserveAlias
843
+ } = options;
844
+ const segmentsArray = Array.isArray(segments) ? segments : [segments];
845
+ const validSegments = segmentsArray.filter((s) => s.length > 0);
846
+ if (validSegments.length === 0) return path2;
847
+ const basePath = shouldNormalize ? normalizeWindowsPath(path2) : path2;
848
+ let alias = "";
849
+ let pathWithoutAlias = basePath;
850
+ if (preserveAlias && position === "before") {
851
+ const aliasMatch = new RegExp(`^${preserveAlias.replace("*", ".*")}`).exec(
852
+ basePath
853
+ );
854
+ if (aliasMatch) {
855
+ alias = aliasMatch[0];
856
+ pathWithoutAlias = basePath.slice(alias.length);
857
+ }
858
+ }
859
+ const isAbsolute2 = IS_ABSOLUTE_RE.test(pathWithoutAlias);
860
+ const root = preserveRoot && isAbsolute2 ? SLASH : "";
861
+ const pathWithoutRoot = isAbsolute2 ? pathWithoutAlias.slice(1) : pathWithoutAlias;
862
+ const joinedSegments = validSegments.join(SLASH);
863
+ let result;
864
+ if (position === "before") {
865
+ result = ensureSlash ? `${alias}${root}${joinedSegments}${SLASH}${pathWithoutRoot}` : `${alias}${root}${joinedSegments}${pathWithoutRoot}`;
866
+ } else {
867
+ result = ensureSlash ? `${alias}${root}${pathWithoutRoot}${SLASH}${joinedSegments}` : `${alias}${root}${pathWithoutRoot}${joinedSegments}`;
868
+ }
869
+ return shouldNormalize ? normalize(result) : result;
870
+ }
871
+ async function attachPathSegmentsInDirectory({
872
+ targetDir,
873
+ segments,
874
+ options = {},
875
+ extensionsToProcess = EXTENSIONS
876
+ }) {
877
+ try {
878
+ const entries = await fs.readdir(targetDir, { withFileTypes: true });
879
+ const results = [];
880
+ await Promise.all(
881
+ entries.map(async (entry) => {
882
+ const fullPath = join(targetDir, entry.name);
883
+ if (entry.isDirectory()) {
884
+ if (entry.name === "node_modules") return;
885
+ const subdirResults = await attachPathSegmentsInDirectory({
886
+ targetDir: fullPath,
887
+ segments,
888
+ options,
889
+ extensionsToProcess
890
+ });
891
+ results.push(...subdirResults);
892
+ } else if (extensionsToProcess.includes(extname(entry.name))) {
893
+ const content = await fs.readFile(fullPath, "utf-8");
894
+ let updated = content;
895
+ const changes = [];
896
+ const matches = Array.from(content.matchAll(IMPORT_REGEX));
897
+ for (const match of matches) {
898
+ const originalQuote = match[1];
899
+ const importPath = match[2];
900
+ if (!importPath.includes(SLASH)) continue;
901
+ const modifiedPath = attachPathSegments(
902
+ importPath,
903
+ segments,
904
+ options
905
+ );
906
+ if (importPath === modifiedPath) continue;
907
+ changes.push({ from: importPath, to: modifiedPath });
908
+ const searchStr = `${originalQuote}${importPath}${originalQuote}`;
909
+ const replaceStr = `${originalQuote}${modifiedPath}${originalQuote}`;
910
+ updated = replaceAllInString(updated, searchStr, replaceStr);
911
+ }
912
+ if (content !== updated) {
913
+ await fs.writeFile(fullPath, updated);
914
+ logInternal(`\u2713 processed: ${fullPath}`);
915
+ if (changes.length > 0) {
916
+ results.push({ file: fullPath, changes });
917
+ }
918
+ }
919
+ } else {
920
+ logInternal(` - skipping non-matching file: ${entry.name}`);
921
+ }
922
+ })
923
+ );
924
+ if (results.length > 0) {
925
+ log("\n[attachPathSegmentsInDirectory] Summary of changes:");
926
+ for (const { file, changes } of results) {
927
+ const displayPath = relative(targetDir, file) || basename(file);
928
+ log(` in ${displayPath}:`);
929
+ for (const { from, to } of changes) {
930
+ log(` - ${from} \u2192 ${to}`);
931
+ }
932
+ }
933
+ }
654
934
  return results;
655
935
  } catch (error) {
656
936
  log(
@@ -743,6 +1023,10 @@ export {
743
1023
  copyDir,
744
1024
  convertStringAliasRelative,
745
1025
  convertImportsAliasToRelative,
746
- convertImportsExt
1026
+ convertImportsExt,
1027
+ stripPathSegments,
1028
+ stripPathSegmentsInDirectory,
1029
+ attachPathSegments,
1030
+ attachPathSegmentsInDirectory
747
1031
  };
748
1032
  export default path;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "license": "MIT",
6
6
  "name": "@reliverse/pathkit",
7
7
  "type": "module",
8
- "version": "1.1.0",
8
+ "version": "1.1.2",
9
9
  "devDependencies": {
10
10
  "@biomejs/biome": "1.9.4",
11
11
  "@eslint/js": "^9.27.0",
@@ -19,7 +19,7 @@
19
19
  "eslint": "^9.27.0",
20
20
  "eslint-plugin-no-relative-import-paths": "^1.6.1",
21
21
  "eslint-plugin-perfectionist": "^4.13.0",
22
- "knip": "^5.57.0",
22
+ "knip": "^5.57.1",
23
23
  "magic-string": "^0.30.17",
24
24
  "p-map": "^7.0.3",
25
25
  "typescript": "^5.8.3",