@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
- sendTelemetry(scanResults, elapsed);
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.findings.length,
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.queue = (0, async_1.queue)(this.scan.bind(this), 2);
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, callback) {
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.72.0",
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.1.0",
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",