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

pmcelhaney / counterfact / 5942341158

22 Aug 2023 05:55PM UTC coverage: 87.693% (+0.4%) from 87.318%
5942341158

Pull #377

github

pmcelhaney
don't add a module right back after deleting it
Pull Request #377: run CI on Windows as well as Linux

431 of 458 branches covered (94.1%)

Branch coverage included in aggregate %.

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

2013 of 2329 relevant lines covered (86.43%)

21.13 hits per line

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

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

1✔
6
import chokidar from "chokidar";
1✔
7

1✔
8
import { ContextRegistry } from "./context-registry.js";
1✔
9
import { CHOKIDAR_OPTIONS } from "./constants.js";
1✔
10

1✔
11
function log(...strings) {
76✔
12
  process.stdout.write(`[module-loader] ${strings.join("\t")}\n`);
76✔
13
}
76✔
14

1✔
15
export class ModuleLoader extends EventTarget {
1✔
16
  basePath;
1✔
17

8✔
18
  registry;
8✔
19

8✔
20
  watcher;
8✔
21

8✔
22
  contextRegistry;
1✔
23

1✔
24
  constructor(basePath, registry, contextRegistry = new ContextRegistry()) {
1✔
25
    super();
8✔
26
    this.basePath = basePath.replace("\\", "/", "all");
8✔
27
    this.registry = registry;
8✔
28
    this.contextRegistry = contextRegistry;
8✔
29
  }
8✔
30

1✔
31
  async watch() {
1✔
32
    log("watching", this.basePath);
6✔
33
    this.watcher = chokidar
6✔
34
      .watch(`${this.basePath}/**/*.{js,mjs,ts,mts}`, CHOKIDAR_OPTIONS)
6✔
35
      // eslint-disable-next-line max-statements
6✔
36
      .on("all", (eventName, pathNameOriginal) => {
6✔
37
        const pathName = pathNameOriginal.replaceAll("\\", "/");
8✔
38

8✔
39
        log("chokidar", eventName, pathName);
8✔
40

8✔
41
        if (!["add", "change", "unlink"].includes(eventName)) {
8!
42
          return;
8✔
43
        }
8✔
44

8✔
45
        const parts = nodePath.parse(pathName.replace(this.basePath, ""));
8✔
46
        const url = nodePath
8✔
47
          .normalize(`/${nodePath.join(parts.dir, parts.name)}`)
8✔
48
          .replaceAll("\\", "/");
8✔
49

8✔
50
        if (eventName === "unlink") {
8✔
51
          log("removing a module from registry at runtime", url);
8✔
52
          this.registry.remove(url);
8✔
53
          log("did remove a module from registry", url);
8✔
54
          this.dispatchEvent(new Event("remove"), pathName);
8✔
55

8✔
56
          return;
8✔
57
        }
8✔
58

8✔
59
        // eslint-disable-next-line no-magic-numbers
8✔
60
        const id = Math.random().toString(36).slice(2);
8✔
61

8✔
62
        log("loading module at runtime", url, id, eventName);
8✔
63

8✔
64
        // eslint-disable-next-line  import/no-dynamic-require, no-unsanitized/method
8✔
65
        import(`${pathName}?cacheBust=${Date.now()}`)
8✔
66
          // eslint-disable-next-line promise/prefer-await-to-then
8✔
67
          .then((endpoint) => {
8✔
68
            this.dispatchEvent(new Event(eventName), pathName);
8✔
69

8✔
70
            if (pathName.includes("$.context")) {
8!
71
              this.contextRegistry.update(parts.dir, endpoint.default);
×
72

×
73
              return "context";
×
74
            }
×
75

8✔
76
            log("adding module to registry at runtime", url, id);
8✔
77
            this.registry.add(url, endpoint);
8✔
78
            log("did add a module to the registry", url, id);
8✔
79

8✔
80
            return "path";
8✔
81
          })
8✔
82
          // eslint-disable-next-line promise/prefer-await-to-then
8✔
83
          .catch((error) => {
8✔
84
            process.stdout.write(`\nError loading ${pathName}:\n${error}\n`);
×
85
          });
×
86
      });
8✔
87

6✔
88
    log("waiting for ready event", this.basePath);
6✔
89
    await once(this.watcher, "ready");
6✔
90
    log("received ready event", this.basePath);
6✔
91
  }
6✔
92

1✔
93
  async stopWatching() {
1✔
94
    log("stopping the watcher...", this.basePath);
6✔
95
    await this.watcher?.close();
6✔
96
    log("stopped the watcher", this.basePath);
6✔
97
  }
6✔
98

1✔
99
  async load(directory = "") {
1✔
100
    if (
12✔
101
      !existsSync(nodePath.join(this.basePath, directory).replaceAll("\\", "/"))
12✔
102
    ) {
12!
103
      log("Directory does not exist", this.basePath, directory);
×
104

×
105
      throw new Error(`Directory does not exist ${this.basePath}`);
×
106
    }
×
107

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

12✔
115
    // eslint-disable-next-line max-statements
12✔
116
    const imports = files.flatMap(async (file) => {
12✔
117
      const extension = file.name.split(".").at(-1);
16✔
118

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

4✔
124
        return;
4✔
125
      }
4✔
126

12✔
127
      if (!["js", "mjs", "ts", "mts"].includes(extension)) {
16✔
128
        return;
1✔
129
      }
1✔
130

11✔
131
      const fullPath = nodePath
11✔
132
        .join(this.basePath, directory, file.name)
11✔
133
        .replaceAll("\\", "/");
11✔
134

11✔
135
      try {
11✔
136
        // eslint-disable-next-line  import/no-dynamic-require, no-unsanitized/method
11✔
137
        const endpoint = await import(fullPath);
11✔
138

11✔
139
        if (file.name.includes("$.context")) {
16✔
140
          log("adding context to registry", directory, endpoint.default);
9✔
141
          this.contextRegistry.add(`/${directory}`, endpoint.default);
9✔
142
        } else {
9✔
143
          log(
9✔
144
            "adding module to registry",
9✔
145
            directory,
9✔
146
            file.name,
9✔
147
            endpoint.default
9✔
148
          );
9✔
149
          this.registry.add(
9✔
150
            `/${nodePath
9✔
151
              .join(directory, nodePath.parse(file.name).name)
9✔
152
              .replaceAll("\\", "/")}`,
9✔
153
            endpoint
9✔
154
          );
9✔
155
        }
9✔
156
      } catch (error) {
16!
157
        log("Error loading", fullPath, error);
×
158
      }
×
159
    });
16✔
160

12✔
161
    await Promise.all(imports);
12✔
162
  }
12✔
163
}
1✔
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