@appland/scanner 1.72.0 → 1.73.1
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
# [@appland/scanner-v1.73.1](https://github.com/getappmap/appmap-js/compare/@appland/scanner-v1.73.0...@appland/scanner-v1.73.1) (2022-11-29)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* Continue watching correctly after skipping files ([b5d7c2d](https://github.com/getappmap/appmap-js/commit/b5d7c2df6da78ae20b6547e0ddd23f41f20cb428))
|
|
7
|
+
|
|
8
|
+
# [@appland/scanner-v1.73.0](https://github.com/getappmap/appmap-js/compare/@appland/scanner-v1.72.0...@appland/scanner-v1.73.0) (2022-11-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* Address PR comments ([788dab4](https://github.com/getappmap/appmap-js/commit/788dab46cea2933dc0c5965f6307f5c0ef4d2873))
|
|
14
|
+
* sendTelemetry uses ScanResults.aggregate ([14e5bbb](https://github.com/getappmap/appmap-js/commit/14e5bbb074e4153878875a9376cbf10330baa1b4))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* Send scan:started at beginning of analysis and send data in ([76d2141](https://github.com/getappmap/appmap-js/commit/76d2141fdadabeb21f15af86f879e76ff0389237))
|
|
20
|
+
|
|
1
21
|
# [@appland/scanner-v1.72.0](https://github.com/getappmap/appmap-js/compare/@appland/scanner-v1.71.7...@appland/scanner-v1.72.0) (2022-11-01)
|
|
2
22
|
|
|
3
23
|
|
|
@@ -20,11 +20,18 @@ const findingsReport_1 = __importDefault(require("../../report/findingsReport"))
|
|
|
20
20
|
const summaryReport_1 = __importDefault(require("../../report/summaryReport"));
|
|
21
21
|
const formatReport_1 = require("./formatReport");
|
|
22
22
|
const telemetry_1 = __importDefault(require("../../telemetry"));
|
|
23
|
+
const scanResults_1 = require("../../report/scanResults");
|
|
23
24
|
const util_1 = require("../../rules/lib/util");
|
|
24
25
|
const validateFile_1 = __importDefault(require("../validateFile"));
|
|
25
26
|
function singleScan(options) {
|
|
26
27
|
return __awaiter(this, void 0, void 0, function* () {
|
|
27
28
|
const { appmapFile, appmapDir, configuration, reportAllFindings, appId, ide, reportFile } = options;
|
|
29
|
+
telemetry_1.default.sendEvent({
|
|
30
|
+
name: 'scan:started',
|
|
31
|
+
properties: {
|
|
32
|
+
ide,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
28
35
|
const skipErrors = appmapDir !== undefined;
|
|
29
36
|
const files = yield (0, util_1.collectAppMapFiles)(appmapFile, appmapDir);
|
|
30
37
|
yield Promise.all(files.map((file) => __awaiter(this, void 0, void 0, function* () { return (0, validateFile_1.default)('file', file); })));
|
|
@@ -52,22 +59,12 @@ function singleScan(options) {
|
|
|
52
59
|
const elapsed = Date.now() - startTime;
|
|
53
60
|
const numChecks = scanResults.checks.length * scanResults.summary.numAppMaps;
|
|
54
61
|
console.log(`Performed ${numChecks} checks in ${elapsed}ms (${Math.floor(numChecks / (elapsed / 1000.0))} checks/sec)`);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
exports.default = singleScan;
|
|
59
|
-
function sendTelemetry(scanResults, msElapsed) {
|
|
60
|
-
const rules = [...new Set(scanResults.checks.map(({ id }) => id))];
|
|
61
|
-
telemetry_1.default.sendEvent({
|
|
62
|
-
name: 'scan:completed',
|
|
63
|
-
properties: {
|
|
64
|
-
rules: rules.join(', '),
|
|
65
|
-
},
|
|
66
|
-
metrics: {
|
|
67
|
-
duration: msElapsed / 1000,
|
|
68
|
-
numRules: rules.length,
|
|
62
|
+
(0, scanResults_1.sendScanResultsTelemetry)({
|
|
63
|
+
ruleIds: scanResults.summary.rules,
|
|
69
64
|
numAppMaps: scanResults.summary.numAppMaps,
|
|
70
|
-
numFindings: scanResults.
|
|
71
|
-
|
|
65
|
+
numFindings: scanResults.summary.numFindings,
|
|
66
|
+
elapsedMs: elapsed,
|
|
67
|
+
});
|
|
72
68
|
});
|
|
73
69
|
}
|
|
70
|
+
exports.default = singleScan;
|
|
@@ -38,12 +38,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
38
38
|
exports.Watcher = void 0;
|
|
39
39
|
const promises_1 = require("fs/promises");
|
|
40
40
|
const chokidar = __importStar(require("chokidar"));
|
|
41
|
-
const formatReport_1 = require("./formatReport");
|
|
42
|
-
const scanner_1 = __importDefault(require("./scanner"));
|
|
43
|
-
const configurationProvider_1 = require("../../configuration/configurationProvider");
|
|
44
41
|
const assert_1 = __importDefault(require("assert"));
|
|
45
42
|
const path_1 = __importDefault(require("path"));
|
|
46
43
|
const async_1 = require("async");
|
|
44
|
+
const node_util_1 = require("node:util");
|
|
45
|
+
const formatReport_1 = require("./formatReport");
|
|
46
|
+
const scanner_1 = __importDefault(require("./scanner"));
|
|
47
|
+
const configurationProvider_1 = require("../../configuration/configurationProvider");
|
|
48
|
+
const telemetry_1 = __importDefault(require("../../telemetry"));
|
|
49
|
+
const events_1 = __importDefault(require("events"));
|
|
47
50
|
function isDir(targetPath) {
|
|
48
51
|
return __awaiter(this, void 0, void 0, function* () {
|
|
49
52
|
try {
|
|
@@ -70,11 +73,17 @@ function isAncestorPath(ancestor, descendant) {
|
|
|
70
73
|
class Watcher {
|
|
71
74
|
constructor(options) {
|
|
72
75
|
this.options = options;
|
|
73
|
-
this.
|
|
76
|
+
this.scanEventEmitter = new events_1.default();
|
|
77
|
+
// do not remove callbackify, apparently on windows
|
|
78
|
+
// passing plain async function doesn't work (?)
|
|
79
|
+
this.queue = (0, async_1.queue)((0, node_util_1.callbackify)(this.scan.bind(this)), 2);
|
|
74
80
|
}
|
|
75
81
|
watch() {
|
|
76
82
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
83
|
yield this.reloadConfig();
|
|
84
|
+
telemetry_1.default.sendEvent({
|
|
85
|
+
name: 'scan:started',
|
|
86
|
+
});
|
|
78
87
|
this.configWatcher = chokidar.watch(this.options.configFile, {
|
|
79
88
|
ignoreInitial: true,
|
|
80
89
|
});
|
|
@@ -128,7 +137,7 @@ class Watcher {
|
|
|
128
137
|
return;
|
|
129
138
|
this.queue.push(mtimePath);
|
|
130
139
|
}
|
|
131
|
-
scan(mtimePath
|
|
140
|
+
scan(mtimePath) {
|
|
132
141
|
return __awaiter(this, void 0, void 0, function* () {
|
|
133
142
|
(0, assert_1.default)(this.config, `config should always be loaded before appmapWatcher triggers a scan`);
|
|
134
143
|
const appmapFile = [path_1.default.dirname(mtimePath), 'appmap.json'].join('.');
|
|
@@ -140,14 +149,13 @@ class Watcher {
|
|
|
140
149
|
reportStats.mtimeMs > appmapStats.mtimeMs &&
|
|
141
150
|
reportStats.mtimeMs > this.config.timestampMs)
|
|
142
151
|
return; // report is up to date
|
|
152
|
+
const startTime = Date.now();
|
|
143
153
|
const scanner = yield (0, scanner_1.default)(true, this.config, [appmapFile]);
|
|
144
154
|
const rawScanResults = yield scanner.scan();
|
|
155
|
+
const elapsed = Date.now() - startTime;
|
|
156
|
+
this.scanEventEmitter.emit('scan', { scanResults: rawScanResults, elapsed });
|
|
145
157
|
// Always report the raw data
|
|
146
158
|
yield (0, promises_1.writeFile)(reportFile, (0, formatReport_1.formatReport)(rawScanResults));
|
|
147
|
-
// tell the queue that the current task is complete
|
|
148
|
-
if (callback) {
|
|
149
|
-
yield callback();
|
|
150
|
-
}
|
|
151
159
|
});
|
|
152
160
|
}
|
|
153
161
|
reloadConfig() {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WatchScanTelemetry = void 0;
|
|
7
|
+
const eventAggregator_1 = __importDefault(require("../../lib/eventAggregator"));
|
|
8
|
+
const scanResults_1 = require("../../report/scanResults");
|
|
9
|
+
class WatchScanTelemetry {
|
|
10
|
+
constructor(scanEvents) {
|
|
11
|
+
new eventAggregator_1.default((events) => {
|
|
12
|
+
const scanEvents = events.map((e) => e.arg);
|
|
13
|
+
this.sendTelemetry(scanEvents);
|
|
14
|
+
}).attach(scanEvents, 'scan');
|
|
15
|
+
}
|
|
16
|
+
sendTelemetry(scanEvents) {
|
|
17
|
+
const ruleIds = new Set();
|
|
18
|
+
let elapsed = 0;
|
|
19
|
+
const telemetryScanResults = new scanResults_1.ScanResults();
|
|
20
|
+
for (const scanEvent of scanEvents) {
|
|
21
|
+
telemetryScanResults.aggregate(scanEvent.scanResults);
|
|
22
|
+
elapsed += scanEvent.elapsed;
|
|
23
|
+
}
|
|
24
|
+
telemetryScanResults.summary.rules.forEach((rule) => ruleIds.add(rule));
|
|
25
|
+
(0, scanResults_1.sendScanResultsTelemetry)({
|
|
26
|
+
ruleIds: [...ruleIds],
|
|
27
|
+
numAppMaps: telemetryScanResults.summary.numAppMaps,
|
|
28
|
+
numFindings: telemetryScanResults.summary.numFindings,
|
|
29
|
+
elapsedMs: elapsed,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.WatchScanTelemetry = WatchScanTelemetry;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MaxMSBetween = void 0;
|
|
4
|
+
exports.MaxMSBetween = 10 * 1000;
|
|
5
|
+
// TODO: Unify with the code in packages/cli - find a way to make a common import.
|
|
6
|
+
class EventAggregator {
|
|
7
|
+
constructor(callback, maxMsBetween = exports.MaxMSBetween) {
|
|
8
|
+
this.callback = callback;
|
|
9
|
+
this.maxMsBetween = maxMsBetween;
|
|
10
|
+
this.pending = [];
|
|
11
|
+
process.on('exit', () => {
|
|
12
|
+
if (this.timeout) {
|
|
13
|
+
clearTimeout(this.timeout);
|
|
14
|
+
this.emitPending();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
push(emitter, event, arg) {
|
|
19
|
+
this.pending.push({ emitter, event, arg });
|
|
20
|
+
this.refresh();
|
|
21
|
+
}
|
|
22
|
+
refresh() {
|
|
23
|
+
if (this.timeout)
|
|
24
|
+
clearTimeout(this.timeout);
|
|
25
|
+
this.timeout = setTimeout(this.emitPending.bind(this), this.maxMsBetween);
|
|
26
|
+
}
|
|
27
|
+
emitPending() {
|
|
28
|
+
this.callback(this.pending);
|
|
29
|
+
this.timeout = undefined;
|
|
30
|
+
this.pending = [];
|
|
31
|
+
}
|
|
32
|
+
attach(emitter, event) {
|
|
33
|
+
emitter.on(event, (...args) => this.push(emitter, event, args[0]));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.default = EventAggregator;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ScanResults = void 0;
|
|
6
|
+
exports.sendScanResultsTelemetry = exports.ScanResults = void 0;
|
|
7
|
+
const telemetry_1 = __importDefault(require("../telemetry"));
|
|
4
8
|
class DistinctItems {
|
|
5
9
|
constructor() {
|
|
6
10
|
this.members = {};
|
|
@@ -55,7 +59,7 @@ function collectMetadata(metadata) {
|
|
|
55
59
|
* It's used for printing a user-friendly summary report, it's not used for machine-readable program output.
|
|
56
60
|
*/
|
|
57
61
|
class ScanResults {
|
|
58
|
-
constructor(configuration, appMapMetadata, findings, checks) {
|
|
62
|
+
constructor(configuration = { checks: [] }, appMapMetadata = {}, findings = [], checks = []) {
|
|
59
63
|
this.configuration = configuration;
|
|
60
64
|
this.appMapMetadata = appMapMetadata;
|
|
61
65
|
this.findings = findings;
|
|
@@ -72,5 +76,31 @@ class ScanResults {
|
|
|
72
76
|
withFindings(findings) {
|
|
73
77
|
return new ScanResults(this.configuration, this.appMapMetadata, findings, this.checks);
|
|
74
78
|
}
|
|
79
|
+
aggregate(sourceScanResults) {
|
|
80
|
+
this.summary.numAppMaps += sourceScanResults.summary.numAppMaps;
|
|
81
|
+
this.summary.numChecks += sourceScanResults.summary.numChecks;
|
|
82
|
+
this.summary.rules = [...new Set(this.summary.rules.concat(sourceScanResults.summary.rules))];
|
|
83
|
+
this.summary.ruleLabels = [
|
|
84
|
+
...new Set(this.summary.ruleLabels.concat(sourceScanResults.summary.ruleLabels)),
|
|
85
|
+
];
|
|
86
|
+
this.summary.numFindings += sourceScanResults.summary.numFindings;
|
|
87
|
+
// we don't need sourceScanResults.summary.appMetadata
|
|
88
|
+
// appMapMetadata.Git may also contain secrets we don't want to transmit.
|
|
89
|
+
}
|
|
75
90
|
}
|
|
76
91
|
exports.ScanResults = ScanResults;
|
|
92
|
+
function sendScanResultsTelemetry(telemetry) {
|
|
93
|
+
telemetry_1.default.sendEvent({
|
|
94
|
+
name: 'scan:completed',
|
|
95
|
+
properties: {
|
|
96
|
+
rules: telemetry.ruleIds.sort().join(', '),
|
|
97
|
+
},
|
|
98
|
+
metrics: {
|
|
99
|
+
duration: telemetry.elapsedMs / 1000,
|
|
100
|
+
numRules: telemetry.ruleIds.length,
|
|
101
|
+
numAppMaps: telemetry.numAppMaps,
|
|
102
|
+
numFindings: telemetry.numFindings,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
exports.sendScanResultsTelemetry = sendScanResultsTelemetry;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appland/scanner",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.73.1",
|
|
4
4
|
"description": "Analyze AppMaps for code flaws",
|
|
5
5
|
"bin": "built/cli.js",
|
|
6
6
|
"files": [
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@appland/client": "^1.3.0",
|
|
63
63
|
"@appland/models": "^1.18.1",
|
|
64
|
-
"@appland/openapi": "1.
|
|
64
|
+
"@appland/openapi": "1.2.0",
|
|
65
65
|
"@appland/sql-parser": "^1.5.0",
|
|
66
66
|
"@types/cli-progress": "^3.9.2",
|
|
67
67
|
"ajv": "^8.8.2",
|