@eventra_dev/eventra-cli 2.1.0 → 2.3.0
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 +49 -11
- package/dist/analysis/resolver/callResolver.js +16 -2
- package/dist/analysis/scanner/scanner.js +1 -0
- package/dist/analysis/scanner/wrapperChains.js +115 -0
- package/dist/analysis/sdk/eventraSdk.js +6 -4
- package/dist/analysis/symbols/wrapperRegistry.js +14 -5
- package/dist/cli/check.js +25 -9
- package/dist/cli/init.js +87 -23
- package/dist/cli/projectDependencies.js +8 -3
- package/dist/cli/send.js +31 -2
- package/dist/cli/sync.js +13 -10
- package/dist/cli/watch.js +163 -62
- package/dist/compiler/compilerContext.js +246 -6
- package/dist/compiler/documentRegistry.js +15 -1
- package/dist/compiler/importGraph.js +8 -0
- package/dist/compiler/workspacePackages.js +58 -0
- package/dist/config/config.js +61 -3
- package/dist/core/EventraEngine.js +143 -16
- package/dist/core/catalogMeta.js +91 -0
- package/dist/core/fileLimits.js +12 -0
- package/dist/core/projectScanner.js +14 -24
- package/dist/core/scanFilters.js +146 -0
- package/dist/core/scanResults.js +21 -7
- package/dist/core/skipped.js +77 -0
- package/dist/index.js +49 -38
- package/dist/plugin/builtins/configuredSinks.js +84 -17
- package/dist/plugin/builtins/consoleDynamicEventReporter.js +21 -2
- package/dist/plugin/loadPlugins.js +20 -2
- package/dist/plugin/registry.js +83 -1
- package/dist/respawn.js +89 -0
- package/package.json +3 -2
|
@@ -3,13 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.memberGlob = exports.MAX_WORKSPACE_PACKAGES = void 0;
|
|
7
|
+
exports.stringList = stringList;
|
|
8
|
+
exports.pnpmWorkspacePatterns = pnpmWorkspacePatterns;
|
|
9
|
+
exports.staysInsideProject = staysInsideProject;
|
|
6
10
|
exports.collectDependencyNames = collectDependencyNames;
|
|
7
11
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
8
12
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
13
|
const path_1 = __importDefault(require("path"));
|
|
10
14
|
const DEPENDENCY_FIELDS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
11
15
|
/** Upper bound on workspace members read, so a huge monorepo cannot make `init` crawl. */
|
|
12
|
-
|
|
16
|
+
exports.MAX_WORKSPACE_PACKAGES = 300;
|
|
13
17
|
/**
|
|
14
18
|
* Parsed package.json, or null when it is missing or not JSON. Whatever else the file holds (an
|
|
15
19
|
* array, a number) is returned as is: every reader below only looks up named properties on it, which
|
|
@@ -86,10 +90,11 @@ async function workspacePatterns(rootDir, rootPackage) {
|
|
|
86
90
|
* `!apps/skipped/package.json`, which fast-glob treats as an exclusion of exactly those files.
|
|
87
91
|
*/
|
|
88
92
|
const memberGlob = (pattern) => `${pattern}/package.json`;
|
|
93
|
+
exports.memberGlob = memberGlob;
|
|
89
94
|
/** Member package.json paths for the patterns; a pattern fast-glob rejects (a NUL byte, say) yields none. */
|
|
90
95
|
async function findMembers(rootDir, patterns) {
|
|
91
96
|
try {
|
|
92
|
-
return await (0, fast_glob_1.default)(patterns.map(memberGlob), { cwd: rootDir, ignore: ["**/node_modules/**"] });
|
|
97
|
+
return await (0, fast_glob_1.default)(patterns.map(exports.memberGlob), { cwd: rootDir, ignore: ["**/node_modules/**"] });
|
|
93
98
|
}
|
|
94
99
|
catch {
|
|
95
100
|
// Stryker disable next-line ArrayDeclaration: a stray path that is not a package.json is read as "no package.json"
|
|
@@ -108,7 +113,7 @@ async function collectDependencyNames(rootDir) {
|
|
|
108
113
|
for (const name of dependencyNames(rootPackage))
|
|
109
114
|
names.add(name);
|
|
110
115
|
const patterns = await workspacePatterns(rootDir, rootPackage);
|
|
111
|
-
const members = (await findMembers(rootDir, patterns)).slice(0, MAX_WORKSPACE_PACKAGES);
|
|
116
|
+
const members = (await findMembers(rootDir, patterns)).slice(0, exports.MAX_WORKSPACE_PACKAGES);
|
|
112
117
|
for (const member of members) {
|
|
113
118
|
const memberPackage = await readPackage(path_1.default.join(rootDir, member));
|
|
114
119
|
if (memberPackage !== null)
|
package/dist/cli/send.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.send = send;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
9
|
const config_1 = require("../config/config");
|
|
10
|
+
const catalogMeta_1 = require("../core/catalogMeta");
|
|
10
11
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
11
12
|
const CLI_VERSION = package_json_1.default.version;
|
|
12
13
|
// Always trusted, no local approval needed — matches `eventra init`'s prompt default.
|
|
@@ -64,6 +65,10 @@ async function attemptDelivery(endpoint, apiKey, payload) {
|
|
|
64
65
|
clearTimeout(timeout);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
68
|
+
// the API answers a rejected unknown property with {"error":{"code":"VALIDATION_ERROR",...}}
|
|
69
|
+
function isValidationError(body) {
|
|
70
|
+
return typeof body === "string" && body.includes("VALIDATION_ERROR");
|
|
71
|
+
}
|
|
67
72
|
async function send(opts = {}) {
|
|
68
73
|
const config = await (0, config_1.loadConfig)();
|
|
69
74
|
if (!config) {
|
|
@@ -134,24 +139,48 @@ async function send(opts = {}) {
|
|
|
134
139
|
}
|
|
135
140
|
console.log("");
|
|
136
141
|
console.log(chalk_1.default.blue(`Sending ${config.events.length} events...`));
|
|
137
|
-
|
|
142
|
+
// Tells the server whether this list is a full snapshot of the code (so names
|
|
143
|
+
// missing from it can be marked as removed) and how many event names could
|
|
144
|
+
// not be resolved. Older servers ignore the extra fields.
|
|
145
|
+
const meta = await (0, catalogMeta_1.collectCatalogMeta)(config);
|
|
146
|
+
if (!meta.complete) {
|
|
147
|
+
console.log(chalk_1.default.gray("Catalog snapshot marked incomplete (files were skipped, the scan failed, or eventra.json is out of date: run 'eventra sync')"));
|
|
148
|
+
}
|
|
149
|
+
const legacyBody = {
|
|
138
150
|
events: config.events,
|
|
139
151
|
cli: {
|
|
140
152
|
name: "@eventra_dev/eventra-cli",
|
|
141
153
|
version: CLI_VERSION,
|
|
142
154
|
runtime: "node",
|
|
143
155
|
},
|
|
156
|
+
};
|
|
157
|
+
const payload = JSON.stringify({
|
|
158
|
+
...legacyBody,
|
|
159
|
+
complete: meta.complete,
|
|
160
|
+
cliVersion: CLI_VERSION,
|
|
161
|
+
...(meta.dynamicCount !== undefined ? { dynamicCount: meta.dynamicCount } : {}),
|
|
162
|
+
...(meta.branch ? { branch: meta.branch } : {}),
|
|
144
163
|
});
|
|
164
|
+
// A server that predates the catalog fields rejects unknown properties with a
|
|
165
|
+
// generic validation 400; the request is then repeated once without them.
|
|
166
|
+
const legacyPayload = JSON.stringify(legacyBody);
|
|
167
|
+
let body = payload;
|
|
145
168
|
// attempt delivery with retry on 429/5xx/network errors
|
|
146
169
|
let data;
|
|
147
170
|
let lastFailure = null;
|
|
148
171
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
149
172
|
try {
|
|
150
|
-
const outcome = await attemptDelivery(endpoint, apiKey,
|
|
173
|
+
const outcome = await attemptDelivery(endpoint, apiKey, body);
|
|
151
174
|
if (outcome.ok) {
|
|
152
175
|
data = outcome.data;
|
|
153
176
|
break;
|
|
154
177
|
}
|
|
178
|
+
if (outcome.status === 400 && body !== legacyPayload && isValidationError(outcome.body)) {
|
|
179
|
+
body = legacyPayload;
|
|
180
|
+
console.log(chalk_1.default.gray("Server does not know the catalog fields yet, sending the plain event list"));
|
|
181
|
+
attempt--;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
155
184
|
if (!outcome.retryable) {
|
|
156
185
|
// permanent failure — surface immediately
|
|
157
186
|
console.log(chalk_1.default.red(`Request failed (${outcome.status})`));
|
package/dist/cli/sync.js
CHANGED
|
@@ -8,17 +8,20 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const config_1 = require("../config/config");
|
|
9
9
|
const projectScanner_1 = require("../core/projectScanner");
|
|
10
10
|
const scanResults_1 = require("../core/scanResults");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const skipped_1 = require("../core/skipped");
|
|
12
|
+
async function sync({ strict = false } = {}) {
|
|
13
|
+
const config = await (0, config_1.requireConfig)();
|
|
14
|
+
if (!config)
|
|
15
15
|
return;
|
|
16
|
-
}
|
|
17
16
|
console.log(chalk_1.default.blue("Scanning..."));
|
|
18
|
-
const { engine, plugins } = await (0, projectScanner_1.scanProject)(config);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
console.log(chalk_1.default.green(`Found ${events
|
|
17
|
+
const { engine, plugins, skipped, ignored } = await (0, projectScanner_1.scanProject)(config);
|
|
18
|
+
// report what was saved: with skipped files that is the union of the known and
|
|
19
|
+
// the found events, not just what this scan found
|
|
20
|
+
const saved = await (0, scanResults_1.persistScanResults)(config, engine, { preserveKnown: skipped.length > 0 });
|
|
21
|
+
console.log(chalk_1.default.green(`Found ${(0, skipped_1.plural)(saved.events?.length ?? 0, "event")}, ${(0, skipped_1.plural)(saved.functionWrappers?.length ?? 0, "function wrapper")}`));
|
|
23
22
|
await plugins.runDynamicEventReporters(engine.getAllDynamicOccurrences());
|
|
23
|
+
(0, skipped_1.reportSkipped)(skipped);
|
|
24
|
+
(0, skipped_1.reportIgnored)(ignored);
|
|
25
|
+
if (strict && skipped.length > 0)
|
|
26
|
+
process.exitCode = 1;
|
|
24
27
|
}
|
package/dist/cli/watch.js
CHANGED
|
@@ -14,141 +14,242 @@ const eventraSdk_1 = require("../analysis/sdk/eventraSdk");
|
|
|
14
14
|
const EventraEngine_1 = require("../core/EventraEngine");
|
|
15
15
|
const scanResults_1 = require("../core/scanResults");
|
|
16
16
|
const loadPlugins_1 = require("../plugin/loadPlugins");
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const scanFilters_1 = require("../core/scanFilters");
|
|
18
|
+
const skipped_1 = require("../core/skipped");
|
|
19
|
+
const fileLimits_1 = require("../core/fileLimits");
|
|
19
20
|
const getVirtualFile_1 = require("../filesystem/getVirtualFile");
|
|
21
|
+
const SDK_TYPES_FILE = "__eventra_sdk_types__.d.ts";
|
|
22
|
+
// Files outside `include` can still change what a scanned file means when
|
|
23
|
+
// something imports them (a shared constants module, a wrapper in a helper).
|
|
24
|
+
// Only code-like files are worth rebuilding the program for.
|
|
25
|
+
const CODE_FILE = /\.(?:[cm]?[jt]s|[jt]sx|json)$/;
|
|
26
|
+
const isNotFound = (err) => err?.code === "ENOENT";
|
|
20
27
|
async function watch() {
|
|
21
|
-
const config = await (0, config_1.
|
|
22
|
-
if (!config)
|
|
23
|
-
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
28
|
+
const config = await (0, config_1.requireConfig)();
|
|
29
|
+
if (!config)
|
|
30
|
+
return undefined;
|
|
26
31
|
console.log(chalk_1.default.blue("Watching...\n"));
|
|
27
32
|
const plugins = await (0, loadPlugins_1.createPluginRegistry)(config);
|
|
28
33
|
const include = [...config.sync.include, ...plugins.getIncludePatterns()];
|
|
29
34
|
const engine = new EventraEngine_1.EventraEngine(process.cwd(), plugins);
|
|
30
|
-
const fileDeps = new Map();
|
|
31
|
-
const cache = new Map();
|
|
32
35
|
const watchedDeps = new Set();
|
|
33
36
|
const trackedFiles = new Set();
|
|
34
37
|
const sourceFiles = new Set();
|
|
38
|
+
// files whose last analysis failed; while there are any, saved results keep
|
|
39
|
+
// the previously known events (see buildConfigFromScan)
|
|
40
|
+
const skippedNow = new Map();
|
|
35
41
|
const normalize = (file) => {
|
|
36
42
|
return path_1.default.resolve(file).replace(/\\/g, "/");
|
|
37
43
|
};
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// The same rules as the initial scan (and as `sync`): plain exclude names match
|
|
45
|
+
// at any depth, and a changed file is only handled when it matches `include`.
|
|
46
|
+
// The config files are written by this process, so they must never trigger a run.
|
|
47
|
+
const filter = (0, scanFilters_1.createFileFilter)(include, config.sync.exclude, process.cwd(), [
|
|
48
|
+
config_1.CONFIG_NAME,
|
|
49
|
+
config_1.LOCAL_CONFIG_NAME,
|
|
50
|
+
]);
|
|
51
|
+
const isExcludedPath = (file) => filter.isExcluded(normalize(file));
|
|
52
|
+
const isSource = (abs) => sourceFiles.has(abs) || trackedFiles.has(abs) || filter.isIncluded(abs);
|
|
53
|
+
// an imported (or newly importable) file outside `include`
|
|
54
|
+
const isDependencyCandidate = (abs) => CODE_FILE.test(abs) && !isSource(abs);
|
|
55
|
+
const shouldHandle = (abs) => !filter.isExcluded(abs) && (isSource(abs) || isDependencyCandidate(abs));
|
|
56
|
+
// what eventra.json holds now: "keep the known events" while a file is broken
|
|
57
|
+
// must mean the events this process saved last, not the ones at startup
|
|
58
|
+
let saved = config;
|
|
59
|
+
const persist = async () => {
|
|
60
|
+
saved = await (0, scanResults_1.persistScanResults)(saved, engine, { preserveKnown: skippedNow.size > 0 });
|
|
45
61
|
};
|
|
62
|
+
const signature = () => JSON.stringify([engine.getAllEvents(), engine.getAllFunctionWrappers()]);
|
|
46
63
|
const files = await (0, fast_glob_1.default)(include, {
|
|
47
|
-
ignore: config.sync.exclude,
|
|
64
|
+
ignore: (0, scanFilters_1.expandExcludePatterns)(config.sync.exclude),
|
|
48
65
|
absolute: true,
|
|
49
66
|
});
|
|
67
|
+
const ignoredInitial = [];
|
|
50
68
|
engine.beginPreload();
|
|
51
69
|
await engine.preloadFile(SDK_TYPES_FILE, eventraSdk_1.EVENTRA_SDK_SHIM);
|
|
52
70
|
for (const file of files) {
|
|
53
71
|
try {
|
|
54
72
|
const abs = normalize(file);
|
|
55
|
-
sourceFiles.add(abs);
|
|
56
73
|
const raw = await promises_1.default.readFile(abs, "utf-8");
|
|
74
|
+
// the same limit as `sync`: too big to analyse, but not a failure
|
|
75
|
+
if ((0, fileLimits_1.isOversized)(raw)) {
|
|
76
|
+
ignoredInitial.push({ file: abs, reason: fileLimits_1.OVERSIZED_REASON });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
sourceFiles.add(abs);
|
|
57
80
|
const virtualFiles = await plugins.preprocessFile({ fileName: abs, content: raw });
|
|
58
81
|
for (const virtualFile of virtualFiles) {
|
|
59
82
|
const scanPath = normalize(virtualFile.fileName);
|
|
60
|
-
const parsed = await (0, processFile_1.processFile)(scanPath, virtualFile.content);
|
|
61
|
-
cache.set(scanPath, parsed);
|
|
62
83
|
trackedFiles.add(scanPath);
|
|
63
84
|
await engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(scanPath), virtualFile.content);
|
|
64
|
-
fileDeps.set(scanPath, new Set(parsed.dependencies
|
|
65
|
-
.filter((dep) => dep.startsWith(".") || dep.startsWith("/"))
|
|
66
|
-
.map((dep) => normalize(path_1.default.resolve(path_1.default.dirname(scanPath), dep)))));
|
|
67
85
|
}
|
|
68
86
|
}
|
|
69
|
-
catch {
|
|
70
|
-
|
|
87
|
+
catch (err) {
|
|
88
|
+
const skip = (0, skipped_1.skippedFrom)(file, err);
|
|
89
|
+
skippedNow.set(normalize(file), skip);
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
92
|
engine.endPreload();
|
|
93
|
+
(0, skipped_1.reportSkipped)([...skippedNow.values()]);
|
|
94
|
+
(0, skipped_1.reportIgnored)(ignoredInitial);
|
|
74
95
|
await engine.runFullAnalysis([SDK_TYPES_FILE, ...trackedFiles].map((f) => (0, getVirtualFile_1.getVirtualFile)(f)), config);
|
|
75
|
-
await (
|
|
96
|
+
await persist();
|
|
76
97
|
const initialEvents = engine.getAllEvents();
|
|
77
98
|
const initialWrappers = engine.getAllFunctionWrappers();
|
|
78
|
-
console.log(chalk_1.default.gray(`Initial: ${initialEvents.length}
|
|
99
|
+
console.log(chalk_1.default.gray(`Initial: ${(0, skipped_1.plural)(initialEvents.length, "event")}, ${(0, skipped_1.plural)(initialWrappers.length, "wrapper")}\n`));
|
|
79
100
|
// Watch the project root (not just the files found by the initial scan) so
|
|
80
|
-
// that brand-new files matching `include` are noticed as they're created
|
|
101
|
+
// that brand-new files matching `include` are noticed as they're created -
|
|
81
102
|
// a fixed file list only ever detects edits to files chokidar already knows
|
|
82
|
-
// about.
|
|
83
|
-
// are unaffected by this and keep working the same way.
|
|
103
|
+
// about. Project files that live outside the root are added below.
|
|
84
104
|
const watcher = chokidar_1.default.watch(process.cwd(), {
|
|
85
105
|
ignoreInitial: true,
|
|
86
106
|
ignored: isExcludedPath,
|
|
87
107
|
});
|
|
108
|
+
const root = normalize(process.cwd());
|
|
109
|
+
const isOutsideRoot = (abs) => abs !== root && !abs.startsWith(`${root}/`);
|
|
110
|
+
// Imported files outside the project root are not covered by the root
|
|
111
|
+
// watcher: follow exactly the ones the program really resolved.
|
|
112
|
+
const syncOutsideDeps = () => {
|
|
113
|
+
const outside = new Set(engine
|
|
114
|
+
.getProjectFileNames()
|
|
115
|
+
.map(normalize)
|
|
116
|
+
.filter((file) => isOutsideRoot(file) && !filter.isExcluded(file) && CODE_FILE.test(file)));
|
|
117
|
+
for (const dep of outside) {
|
|
118
|
+
if (watchedDeps.has(dep))
|
|
119
|
+
continue;
|
|
120
|
+
watcher.add(dep);
|
|
121
|
+
watchedDeps.add(dep);
|
|
122
|
+
}
|
|
123
|
+
for (const dep of [...watchedDeps]) {
|
|
124
|
+
if (outside.has(dep))
|
|
125
|
+
continue;
|
|
126
|
+
watcher.unwatch(dep);
|
|
127
|
+
watchedDeps.delete(dep);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
syncOutsideDeps();
|
|
88
131
|
const queue = new Map();
|
|
89
132
|
let timer = null;
|
|
133
|
+
// a source file is gone (deleted, or no longer analysable such as one that grew past the size limit)
|
|
134
|
+
const dropSource = async (abs) => {
|
|
135
|
+
sourceFiles.delete(abs);
|
|
136
|
+
skippedNow.delete(abs);
|
|
137
|
+
for (const virtualPath of plugins.getVirtualPathsForSource(abs)) {
|
|
138
|
+
trackedFiles.delete(virtualPath);
|
|
139
|
+
await engine.removeFile((0, getVirtualFile_1.getVirtualFile)(virtualPath), config);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
90
142
|
const run = async () => {
|
|
91
143
|
const batch = [...queue.keys()];
|
|
92
144
|
queue.clear();
|
|
145
|
+
if (batch.length === 0)
|
|
146
|
+
return;
|
|
147
|
+
const before = signature();
|
|
148
|
+
let touchedSource = false;
|
|
149
|
+
const skippedThisRun = [];
|
|
150
|
+
const ignoredThisRun = [];
|
|
151
|
+
// imported files outside `include` that changed, appeared or vanished: one rebuild for all of them
|
|
152
|
+
const external = batch.filter((file) => !isSource(file));
|
|
153
|
+
if (external.length > 0) {
|
|
154
|
+
try {
|
|
155
|
+
await engine.refreshExternalFiles(external, config);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.error(chalk_1.default.red(`watch error: ${(0, skipped_1.errorMessage)(err)}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
93
161
|
for (const file of batch) {
|
|
162
|
+
if (!isSource(file))
|
|
163
|
+
continue;
|
|
164
|
+
touchedSource = true;
|
|
94
165
|
try {
|
|
95
166
|
const raw = await promises_1.default.readFile(file, "utf-8");
|
|
167
|
+
if ((0, fileLimits_1.isOversized)(raw)) {
|
|
168
|
+
// too big to analyse, like `sync`: not a failure, and its events are not kept
|
|
169
|
+
await dropSource(file);
|
|
170
|
+
ignoredThisRun.push({ file, reason: fileLimits_1.OVERSIZED_REASON });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
96
173
|
const virtualFiles = await plugins.preprocessFile({ fileName: file, content: raw });
|
|
174
|
+
sourceFiles.add(file);
|
|
175
|
+
skippedNow.delete(file);
|
|
97
176
|
for (const virtualFile of virtualFiles) {
|
|
98
177
|
const scanPath = normalize(virtualFile.fileName);
|
|
99
|
-
const parsed = await (0, processFile_1.processFile)(scanPath, virtualFile.content);
|
|
100
|
-
cache.set(scanPath, parsed);
|
|
101
178
|
trackedFiles.add(scanPath);
|
|
102
179
|
await engine.updateFile((0, getVirtualFile_1.getVirtualFile)(scanPath), virtualFile.content, config);
|
|
103
|
-
const prevDeps = fileDeps.get(scanPath) ?? new Set();
|
|
104
|
-
const nextDeps = new Set(parsed.dependencies
|
|
105
|
-
.filter((dep) => dep.startsWith(".") || dep.startsWith("/"))
|
|
106
|
-
.map((dep) => normalize(path_1.default.resolve(path_1.default.dirname(scanPath), dep))));
|
|
107
|
-
for (const dep of nextDeps) {
|
|
108
|
-
if (prevDeps.has(dep))
|
|
109
|
-
continue;
|
|
110
|
-
watcher.add(dep);
|
|
111
|
-
watchedDeps.add(dep);
|
|
112
|
-
}
|
|
113
|
-
for (const dep of prevDeps) {
|
|
114
|
-
if (nextDeps.has(dep))
|
|
115
|
-
continue;
|
|
116
|
-
watcher.unwatch(dep);
|
|
117
|
-
watchedDeps.delete(dep);
|
|
118
|
-
}
|
|
119
|
-
fileDeps.set(scanPath, nextDeps);
|
|
120
180
|
}
|
|
121
181
|
}
|
|
122
|
-
catch {
|
|
123
|
-
|
|
182
|
+
catch (err) {
|
|
183
|
+
if (isNotFound(err)) {
|
|
184
|
+
// gone before it could be read (created and deleted within one debounce, or removed mid-run)
|
|
185
|
+
await dropSource(file);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const skip = (0, skipped_1.skippedFrom)(file, err);
|
|
189
|
+
skippedNow.set(file, skip);
|
|
190
|
+
skippedThisRun.push(skip);
|
|
124
191
|
}
|
|
125
192
|
}
|
|
126
|
-
|
|
127
|
-
|
|
193
|
+
(0, skipped_1.reportSkipped)(skippedThisRun);
|
|
194
|
+
(0, skipped_1.reportIgnored)(ignoredThisRun);
|
|
195
|
+
syncOutsideDeps();
|
|
196
|
+
// a batch of imported-only files that changed nothing about the result is not worth a write or a log line
|
|
197
|
+
if (!touchedSource && signature() === before)
|
|
198
|
+
return;
|
|
199
|
+
await persist();
|
|
200
|
+
console.log(chalk_1.default.green(`Updated: ${(0, skipped_1.plural)(engine.getAllEvents().length, "event")}, ${(0, skipped_1.plural)(engine.getAllFunctionWrappers().length, "wrapper")}`));
|
|
201
|
+
};
|
|
202
|
+
// One run at a time: a timer firing while the previous run is still awaiting
|
|
203
|
+
// would otherwise interleave two runs over the same engine.
|
|
204
|
+
let chain = Promise.resolve();
|
|
205
|
+
const enqueue = (task) => {
|
|
206
|
+
chain = chain.then(task).catch((err) => {
|
|
207
|
+
console.error(chalk_1.default.red(`watch error: ${(0, skipped_1.errorMessage)(err)}`));
|
|
208
|
+
});
|
|
128
209
|
};
|
|
129
210
|
const schedule = (file) => {
|
|
130
211
|
const abs = normalize(file);
|
|
212
|
+
if (!shouldHandle(abs))
|
|
213
|
+
return;
|
|
131
214
|
queue.set(abs, true);
|
|
132
215
|
if (timer)
|
|
133
216
|
clearTimeout(timer);
|
|
134
|
-
timer = setTimeout(run, 200);
|
|
217
|
+
timer = setTimeout(() => enqueue(run), 200);
|
|
135
218
|
};
|
|
136
219
|
watcher
|
|
137
220
|
.on("change", schedule)
|
|
138
221
|
.on("add", schedule)
|
|
139
|
-
.on("unlink",
|
|
222
|
+
.on("unlink", (file) => {
|
|
140
223
|
const abs = normalize(file);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
224
|
+
if (filter.isExcluded(abs))
|
|
225
|
+
return;
|
|
226
|
+
// a file that failed to analyse is in no set but is still remembered as skipped
|
|
227
|
+
if (!sourceFiles.has(abs) && !trackedFiles.has(abs) && !skippedNow.has(abs)) {
|
|
228
|
+
// an imported-only file vanished: dependents resolve differently now
|
|
229
|
+
if (CODE_FILE.test(abs) && engine.hasProgramFile(abs)) {
|
|
230
|
+
queue.set(abs, true);
|
|
231
|
+
if (timer)
|
|
232
|
+
clearTimeout(timer);
|
|
233
|
+
timer = setTimeout(() => enqueue(run), 200);
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
146
236
|
}
|
|
147
|
-
|
|
148
|
-
|
|
237
|
+
enqueue(async () => {
|
|
238
|
+
await dropSource(abs);
|
|
239
|
+
await persist();
|
|
240
|
+
console.log(chalk_1.default.red(`Removed -> ${(0, skipped_1.plural)(engine.getAllEvents().length, "event")}, ${(0, skipped_1.plural)(engine.getAllFunctionWrappers().length, "wrapper")}`));
|
|
241
|
+
});
|
|
149
242
|
});
|
|
150
243
|
process.on("SIGINT", async () => {
|
|
151
244
|
await watcher.close();
|
|
152
245
|
process.exit(0);
|
|
153
246
|
});
|
|
247
|
+
return {
|
|
248
|
+
close: async () => {
|
|
249
|
+
if (timer)
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
await watcher.close();
|
|
252
|
+
await chain;
|
|
253
|
+
},
|
|
254
|
+
};
|
|
154
255
|
}
|