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

pmcelhaney / counterfact / 6078482043

04 Sep 2023 11:49PM UTC coverage: 85.267% (+0.1%) from 85.172%
6078482043

Pull #479

github

pmcelhaney
don't have TypeScript check code in node_modules
Pull Request #479: Convert server to TypeScript

704 of 784 branches covered (0.0%)

Branch coverage included in aggregate %.

1213 of 1213 new or added lines in 13 files covered. (100.0%)

2265 of 2698 relevant lines covered (83.95%)

20.86 hits per line

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

87.5
/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
import { pathToFileURL } from "node:url";
2✔
6

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

2✔
10
import { type Context, ContextRegistry } from "./context-registry.js";
2✔
11
import type { Module, Registry } from "./registry.js";
2✔
12

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

2✔
15
interface ContextModule {
2✔
16
  default?: Context;
2✔
17
}
2✔
18

2✔
19
export class ModuleLoader extends EventTarget {
2✔
20
  private readonly basePath: string;
16✔
21

16✔
22
  public readonly registry: Registry;
16✔
23

16✔
24
  private watcher: FSWatcher | undefined;
16✔
25

16✔
26
  private readonly contextRegistry: ContextRegistry;
16✔
27

16✔
28
  public constructor(
16✔
29
    basePath: string,
16✔
30
    registry: Registry,
16✔
31
    contextRegistry = new ContextRegistry(),
16✔
32
  ) {
16✔
33
    super();
16✔
34
    this.basePath = basePath.replaceAll("\\", "/");
16✔
35
    this.registry = registry;
16✔
36
    this.contextRegistry = contextRegistry;
16✔
37
  }
16✔
38

16✔
39
  public async watch(): Promise<void> {
16✔
40
    this.watcher = watch(`${this.basePath}/**/*.{js,mjs,ts,mts}`).on(
12✔
41
      "all",
12✔
42
      // eslint-disable-next-line max-statements
12✔
43
      (eventName: string, pathNameOriginal: string) => {
12✔
44
        const pathName = pathNameOriginal.replaceAll("\\", "/");
18✔
45

18✔
46
        if (!["add", "change", "unlink"].includes(eventName)) {
18!
47
          return;
×
48
        }
×
49

18✔
50
        const parts = nodePath.parse(pathName.replace(this.basePath, ""));
18✔
51
        const url = `/${parts.dir}/${parts.name}`
18✔
52
          .replaceAll("\\", "/")
18✔
53
          .replaceAll(/\/+/gu, "/");
18✔
54

18✔
55
        if (eventName === "unlink") {
18✔
56
          this.registry.remove(url);
2✔
57
          this.dispatchEvent(new Event("remove"));
2✔
58
        }
2✔
59

18✔
60
        const fileUrl = `${pathToFileURL(
18✔
61
          pathName,
18✔
62
        ).toString()}?cacheBust=${Date.now()}`;
18✔
63

18✔
64
        debug("importing module: %s", fileUrl);
18✔
65

18✔
66
        // eslint-disable-next-line import/no-dynamic-require, no-unsanitized/method
18✔
67
        import(fileUrl)
18✔
68
          // eslint-disable-next-line promise/prefer-await-to-then
18✔
69
          .then((endpoint: ContextModule | Module) => {
18✔
70
            this.dispatchEvent(new Event(eventName));
18✔
71

18✔
72
            if (pathName.includes("$.context")) {
18!
73
              this.contextRegistry.update(
×
74
                parts.dir,
×
75
                // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
×
76
                (endpoint as ContextModule).default,
×
77
              );
×
78

×
79
              return "context";
×
80
            }
×
81

18✔
82
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
18✔
83
            this.registry.add(url, endpoint as Module);
18✔
84

18✔
85
            return "path";
18✔
86
          })
18✔
87
          // eslint-disable-next-line promise/prefer-await-to-then
18✔
88
          .catch((error: unknown) => {
18✔
89
            process.stdout.write(
×
90
              `\nError loading ${pathName}:\n${String(error)}\n`,
×
91
            );
×
92
          });
×
93
      },
18✔
94
    );
12✔
95
    await once(this.watcher, "ready");
12✔
96
  }
12✔
97

16✔
98
  public async stopWatching(): Promise<void> {
16✔
99
    await this.watcher?.close();
12✔
100
  }
12✔
101

16✔
102
  public async load(directory = ""): Promise<void> {
16✔
103
    if (
24✔
104
      !existsSync(nodePath.join(this.basePath, directory).replaceAll("\\", "/"))
24✔
105
    ) {
24!
106
      throw new Error(`Directory does not exist ${this.basePath}`);
×
107
    }
×
108

24✔
109
    const files = await fs.readdir(
24✔
110
      nodePath.join(this.basePath, directory).replaceAll("\\", "/"),
24✔
111
      {
24✔
112
        withFileTypes: true,
24✔
113
      },
24✔
114
    );
24✔
115

24✔
116
    const imports = files.flatMap(async (file): Promise<void> => {
24✔
117
      const extension = file.name.split(".").at(-1);
32✔
118

32✔
119
      if (file.isDirectory()) {
32✔
120
        await this.load(
8✔
121
          nodePath.join(directory, file.name).replaceAll("\\", "/"),
8✔
122
        );
8✔
123

8✔
124
        return;
8✔
125
      }
8✔
126

24✔
127
      if (!["js", "mjs", "mts", "ts"].includes(extension ?? "")) {
32!
128
        return;
2✔
129
      }
2✔
130

22✔
131
      const fullPath = nodePath
22✔
132
        .join(this.basePath, directory, file.name)
22✔
133
        .replaceAll("\\", "/");
22✔
134
      await this.loadEndpoint(fullPath, directory, file);
22✔
135
    });
22✔
136

24✔
137
    await Promise.all(imports);
24✔
138
  }
24✔
139

16✔
140
  private async loadEndpoint(
16✔
141
    fullPath: string,
22✔
142
    directory: string,
22✔
143
    file: Dirent,
22✔
144
  ) {
22✔
145
    try {
22✔
146
      // eslint-disable-next-line import/no-dynamic-require, no-unsanitized/method, @typescript-eslint/consistent-type-assertions
22✔
147
      const endpoint: ContextModule | Module = (await import(fullPath)) as
22✔
148
        | ContextModule
22✔
149
        | Module;
22✔
150

22✔
151
      if (file.name.includes("$.context")) {
22✔
152
        this.contextRegistry.add(
4✔
153
          `/${directory.replaceAll("\\", "/")}`,
4✔
154

4✔
155
          // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
4✔
156
          (endpoint as ContextModule).default,
4✔
157
        );
4✔
158
      } else {
22✔
159
        const url = `/${nodePath.join(
18✔
160
          directory,
18✔
161
          nodePath.parse(file.name).name,
18✔
162
        )}`
18✔
163
          .replaceAll("\\", "/")
18✔
164
          .replaceAll(/\/+/gu, "/");
18✔
165

18✔
166
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
18✔
167
        this.registry.add(url, endpoint as Module);
18✔
168
      }
18✔
169
    } catch (error: unknown) {
22!
170
      process.stdout.write(`\nError loading ${fullPath}:\n${String(error)}\n`);
×
171
    }
×
172
  }
22✔
173
}
16✔
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

© 2026 Coveralls, Inc