• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmcelhaney / counterfact / 8836532298

25 Apr 2024 05:01PM UTC coverage: 87.36% (-0.01%) from 87.374%
8836532298

push

github

pmcelhaney
refactor and improve debug logs

896 of 989 branches covered (90.6%)

Branch coverage included in aggregate %.

8 of 8 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

2919 of 3378 relevant lines covered (86.41%)

45.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.93
/src/server/module-loader.ts
1
import { once } from "node:events";
2✔
2
import { type Dirent, existsSync } from "node:fs";
2✔
3
import fs from "node:fs/promises";
2✔
4
import nodePath from "node:path";
2✔
5

2✔
6
import { type FSWatcher, watch } from "chokidar";
2✔
7
import createDebug from "debug";
2✔
8

2✔
9
import { type Context, ContextRegistry } from "./context-registry.js";
2✔
10
import { determineModuleKind } from "./determine-module-kind.js";
2✔
11
import type { Module, Registry } from "./registry.js";
2✔
12
import { uncachedImport } from "./uncached-import.js";
2✔
13

2✔
14
const { uncachedRequire } = await import("./uncached-require.cjs");
2✔
15

2✔
16
const debug = createDebug("counterfact:typescript-generator:module-loader");
2✔
17

2✔
18
interface ContextModule {
2✔
19
  Context: Context;
2✔
20
}
2✔
21

2✔
22
function isContextModule(
14✔
23
  module: ContextModule | Module,
14✔
24
): module is ContextModule {
14✔
25
  return "Context" in module && typeof module.Context === "function";
14✔
26
}
14✔
27

2✔
28
export class ModuleLoader extends EventTarget {
2✔
29
  private readonly basePath: string;
14✔
30

14✔
31
  public readonly registry: Registry;
14✔
32

14✔
33
  private watcher: FSWatcher | undefined;
14✔
34

14✔
35
  private readonly contextRegistry: ContextRegistry;
14✔
36

14✔
37
  private uncachedImport: (moduleName: string) => Promise<unknown> =
14✔
38
    // eslint-disable-next-line @typescript-eslint/require-await
14✔
39
    async function (moduleName: string) {
14✔
40
      throw new Error(`uncachedImport not set up; importing ${moduleName}`);
×
41
    };
×
42

14✔
43
  public constructor(
14✔
44
    basePath: string,
14✔
45
    registry: Registry,
14✔
46
    contextRegistry = new ContextRegistry(),
14✔
47
  ) {
14✔
48
    super();
14✔
49
    this.basePath = basePath.replaceAll("\\", "/");
14✔
50
    this.registry = registry;
14✔
51
    this.contextRegistry = contextRegistry;
14✔
52
  }
14✔
53

14✔
54
  public async watch(): Promise<void> {
14✔
55
    debug("watching: %s", this.basePath);
6✔
56

6✔
57
    this.watcher = watch(`${this.basePath}/**/*.{js,mjs,ts,mts}`).on(
6✔
58
      "all",
6✔
59
      // eslint-disable-next-line @typescript-eslint/no-misused-promises
6✔
60
      async (eventName: string) => {
6✔
61
        if (!["add", "change", "unlink"].includes(eventName)) {
6!
62
          return;
×
63
        }
×
64

6✔
65
        await this.load();
6✔
66
      },
6✔
67
    );
6✔
68
    await once(this.watcher, "ready");
6✔
69
  }
6✔
70

14✔
71
  public async stopWatching(): Promise<void> {
14✔
72
    await this.watcher?.close();
6✔
73
  }
6✔
74

14✔
75
  // eslint-disable-next-line max-statements
14✔
76
  public async load(directory = "", isRoot = true): Promise<void> {
14✔
77
    const path = nodePath.join(this.basePath, directory).replaceAll("\\", "/");
36✔
78

36✔
79
    debug("loading: %s", this.basePath);
36✔
80
    const moduleKind = await determineModuleKind(this.basePath);
36✔
81

36✔
82
    if (isRoot) {
36✔
83
      this.registry.clear();
22✔
84
    }
22✔
85

36✔
86
    this.uncachedImport =
36✔
87
      moduleKind === "module" ? uncachedImport : uncachedRequire;
36!
88

36✔
89
    if (!existsSync(path)) {
36!
UNCOV
90
      throw new Error(`Directory does not exist ${this.basePath}`);
×
91
    }
×
92

36✔
93
    const files = await fs.readdir(path, {
36✔
94
      withFileTypes: true,
36✔
95
    });
36✔
96

36✔
97
    const imports = files.flatMap(async (file): Promise<void> => {
36✔
98
      const extension = file.name.split(".").at(-1);
70✔
99

70✔
100
      if (file.isDirectory()) {
70✔
101
        await this.load(
14✔
102
          nodePath.join(directory, file.name).replaceAll("\\", "/"),
14✔
103
          false,
14✔
104
        );
14✔
105

14✔
106
        return;
14✔
107
      }
14✔
108

56✔
109
      if (!["js", "mjs", "mts", "ts"].includes(extension ?? "")) {
70!
110
        return;
28✔
111
      }
28✔
112

28✔
113
      const fullPath = nodePath.join(path, file.name).replaceAll("\\", "/");
28✔
114
      await this.loadEndpoint(fullPath, directory, file);
28✔
115
    });
28✔
116

36✔
117
    await Promise.all(imports);
36✔
118

36✔
119
    this.dispatchEvent(new Event("load"));
36✔
120
    debug("finished loading: %s", path);
36✔
121
  }
36✔
122

14✔
123
  private async loadEndpoint(
14✔
124
    fullPath: string,
28✔
125
    directory: string,
28✔
126
    file: Dirent,
28✔
127
  ) {
28✔
128
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
28✔
129
    const endpoint: ContextModule | Module = (await this.uncachedImport(
28✔
130
      fullPath,
28✔
131
    )) as ContextModule | Module;
28✔
132

28✔
133
    if (file.name.includes("_.context")) {
28✔
134
      if (isContextModule(endpoint)) {
14✔
135
        this.contextRegistry.update(
14✔
136
          `/${directory.replaceAll("\\", "/")}`,
14✔
137

14✔
138
          // @ts-expect-error TS says Context has no constructable signatures but that's not true?
14✔
139
          // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
14✔
140
          new endpoint.Context(),
14✔
141
        );
14✔
142
      }
14✔
143
    } else {
14✔
144
      const url = `/${nodePath.join(directory, nodePath.parse(file.name).name)}`
14✔
145
        .replaceAll("\\", "/")
14✔
146
        .replaceAll(/\/+/gu, "/");
14✔
147

14✔
148
      // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
14✔
149
      this.registry.add(url, endpoint as Module);
14✔
150
    }
14✔
151
  }
28✔
152
}
14✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc