@decohere/core 0.0.1-dev.125 → 0.0.1-dev.5

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.
Files changed (252) hide show
  1. package/package.json +40 -15
  2. package/src/caching/cache-manager.ts +102 -0
  3. package/src/caching/cache.decorator.ts +56 -0
  4. package/src/caching/cache.ts +11 -0
  5. package/{es6/src/caching/index.js → src/caching/index.ts} +1 -1
  6. package/src/caching/invalidate-cache.decorator.ts +81 -0
  7. package/src/caching/memoize.decorator.ts +34 -0
  8. package/src/caching/node-cache-memoize.adapter.ts +47 -0
  9. package/src/cli/action.ts +77 -0
  10. package/src/cli/command.ts +173 -0
  11. package/src/cli/index.ts +30 -0
  12. package/src/cli/module.type.ts +48 -0
  13. package/src/cli/options.ts +81 -0
  14. package/src/collections/find-duplicates.ts +16 -0
  15. package/src/collections/merge-arrays.ts +9 -0
  16. package/src/collections/sum.ts +71 -0
  17. package/src/collections/unique-objects.ts +23 -0
  18. package/src/composition/index.ts +48 -0
  19. package/src/configuration/configuration.types.ts +13 -0
  20. package/src/diagnostics/index.ts +1 -0
  21. package/src/diagnostics/trace.ts +97 -0
  22. package/{es6/src/extensibility/decorator-factory-base.js → src/extensibility/decorator-factory-base.ts} +17 -9
  23. package/src/extensibility/decorator-factory.ts +44 -0
  24. package/src/extensibility/extension.ts +34 -0
  25. package/src/extensibility/mixwith.ts +42 -0
  26. package/src/extensibility/pipeline.ts +60 -0
  27. package/{types/src/extensibility/step.d.ts → src/extensibility/step.ts} +6 -4
  28. package/src/interop/docker/docker.ts +57 -0
  29. package/src/interop/errors/interop.error.ts +1 -0
  30. package/src/interop/git/git.ts +98 -0
  31. package/src/interop/git/git.types.ts +17 -0
  32. package/src/interop/index.ts +22 -0
  33. package/src/interop/node/index.ts +1 -0
  34. package/src/interop/node/node.ts +38 -0
  35. package/src/interop/npm/npm.ts +168 -0
  36. package/src/interop/npm/npm.types.ts +28 -0
  37. package/src/interop/pnpm/pnpm.ts +22 -0
  38. package/src/interop/pnpm/pnpm.types.ts +3 -0
  39. package/src/interop/shell/shell.argv.ts +31 -0
  40. package/src/interop/shell/shell.ts +100 -0
  41. package/src/interop/shell/shell.types.ts +9 -0
  42. package/src/interop/types/detect-package-manager.ts +45 -0
  43. package/src/interop/types/package-manager.ts +7 -0
  44. package/src/interop/yarn/yarn.ts +28 -0
  45. package/src/interop/yarn/yarn.types.ts +3 -0
  46. package/src/io/copy-file.ts +13 -0
  47. package/src/io/delete-file.ts +4 -0
  48. package/src/io/directory-exists.ts +9 -0
  49. package/src/io/file-exists.ts +10 -0
  50. package/src/io/find-directories.ts +13 -0
  51. package/src/io/find-file.ts +37 -0
  52. package/{es6/src/io/io.js → src/io/io.ts} +3 -0
  53. package/src/io/read-file.ts +25 -0
  54. package/src/io/remove-dir.ts +11 -0
  55. package/src/io/types/directory.type.ts +4 -0
  56. package/src/io/types/file.ts +34 -0
  57. package/src/io/url-to-path.ts +17 -0
  58. package/src/io/watch.ts +290 -0
  59. package/src/io/write-file.ts +21 -0
  60. package/src/objects/deep-merge.ts +131 -0
  61. package/{es6/src/reflection/index.js → src/reflection/index.ts} +1 -0
  62. package/src/reflection/types/reflection.types.ts +28 -0
  63. package/src/reflection/utilities.ts +92 -0
  64. package/src/semver/index.ts +1 -0
  65. package/src/semver/semver.ts +96 -0
  66. package/src/system/camel-case.ts +3 -0
  67. package/src/system/debounce.ts +13 -0
  68. package/src/system/decorator.ts +41 -0
  69. package/src/system/environment.ts +6 -0
  70. package/{types/src/system/function.types.d.ts → src/system/function.types.ts} +10 -2
  71. package/src/system/retry.decorator.ts +43 -0
  72. package/src/system/sleep.ts +6 -0
  73. package/src/system/type.utils.ts +5 -0
  74. package/src/testing/flush-promises.ts +7 -0
  75. package/src/testing/glob-with-memfs.ts +25 -0
  76. package/src/testing/index.ts +5 -0
  77. package/src/testing/memfs.ts +28 -0
  78. package/src/testing/mock-logger.ts +42 -0
  79. package/src/testing/spy-on-constructor.ts +33 -0
  80. package/tests/caching/cache-manager.spec.ts +147 -0
  81. package/tests/caching/cache-test-class.ts +91 -0
  82. package/tests/caching/cache.decorator.spec.ts +259 -0
  83. package/tests/caching/invalidate-cache-test-class.ts +52 -0
  84. package/tests/caching/invalidate-cache.decorator.spec.ts +120 -0
  85. package/tests/caching/memoize-test-class.ts +99 -0
  86. package/tests/caching/memoize.decorator.spec.ts +203 -0
  87. package/tests/cli/command.model.spec.ts +91 -0
  88. package/tests/collections/merge-arrays.spec.ts +50 -0
  89. package/tests/collections/sum.spec.ts +86 -0
  90. package/tests/collections/unique-objects.spec.ts +233 -0
  91. package/tests/extensibility/extension.spec.ts +29 -0
  92. package/tests/extensibility/pipeline.spec.ts +379 -0
  93. package/tests/interop/detect-package-manager.spec.ts +70 -0
  94. package/tests/interop/npm.spec.ts +129 -0
  95. package/tests/interop/pnpm.spec.ts +30 -0
  96. package/tests/interop/shell.spec.ts +47 -0
  97. package/tests/interop/yarn.spec.ts +18 -0
  98. package/tests/io/find-file.spec.ts +188 -0
  99. package/tests/io/watch.real-fs.spec.ts +76 -0
  100. package/tests/io/watch.spec.ts +291 -0
  101. package/tests/mock-prototype.ts +12 -0
  102. package/tests/semver/semver.spec.ts +51 -0
  103. package/tests/system/decorator.spec.ts +250 -0
  104. package/tests/system/retry.decorator.spec.ts +57 -0
  105. package/LICENSE +0 -15
  106. package/commonjs/src/caching/cache-manager.js +0 -98
  107. package/commonjs/src/caching/cache.decorator.js +0 -69
  108. package/commonjs/src/caching/cache.js +0 -6
  109. package/commonjs/src/caching/index.js +0 -23
  110. package/commonjs/src/caching/invalidate-cache.decorator.js +0 -92
  111. package/commonjs/src/caching/map-cache.js +0 -39
  112. package/commonjs/src/caching/memoize.decorator.js +0 -36
  113. package/commonjs/src/collections/collections.js +0 -21
  114. package/commonjs/src/collections/find-duplicates.js +0 -35
  115. package/commonjs/src/collections/index.js +0 -52
  116. package/commonjs/src/collections/merge-arrays.js +0 -34
  117. package/commonjs/src/collections/sum.js +0 -63
  118. package/commonjs/src/collections/unique-objects.js +0 -39
  119. package/commonjs/src/composition/index.js +0 -4
  120. package/commonjs/src/configuration/configuration.types.js +0 -11
  121. package/commonjs/src/configuration/index.js +0 -18
  122. package/commonjs/src/extensibility/decorator-factory-base.js +0 -22
  123. package/commonjs/src/extensibility/decorator-factory.js +0 -31
  124. package/commonjs/src/extensibility/extension.js +0 -37
  125. package/commonjs/src/extensibility/index.js +0 -23
  126. package/commonjs/src/extensibility/mixwith.js +0 -36
  127. package/commonjs/src/extensibility/pipeline.js +0 -56
  128. package/commonjs/src/extensibility/step.js +0 -18
  129. package/commonjs/src/io/copy-file.js +0 -25
  130. package/commonjs/src/io/delete-file.js +0 -21
  131. package/commonjs/src/io/directory-exists.js +0 -24
  132. package/commonjs/src/io/file-exists.js +0 -19
  133. package/commonjs/src/io/index.js +0 -66
  134. package/commonjs/src/io/io.js +0 -25
  135. package/commonjs/src/io/read-file.js +0 -39
  136. package/commonjs/src/io/remove-dir.js +0 -22
  137. package/commonjs/src/io/types/directory.type.js +0 -4
  138. package/commonjs/src/io/types/file.js +0 -28
  139. package/commonjs/src/io/types/index.js +0 -19
  140. package/commonjs/src/io/url-to-path.js +0 -33
  141. package/commonjs/src/io/write-file.js +0 -30
  142. package/commonjs/src/objects/deep-merge.js +0 -67
  143. package/commonjs/src/objects/index.js +0 -11
  144. package/commonjs/src/reflection/index.js +0 -20
  145. package/commonjs/src/reflection/types/index.js +0 -18
  146. package/commonjs/src/reflection/types/reflection.types.js +0 -26
  147. package/commonjs/src/reflection/utilities.js +0 -118
  148. package/commonjs/src/system/camel-case.js +0 -13
  149. package/commonjs/src/system/debounce.js +0 -19
  150. package/commonjs/src/system/decorator.js +0 -46
  151. package/commonjs/src/system/environment.js +0 -23
  152. package/commonjs/src/system/function.types.js +0 -18
  153. package/commonjs/src/system/index.js +0 -24
  154. package/commonjs/src/system/retry.decorator.js +0 -40
  155. package/commonjs/src/system/sleep.js +0 -13
  156. package/commonjs/src/system/type.utils.js +0 -4
  157. package/es6/src/caching/cache-manager.js +0 -82
  158. package/es6/src/caching/cache.decorator.js +0 -51
  159. package/es6/src/caching/cache.js +0 -3
  160. package/es6/src/caching/invalidate-cache.decorator.js +0 -71
  161. package/es6/src/caching/map-cache.js +0 -35
  162. package/es6/src/caching/memoize.decorator.js +0 -27
  163. package/es6/src/collections/find-duplicates.js +0 -17
  164. package/es6/src/collections/merge-arrays.js +0 -16
  165. package/es6/src/collections/sum.js +0 -45
  166. package/es6/src/collections/unique-objects.js +0 -21
  167. package/es6/src/composition/index.js +0 -1
  168. package/es6/src/configuration/configuration.types.js +0 -1
  169. package/es6/src/extensibility/decorator-factory.js +0 -21
  170. package/es6/src/extensibility/extension.js +0 -27
  171. package/es6/src/extensibility/mixwith.js +0 -26
  172. package/es6/src/extensibility/pipeline.js +0 -46
  173. package/es6/src/extensibility/step.js +0 -16
  174. package/es6/src/io/copy-file.js +0 -10
  175. package/es6/src/io/delete-file.js +0 -6
  176. package/es6/src/io/directory-exists.js +0 -9
  177. package/es6/src/io/file-exists.js +0 -9
  178. package/es6/src/io/read-file.js +0 -13
  179. package/es6/src/io/remove-dir.js +0 -7
  180. package/es6/src/io/types/directory.type.js +0 -1
  181. package/es6/src/io/types/file.js +0 -18
  182. package/es6/src/io/url-to-path.js +0 -15
  183. package/es6/src/io/write-file.js +0 -15
  184. package/es6/src/objects/deep-merge.js +0 -57
  185. package/es6/src/reflection/types/reflection.types.js +0 -8
  186. package/es6/src/reflection/utilities.js +0 -74
  187. package/es6/src/system/camel-case.js +0 -3
  188. package/es6/src/system/debounce.js +0 -9
  189. package/es6/src/system/decorator.js +0 -30
  190. package/es6/src/system/environment.js +0 -5
  191. package/es6/src/system/function.types.js +0 -26
  192. package/es6/src/system/retry.decorator.js +0 -30
  193. package/es6/src/system/sleep.js +0 -3
  194. package/es6/src/system/type.utils.js +0 -1
  195. package/types/src/caching/cache-manager.d.ts +0 -59
  196. package/types/src/caching/cache.d.ts +0 -11
  197. package/types/src/caching/cache.decorator.d.ts +0 -18
  198. package/types/src/caching/index.d.ts +0 -6
  199. package/types/src/caching/invalidate-cache.decorator.d.ts +0 -17
  200. package/types/src/caching/map-cache.d.ts +0 -23
  201. package/types/src/caching/memoize.decorator.d.ts +0 -16
  202. package/types/src/collections/collections.d.ts +0 -4
  203. package/types/src/collections/find-duplicates.d.ts +0 -2
  204. package/types/src/collections/index.d.ts +0 -1
  205. package/types/src/collections/merge-arrays.d.ts +0 -2
  206. package/types/src/collections/sum.d.ts +0 -4
  207. package/types/src/collections/unique-objects.d.ts +0 -2
  208. package/types/src/composition/index.d.ts +0 -37
  209. package/types/src/configuration/configuration.types.d.ts +0 -10
  210. package/types/src/configuration/index.d.ts +0 -1
  211. package/types/src/extensibility/decorator-factory-base.d.ts +0 -39
  212. package/types/src/extensibility/decorator-factory.d.ts +0 -19
  213. package/types/src/extensibility/extension.d.ts +0 -9
  214. package/types/src/extensibility/index.d.ts +0 -6
  215. package/types/src/extensibility/mixwith.d.ts +0 -6
  216. package/types/src/extensibility/pipeline.d.ts +0 -12
  217. package/types/src/io/copy-file.d.ts +0 -4
  218. package/types/src/io/delete-file.d.ts +0 -1
  219. package/types/src/io/directory-exists.d.ts +0 -1
  220. package/types/src/io/file-exists.d.ts +0 -1
  221. package/types/src/io/index.d.ts +0 -2
  222. package/types/src/io/io.d.ts +0 -8
  223. package/types/src/io/read-file.d.ts +0 -4
  224. package/types/src/io/remove-dir.d.ts +0 -5
  225. package/types/src/io/types/directory.type.d.ts +0 -4
  226. package/types/src/io/types/file.d.ts +0 -20
  227. package/types/src/io/types/index.d.ts +0 -2
  228. package/types/src/io/url-to-path.d.ts +0 -2
  229. package/types/src/io/write-file.d.ts +0 -5
  230. package/types/src/objects/deep-merge.d.ts +0 -41
  231. package/types/src/objects/index.d.ts +0 -1
  232. package/types/src/reflection/index.d.ts +0 -3
  233. package/types/src/reflection/types/index.d.ts +0 -1
  234. package/types/src/reflection/types/reflection.types.d.ts +0 -21
  235. package/types/src/reflection/utilities.d.ts +0 -27
  236. package/types/src/system/camel-case.d.ts +0 -1
  237. package/types/src/system/debounce.d.ts +0 -1
  238. package/types/src/system/decorator.d.ts +0 -15
  239. package/types/src/system/environment.d.ts +0 -6
  240. package/types/src/system/index.d.ts +0 -7
  241. package/types/src/system/retry.decorator.d.ts +0 -6
  242. package/types/src/system/sleep.d.ts +0 -1
  243. package/types/src/system/type.utils.d.ts +0 -1
  244. /package/{es6/src/collections/collections.js → src/collections/collections.ts} +0 -0
  245. /package/{es6/src/collections/index.js → src/collections/index.ts} +0 -0
  246. /package/{es6/src/configuration/index.js → src/configuration/index.ts} +0 -0
  247. /package/{es6/src/extensibility/index.js → src/extensibility/index.ts} +0 -0
  248. /package/{es6/src/io/index.js → src/io/index.ts} +0 -0
  249. /package/{es6/src/io/types/index.js → src/io/types/index.ts} +0 -0
  250. /package/{es6/src/objects/index.js → src/objects/index.ts} +0 -0
  251. /package/{es6/src/reflection/types/index.js → src/reflection/types/index.ts} +0 -0
  252. /package/{es6/src/system/index.js → src/system/index.ts} +0 -0
@@ -0,0 +1,28 @@
1
+ import { Shell } from "../shell/shell.js";
2
+ import type { PackageManager } from "../types/package-manager.js";
3
+ import type { YarnOptions } from "./yarn.types.js";
4
+
5
+ const Commands = {
6
+ install: "install",
7
+ };
8
+
9
+ export class Yarn extends Shell implements PackageManager {
10
+ public readonly name = "yarn" as const;
11
+ protected program: string = "yarn";
12
+
13
+ public async install(options: YarnOptions = { cwd: "./" }) {
14
+ const { cwd } = options;
15
+ return this.run({ cwd }, Commands.install);
16
+ }
17
+
18
+ public async syncLockFile(options: YarnOptions = { cwd: "./" }) {
19
+ const { cwd } = options;
20
+ return this.run(
21
+ { cwd },
22
+ Commands.install,
23
+ "--mode",
24
+ "update-lockfile",
25
+ "--ignore-scripts",
26
+ );
27
+ }
28
+ }
@@ -0,0 +1,3 @@
1
+ export type YarnOptions = {
2
+ cwd?: string;
3
+ };
@@ -0,0 +1,13 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ export type CopyFileOptions = {
5
+ mkdir?: boolean;
6
+ };
7
+
8
+ export async function copyFile(src: string, dest: string, options: CopyFileOptions = {}) {
9
+ if (options.mkdir) {
10
+ await fs.mkdir(path.dirname(dest), { recursive: true });
11
+ }
12
+ await fs.copyFile(src, dest);
13
+ }
@@ -0,0 +1,4 @@
1
+ import fs from "node:fs/promises";
2
+ export async function deleteFile(filePath: string) {
3
+ return fs.rm(filePath, { force: true });
4
+ }
@@ -0,0 +1,9 @@
1
+ import fs from "node:fs/promises";
2
+ export async function directoryExists(path: string): Promise<boolean> {
3
+ try {
4
+ await fs.access(path);
5
+ return true;
6
+ } catch {
7
+ return false;
8
+ }
9
+ }
@@ -0,0 +1,10 @@
1
+ import { stat } from "node:fs/promises";
2
+
3
+ export async function fileExists(path: string): Promise<boolean> {
4
+ try {
5
+ const stats = await stat(path);
6
+ return stats.isFile();
7
+ } catch {
8
+ return false;
9
+ }
10
+ }
@@ -0,0 +1,13 @@
1
+ import { glob } from "glob";
2
+ import type { Directory } from "./types/directory.type.js";
3
+
4
+ export async function findDirectories(...pattern: string[]): Promise<Directory[]> {
5
+ const paths = await glob(pattern, { withFileTypes: true });
6
+ const dirs = paths
7
+ .filter((p) => p.isDirectory())
8
+ .map((p) => ({
9
+ name: p.name,
10
+ parentPath: p.parentPath,
11
+ }));
12
+ return dirs;
13
+ }
@@ -0,0 +1,37 @@
1
+ import { glob } from "glob";
2
+ import { FileEx } from "./types/file.js";
3
+
4
+ export type FindFileOptions = {
5
+ cwd: string;
6
+ ignore?: string | string[];
7
+ };
8
+
9
+ export async function findFile(pattern: string) {
10
+ const files = await glob(pattern, { includeChildMatches: false });
11
+ if (files.length === 0) {
12
+ return;
13
+ }
14
+ const file = {
15
+ relativePath: files.at(0),
16
+ };
17
+ return file;
18
+ }
19
+
20
+ export async function findFiles(
21
+ patterns: string[],
22
+ options?: FindFileOptions,
23
+ ): Promise<FileEx[]> {
24
+ const paths = await glob(patterns, {
25
+ ...options,
26
+ withFileTypes: true,
27
+ });
28
+ const files = paths
29
+ .filter((p) => p.isFile())
30
+ .map((p) => {
31
+ return new FileEx({
32
+ relativePath: p.relative(),
33
+ fullPath: p.fullpath(),
34
+ });
35
+ });
36
+ return files;
37
+ }
@@ -2,7 +2,10 @@ export * from "./copy-file.js";
2
2
  export * from "./delete-file.js";
3
3
  export * from "./directory-exists.js";
4
4
  export * from "./file-exists.js";
5
+ export * from "./find-directories.js";
6
+ export * from "./find-file.js";
5
7
  export * from "./read-file.js";
6
8
  export * from "./remove-dir.js";
7
9
  export * from "./url-to-path.js";
10
+ export * from "./watch.js";
8
11
  export * from "./write-file.js";
@@ -0,0 +1,25 @@
1
+ import fs from "node:fs/promises";
2
+ import type { File } from "./types/file.js";
3
+ export async function readFiles(
4
+ filePaths: File[],
5
+ encoding: BufferEncoding = "utf8",
6
+ ): Promise<string[]> {
7
+ const data = await Promise.all(filePaths.map((file) => readFile(file.fullPath, encoding)));
8
+ return data;
9
+ }
10
+
11
+ export async function readFile(
12
+ filePath: string,
13
+ encoding: BufferEncoding = "utf8",
14
+ ): Promise<string> {
15
+ const data = await fs.readFile(filePath, encoding);
16
+ return data;
17
+ }
18
+
19
+ export async function readJsonFile<T>(
20
+ filePath: string,
21
+ encoding: BufferEncoding = "utf8",
22
+ ): Promise<T> {
23
+ const data = await readFile(filePath, encoding);
24
+ return JSON.parse(data);
25
+ }
@@ -0,0 +1,11 @@
1
+ import fs from "node:fs/promises";
2
+ export type RemoveDirOptions = {
3
+ force: boolean;
4
+ recursive: boolean;
5
+ };
6
+ export async function removeDir(
7
+ path: string,
8
+ options: RemoveDirOptions = { recursive: false, force: false },
9
+ ) {
10
+ await fs.rm(path, options);
11
+ }
@@ -0,0 +1,4 @@
1
+ export type Directory = {
2
+ name: string;
3
+ parentPath: string;
4
+ };
@@ -0,0 +1,34 @@
1
+ import { readFile } from "../read-file.js";
2
+
3
+ export type File = {
4
+ fullPath: string;
5
+ relativePath: string;
6
+ };
7
+
8
+ export class FileEx implements File {
9
+ public constructor(data: Partial<File> & { content?: string }) {
10
+ this.fullPath = data.fullPath;
11
+ this.relativePath = data.relativePath ?? data.fullPath;
12
+ this.#content = data.content;
13
+ }
14
+ #content?: string;
15
+ public fullPath: string;
16
+ public relativePath: string;
17
+ public toString() {
18
+ return this.relativePath;
19
+ }
20
+ public async getContent() {
21
+ if (this.#content != null) return this.#content;
22
+ return readFile(this.fullPath);
23
+ }
24
+ }
25
+
26
+ export type Watcher = {
27
+ readonly ready: Promise<void>;
28
+ on(event: "ready", handler: () => unknown): Watcher;
29
+ on(
30
+ event: "add" | "change" | "addDir" | "unlink" | "unlinkDir",
31
+ handler: (file: File) => unknown,
32
+ ): Watcher;
33
+ close(): void;
34
+ };
@@ -0,0 +1,17 @@
1
+ import { fileURLToPath, pathToFileURL } from "node:url";
2
+
3
+ export const fileUrlToPath = (url: string) => {
4
+ try {
5
+ return fileURLToPath(url);
6
+ } catch {
7
+ return url;
8
+ }
9
+ };
10
+
11
+ export const pathToFileUrl = (path: string) => {
12
+ try {
13
+ return pathToFileURL(path).toString();
14
+ } catch {
15
+ return path;
16
+ }
17
+ };
@@ -0,0 +1,290 @@
1
+ import { type FSWatcher, watch as nativeWatch, readdirSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import picomatch from "picomatch";
4
+ import { type File, FileEx, type Watcher } from "./types/file.js";
5
+
6
+ export type WatchOptions = {
7
+ include?: string[];
8
+ ignore?: string[];
9
+ cwd?: string;
10
+ ignoreInitial?: boolean;
11
+ };
12
+
13
+ export type WatchEventName = "add" | "change" | "addDir" | "unlink" | "unlinkDir";
14
+ export const WatchEvents = {
15
+ add: "add",
16
+ change: "change",
17
+ addDir: "addDir",
18
+ unlink: "unlink",
19
+ unlinkDir: "unlinkDir",
20
+ } satisfies Record<string, WatchEventName>;
21
+ export function watch(directory: string, options: WatchOptions = { cwd: "." }) {
22
+ const { include, ignore, ignoreInitial = false } = options;
23
+
24
+ const changeCoalesceMs = 25;
25
+
26
+ const handlers = {
27
+ ready: new Set<() => unknown>(),
28
+ add: new Set<(file: File) => unknown>(),
29
+ change: new Set<(file: File) => unknown>(),
30
+ addDir: new Set<(file: File) => unknown>(),
31
+ unlink: new Set<(file: File) => unknown>(),
32
+ unlinkDir: new Set<(file: File) => unknown>(),
33
+ };
34
+
35
+ let resolveReady: (() => void) | undefined;
36
+ const ready = new Promise<void>((resolve) => {
37
+ resolveReady = resolve;
38
+ });
39
+
40
+ const snapshot = buildSnapshot(directory);
41
+
42
+ const isFiltered = (relativePath: string) => {
43
+ const posixRelativePath = toPosixPath(relativePath);
44
+ const posixFullPath = toPosixPath(path.resolve(directory, relativePath));
45
+ const matchesEitherPath = (patterns: string[]) =>
46
+ picomatch.isMatch(posixRelativePath, patterns) ||
47
+ picomatch.isMatch(posixFullPath, patterns);
48
+
49
+ if (include && include.length > 0) {
50
+ return !matchesEitherPath(include);
51
+ }
52
+
53
+ if (ignore && ignore.length > 0) {
54
+ return matchesEitherPath(ignore);
55
+ }
56
+
57
+ return false;
58
+ };
59
+
60
+ const emit = (event: WatchEventName, relativePath: string) => {
61
+ if (isFiltered(relativePath)) {
62
+ return;
63
+ }
64
+
65
+ const fullPath = path.resolve(directory, relativePath);
66
+ for (const handler of handlers[event]) {
67
+ handler(new FileEx({ fullPath, relativePath }));
68
+ }
69
+ };
70
+
71
+ const pendingChangeTimers = new Map<string, ReturnType<typeof setTimeout>>();
72
+
73
+ const emitChange = (relativePath: string) => {
74
+ const existing = pendingChangeTimers.get(relativePath);
75
+ if (existing != null) {
76
+ clearTimeout(existing);
77
+ }
78
+
79
+ const timer = setTimeout(() => {
80
+ pendingChangeTimers.delete(relativePath);
81
+ emit("change", relativePath);
82
+ }, changeCoalesceMs);
83
+
84
+ pendingChangeTimers.set(relativePath, timer);
85
+ };
86
+
87
+ const emitReady = () => {
88
+ for (const handler of handlers.ready) {
89
+ handler();
90
+ }
91
+ };
92
+
93
+ queueMicrotask(() => {
94
+ if (!ignoreInitial) {
95
+ for (const [relativePath, entry] of snapshot) {
96
+ emit(entry.isDirectory ? "addDir" : "add", relativePath);
97
+ }
98
+ }
99
+
100
+ emitReady();
101
+ resolveReady?.();
102
+ resolveReady = undefined;
103
+ });
104
+
105
+ // One non-recursive watcher per directory. Node's recursive mode on Linux tracks files
106
+ // individually and goes silent for a file once an editor replaces it with an atomic save
107
+ // (write temp file, rename over the original); directory watchers keep seeing it (#505).
108
+ const directoryWatchers = new Map<string, FSWatcher>();
109
+
110
+ const watchDirectory = (relativeDir: string) => {
111
+ if (directoryWatchers.has(relativeDir)) {
112
+ return;
113
+ }
114
+ try {
115
+ const watcher = nativeWatch(
116
+ path.resolve(directory, relativeDir),
117
+ (_eventType, fileName) => {
118
+ if (fileName != null) {
119
+ onFsEvent(path.posix.join(relativeDir, toPosixPath(fileName.toString())));
120
+ }
121
+ },
122
+ );
123
+ watcher.on("error", () => unwatchDirectory(relativeDir));
124
+ directoryWatchers.set(relativeDir, watcher);
125
+ } catch {
126
+ // The directory vanished before we could watch it; its unlink event covers it.
127
+ }
128
+ };
129
+
130
+ const unwatchDirectory = (relativeDir: string) => {
131
+ for (const [watchedDir, watcher] of directoryWatchers) {
132
+ if (watchedDir === relativeDir || watchedDir.startsWith(`${relativeDir}/`)) {
133
+ watcher.close();
134
+ directoryWatchers.delete(watchedDir);
135
+ }
136
+ }
137
+ };
138
+
139
+ /** A directory appeared: watch it, and report what was created in it before we were watching. */
140
+ const addDirectory = (relativeDir: string) => {
141
+ watchDirectory(relativeDir);
142
+ const found = new Map<string, { isDirectory: boolean; fingerprint: string }>();
143
+ try {
144
+ collectSnapshotEntries(directory, path.resolve(directory, relativeDir), found);
145
+ } catch {
146
+ return;
147
+ }
148
+ for (const [relativePath, entry] of found) {
149
+ if (snapshot.has(relativePath)) continue;
150
+ snapshot.set(relativePath, entry);
151
+ if (entry.isDirectory) watchDirectory(relativePath);
152
+ emit(entry.isDirectory ? "addDir" : "add", relativePath);
153
+ }
154
+ };
155
+
156
+ /** A directory went away: stop watching it and forget everything that was under it. */
157
+ const removeDirectoryContents = (relativeDir: string) => {
158
+ unwatchDirectory(relativeDir);
159
+ // oxlint-disable-next-line unicorn/no-useless-spread -- iterate a copy: the loop deletes from the snapshot
160
+ for (const [relativePath, entry] of [...snapshot]) {
161
+ if (relativePath.startsWith(`${relativeDir}/`)) {
162
+ snapshot.delete(relativePath);
163
+ emit(entry.isDirectory ? "unlinkDir" : "unlink", relativePath);
164
+ }
165
+ }
166
+ };
167
+
168
+ const onFsEvent = (eventPath: string) => {
169
+ const relativePath = eventPath === "." ? "" : eventPath;
170
+ if (relativePath.length === 0) {
171
+ return;
172
+ }
173
+
174
+ const fullPath = path.resolve(directory, relativePath);
175
+ const previous = snapshot.get(relativePath);
176
+
177
+ try {
178
+ const stat = statSync(fullPath);
179
+ const current = toSnapshotEntry(stat);
180
+ snapshot.set(relativePath, current);
181
+
182
+ if (previous == null) {
183
+ emit(current.isDirectory ? "addDir" : "add", relativePath);
184
+ if (current.isDirectory) addDirectory(relativePath);
185
+ return;
186
+ }
187
+
188
+ if (previous.isDirectory !== current.isDirectory) {
189
+ if (previous.isDirectory) removeDirectoryContents(relativePath);
190
+ emit(previous.isDirectory ? "unlinkDir" : "unlink", relativePath);
191
+ emit(current.isDirectory ? "addDir" : "add", relativePath);
192
+ return;
193
+ }
194
+
195
+ if (!current.isDirectory && previous.fingerprint !== current.fingerprint) {
196
+ emitChange(relativePath);
197
+ }
198
+ } catch {
199
+ if (previous == null) {
200
+ return;
201
+ }
202
+
203
+ snapshot.delete(relativePath);
204
+ const pending = pendingChangeTimers.get(relativePath);
205
+ if (pending != null) {
206
+ clearTimeout(pending);
207
+ pendingChangeTimers.delete(relativePath);
208
+ }
209
+ emit(previous.isDirectory ? "unlinkDir" : "unlink", relativePath);
210
+ }
211
+ };
212
+
213
+ watchDirectory("");
214
+ for (const [relativePath, entry] of snapshot) {
215
+ if (entry.isDirectory) watchDirectory(relativePath);
216
+ }
217
+
218
+ const watchApi: Watcher = {
219
+ ready,
220
+ on(event: "ready" | WatchEventName, handler: ((file: File) => unknown) | (() => unknown)) {
221
+ if (event === "ready") {
222
+ handlers.ready.add(handler as () => unknown);
223
+ return watchApi;
224
+ }
225
+
226
+ handlers[event].add(handler as (file: File) => unknown);
227
+ return watchApi;
228
+ },
229
+ close: () => {
230
+ for (const pending of pendingChangeTimers.values()) {
231
+ clearTimeout(pending);
232
+ }
233
+ pendingChangeTimers.clear();
234
+ for (const watcher of directoryWatchers.values()) {
235
+ watcher.close();
236
+ }
237
+ directoryWatchers.clear();
238
+ },
239
+ };
240
+
241
+ return watchApi;
242
+ }
243
+
244
+ function buildSnapshot(
245
+ directoryPath: string,
246
+ ): Map<string, { isDirectory: boolean; fingerprint: string }> {
247
+ const snapshot = new Map<string, { isDirectory: boolean; fingerprint: string }>();
248
+ collectSnapshotEntries(directoryPath, directoryPath, snapshot);
249
+ return snapshot;
250
+ }
251
+
252
+ /** Never source, and huge: watching every folder inside them could exhaust OS watch limits. */
253
+ const UNWATCHED_DIRECTORIES = new Set(["node_modules", ".git"]);
254
+
255
+ function collectSnapshotEntries(
256
+ rootDirectoryPath: string,
257
+ currentDirectoryPath: string,
258
+ snapshot: Map<string, { isDirectory: boolean; fingerprint: string }>,
259
+ ) {
260
+ for (const entry of readdirSync(currentDirectoryPath, { encoding: "utf8" })) {
261
+ const name = typeof entry === "string" ? entry : String(entry);
262
+ const fullPath = path.join(currentDirectoryPath, name);
263
+ if (UNWATCHED_DIRECTORIES.has(name)) {
264
+ continue;
265
+ }
266
+ const relativePath = toPosixPath(path.relative(rootDirectoryPath, fullPath));
267
+ const stat = statSync(fullPath);
268
+ const snapshotEntry = toSnapshotEntry(stat);
269
+
270
+ snapshot.set(relativePath, snapshotEntry);
271
+
272
+ if (!snapshotEntry.isDirectory) {
273
+ continue;
274
+ }
275
+
276
+ collectSnapshotEntries(rootDirectoryPath, fullPath, snapshot);
277
+ }
278
+ }
279
+
280
+ function toSnapshotEntry(stat: { isDirectory(): boolean; mtimeMs: number; size: number }) {
281
+ const isDirectory = stat.isDirectory();
282
+ return {
283
+ isDirectory,
284
+ fingerprint: isDirectory ? `dir:${stat.mtimeMs}` : `file:${stat.mtimeMs}:${stat.size}`,
285
+ };
286
+ }
287
+
288
+ function toPosixPath(filePath: string) {
289
+ return filePath.split(path.sep).join(path.posix.sep);
290
+ }
@@ -0,0 +1,21 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ export type WriteFileOptions = {
5
+ mkdir?: boolean;
6
+ encoding?: BufferEncoding;
7
+ };
8
+
9
+ export async function writeFile(
10
+ filePath: string,
11
+ content: string,
12
+ options: WriteFileOptions = { encoding: "utf8", mkdir: false },
13
+ ): Promise<void> {
14
+ const { encoding, mkdir } = options;
15
+ if (mkdir) {
16
+ const directory = path.dirname(filePath);
17
+ await fs.mkdir(directory, { recursive: true });
18
+ }
19
+
20
+ await fs.writeFile(filePath, content, encoding);
21
+ }
@@ -0,0 +1,131 @@
1
+ // Source https://github.com/voodoocreation/ts-deepmerge/blob/master/package.json
2
+
3
+ type TAllKeys<T> = T extends unknown ? keyof T : never;
4
+
5
+ type TIndexValue<T, K extends PropertyKey, D = never> = T extends unknown
6
+ ? K extends keyof T
7
+ ? T[K]
8
+ : D
9
+ : never;
10
+
11
+ type TPartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>> extends infer O
12
+ ? { [P in keyof O]: O[P] }
13
+ : never;
14
+
15
+ type TFunction = (...a: unknown[]) => unknown;
16
+
17
+ type TPrimitives = string | number | boolean | bigint | symbol | Date | TFunction;
18
+
19
+ type TMerged<T> = [T] extends [Array<unknown>]
20
+ ? { [K in keyof T]: TMerged<T[K]> }
21
+ : [T] extends [TPrimitives]
22
+ ? T
23
+ : [T] extends [object]
24
+ ? TPartialKeys<{ [K in TAllKeys<T>]: TMerged<TIndexValue<T, K>> }, never>
25
+ : T;
26
+
27
+ // istanbul ignore next
28
+ const isObject = (obj: unknown) => {
29
+ if (typeof obj === "object" && obj !== null) {
30
+ if (typeof Object.getPrototypeOf === "function") {
31
+ const prototype = Object.getPrototypeOf(obj);
32
+ return prototype === Object.prototype || prototype === null;
33
+ }
34
+
35
+ return Object.prototype.toString.call(obj) === "[object Object]";
36
+ }
37
+
38
+ return false;
39
+ };
40
+
41
+ interface IObject {
42
+ [key: string]: unknown;
43
+ }
44
+
45
+ export const merge = <T extends object[]>(...objects: T): TMerged<T[number]> =>
46
+ objects.reduce((result, current) => {
47
+ if (current === undefined) {
48
+ return result;
49
+ }
50
+
51
+ if (Array.isArray(current)) {
52
+ throw new TypeError("Arguments provided to ts-deepmerge must be objects, not arrays.");
53
+ }
54
+
55
+ Object.keys(current).forEach((key) => {
56
+ if (["__proto__", "constructor", "prototype"].includes(key)) {
57
+ return;
58
+ }
59
+
60
+ if (Array.isArray(result[key]) && Array.isArray(current[key])) {
61
+ result[key] = merge.options.mergeArrays
62
+ ? merge.options.uniqueArrayItems
63
+ ? Array.from(new Set((result[key] as unknown[]).concat(current[key])))
64
+ : [...result[key], ...current[key]]
65
+ : current[key];
66
+ } else if (isObject(result[key]) && isObject(current[key])) {
67
+ result[key] = merge(result[key] as IObject, current[key] as IObject);
68
+ } else if (!isObject(result[key]) && isObject(current[key])) {
69
+ result[key] = merge(current[key], undefined);
70
+ } else {
71
+ result[key] =
72
+ current[key] === undefined
73
+ ? merge.options.allowUndefinedOverrides
74
+ ? current[key]
75
+ : result[key]
76
+ : current[key];
77
+ }
78
+ });
79
+
80
+ return result;
81
+ }, {} as IObject) as TMerged<T[number]>;
82
+
83
+ interface IOptions {
84
+ /**
85
+ * When `true`, values explicitly provided as `undefined` will override existing values, though properties that are simply omitted won't affect anything.
86
+ * When `false`, values explicitly provided as `undefined` won't override existing values.
87
+ *
88
+ * Default: `true`
89
+ */
90
+ allowUndefinedOverrides: boolean;
91
+
92
+ /**
93
+ * When `true` it will merge array properties.
94
+ * When `false` it will replace array properties with the last instance entirely instead of merging their contents.
95
+ *
96
+ * Default: `true`
97
+ */
98
+ mergeArrays: boolean;
99
+
100
+ /**
101
+ * When `true` it will ensure there are no duplicate array items.
102
+ * When `false` it will allow duplicates when merging arrays.
103
+ *
104
+ * Default: `true`
105
+ */
106
+ uniqueArrayItems: boolean;
107
+ }
108
+
109
+ const defaultOptions: IOptions = {
110
+ allowUndefinedOverrides: true,
111
+ mergeArrays: true,
112
+ uniqueArrayItems: true,
113
+ };
114
+
115
+ merge.options = defaultOptions;
116
+
117
+ merge.withOptions = <R = unknown, T extends object[] = object[]>(
118
+ options: Partial<IOptions>,
119
+ ...objects: T
120
+ ): R => {
121
+ merge.options = {
122
+ ...defaultOptions,
123
+ ...options,
124
+ };
125
+
126
+ const result = merge(...objects);
127
+
128
+ merge.options = defaultOptions;
129
+
130
+ return result as unknown as R;
131
+ };
@@ -1,3 +1,4 @@
1
1
  import "reflect-metadata";
2
+
2
3
  export * from "./types/index.js";
3
4
  export * from "./utilities.js";