@groma/scanner-go 0.1.0 → 0.1.2
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/dist/darwin-arm64/worker +0 -0
- package/dist/linux-arm64/worker +0 -0
- package/dist/linux-x64/worker +0 -0
- package/dist/win32-arm64/worker.exe +0 -0
- package/dist/win32-x64/worker.exe +0 -0
- package/package.json +4 -7
- package/src/index.js +281 -47
package/dist/darwin-arm64/worker
CHANGED
|
Binary file
|
package/dist/linux-arm64/worker
CHANGED
|
Binary file
|
package/dist/linux-x64/worker
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groma/scanner-go",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Go project evidence through Go language tooling.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"os": [
|
|
9
|
-
"win32",
|
|
10
9
|
"linux",
|
|
11
|
-
"darwin"
|
|
10
|
+
"darwin",
|
|
11
|
+
"win32"
|
|
12
12
|
],
|
|
13
13
|
"cpu": [
|
|
14
14
|
"arm64",
|
|
@@ -35,10 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"compatibility": {
|
|
38
|
-
"groma": "^0.3.0"
|
|
39
|
-
"technologyVersions": {
|
|
40
|
-
"go": "1.20.0"
|
|
41
|
-
}
|
|
38
|
+
"groma": "^0.3.0"
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
41
|
}
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,152 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// plugins/scanners/go/src/
|
|
2
|
+
// plugins/scanners/go/src/index.ts
|
|
3
|
+
import path5 from "path";
|
|
4
|
+
|
|
5
|
+
// plugins/scanners/projects.ts
|
|
3
6
|
import { execFile } from "child_process";
|
|
4
|
-
import {
|
|
7
|
+
import { existsSync } from "fs";
|
|
5
8
|
import path from "path";
|
|
6
|
-
import {
|
|
9
|
+
import { promisify } from "util";
|
|
10
|
+
var execute = promisify(execFile);
|
|
11
|
+
var excluded = new Set([
|
|
12
|
+
".git",
|
|
13
|
+
"node_modules",
|
|
14
|
+
"vendor",
|
|
15
|
+
"target",
|
|
16
|
+
"dist",
|
|
17
|
+
"build",
|
|
18
|
+
"bin",
|
|
19
|
+
"obj",
|
|
20
|
+
".gradle",
|
|
21
|
+
".angular",
|
|
22
|
+
"coverage",
|
|
23
|
+
"generated",
|
|
24
|
+
"groma",
|
|
25
|
+
".groma"
|
|
26
|
+
]);
|
|
27
|
+
async function repositoryFiles(root, matches) {
|
|
28
|
+
const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
|
|
29
|
+
return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && existsSync(path.join(root, file))))].sort();
|
|
30
|
+
}
|
|
31
|
+
function projectFiles(root, matches) {
|
|
32
|
+
return repositoryFiles(root, (file) => matches(file) && !file.split("/").some((part) => excluded.has(part)));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// plugins/scanners/project-scanner.ts
|
|
36
|
+
import path3 from "path";
|
|
37
|
+
|
|
38
|
+
// plugins/scanners/observations.ts
|
|
39
|
+
import path2 from "path";
|
|
40
|
+
|
|
41
|
+
// packages/scanner/src/values.ts
|
|
42
|
+
function object(value, label) {
|
|
43
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
44
|
+
throw new Error(`${label} must be an object`);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function string(value, label) {
|
|
49
|
+
if (typeof value !== "string")
|
|
50
|
+
throw new Error(`${label} must be a string`);
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function array(value, label) {
|
|
54
|
+
if (!Array.isArray(value))
|
|
55
|
+
throw new Error(`${label} must be an array`);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// packages/scanner/src/http.ts
|
|
60
|
+
var pathText = /^[A-Za-z0-9\-._~!$&'()*+,;=:@%]+$/;
|
|
61
|
+
var methodToken = /^[A-Z][A-Z-]*$/;
|
|
62
|
+
function fail(message) {
|
|
63
|
+
throw new Error(message);
|
|
64
|
+
}
|
|
65
|
+
function text(value, label) {
|
|
66
|
+
if (typeof value !== "string" || !pathText.test(value))
|
|
67
|
+
fail(`${label} must be nonempty URL path characters`);
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function method(value, label, anyMethod) {
|
|
71
|
+
if (typeof value !== "string" || !(methodToken.test(value) || anyMethod && value === "*")) {
|
|
72
|
+
fail(`${label} must be an uppercase HTTP method${anyMethod ? " or *" : ""}`);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
function operation(value, operations) {
|
|
77
|
+
if (typeof value !== "string" || !operations.has(value))
|
|
78
|
+
fail("HTTP fact references unknown operation");
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
function endpointSegment(value, last) {
|
|
82
|
+
const segment = object(value, "HTTP endpoint segment");
|
|
83
|
+
if (segment.kind === "literal")
|
|
84
|
+
return { kind: "literal", value: text(segment.value, "HTTP literal segment") };
|
|
85
|
+
if (segment.kind !== "parameter" && segment.kind !== "catch-all")
|
|
86
|
+
fail(`unknown HTTP endpoint segment kind: ${String(segment.kind)}`);
|
|
87
|
+
if (segment.kind === "catch-all" && !last)
|
|
88
|
+
fail("an HTTP catch-all segment must be last");
|
|
89
|
+
for (const flag of ["optional", "constrained"]) {
|
|
90
|
+
if (segment[flag] !== undefined && typeof segment[flag] !== "boolean")
|
|
91
|
+
fail(`HTTP segment ${flag} must be a boolean`);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
kind: segment.kind,
|
|
95
|
+
name: text(segment.name, "HTTP segment name"),
|
|
96
|
+
...segment.optional ? { optional: true } : {},
|
|
97
|
+
...segment.constrained ? { constrained: true } : {}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function requestSegment(value) {
|
|
101
|
+
const segment = object(value, "HTTP request segment");
|
|
102
|
+
if (segment.kind === "literal")
|
|
103
|
+
return { kind: "literal", value: text(segment.value, "HTTP literal segment") };
|
|
104
|
+
if (segment.kind === "dynamic" || segment.kind === "unknown")
|
|
105
|
+
return { kind: segment.kind };
|
|
106
|
+
return fail(`unknown HTTP request segment kind: ${String(segment.kind)}`);
|
|
107
|
+
}
|
|
108
|
+
function order(value) {
|
|
109
|
+
const fact = object(value, "HTTP endpoint order");
|
|
110
|
+
const application = string(fact.application, "HTTP endpoint order application");
|
|
111
|
+
if (application === "")
|
|
112
|
+
fail("HTTP endpoint order application must be nonempty");
|
|
113
|
+
const position = fact.position;
|
|
114
|
+
if (typeof position !== "number" || !Number.isInteger(position) || position < 0) {
|
|
115
|
+
fail("HTTP endpoint order position must be a nonnegative integer");
|
|
116
|
+
}
|
|
117
|
+
return { application, position };
|
|
118
|
+
}
|
|
119
|
+
function endpoint(value, operations) {
|
|
120
|
+
const fact = object(value, "HTTP endpoint");
|
|
121
|
+
const path = array(fact.path, "HTTP endpoint path");
|
|
122
|
+
return {
|
|
123
|
+
operation: operation(fact.operation, operations),
|
|
124
|
+
method: method(fact.method, "HTTP endpoint method", true),
|
|
125
|
+
path: path.map((segment, index) => endpointSegment(segment, index === path.length - 1)),
|
|
126
|
+
...fact.order === undefined ? {} : { order: order(fact.order) }
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function request(value, operations) {
|
|
130
|
+
const fact = object(value, "HTTP request");
|
|
131
|
+
if (fact.configured !== undefined && typeof fact.configured !== "boolean")
|
|
132
|
+
fail("HTTP request configured must be a boolean");
|
|
133
|
+
return {
|
|
134
|
+
operation: operation(fact.operation, operations),
|
|
135
|
+
...fact.method === undefined ? {} : { method: method(fact.method, "HTTP request method", false) },
|
|
136
|
+
...fact.configured ? { configured: true } : {},
|
|
137
|
+
path: array(fact.path, "HTTP request path").map(requestSegment)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function httpEvidence(endpoints, requests, operations) {
|
|
141
|
+
if (endpoints === undefined && requests === undefined)
|
|
142
|
+
return {};
|
|
143
|
+
if (operations === undefined)
|
|
144
|
+
fail("HTTP facts require operation declarations");
|
|
145
|
+
return {
|
|
146
|
+
...endpoints === undefined ? {} : { httpEndpoints: array(endpoints, "httpEndpoints").map((fact) => endpoint(fact, operations)) },
|
|
147
|
+
...requests === undefined ? {} : { httpRequests: array(requests, "httpRequests").map((fact) => request(fact, operations)) }
|
|
148
|
+
};
|
|
149
|
+
}
|
|
7
150
|
|
|
8
151
|
// packages/scanner/src/index.ts
|
|
9
152
|
function compare(...values) {
|
|
@@ -48,7 +191,9 @@ function createScanObservation(input) {
|
|
|
48
191
|
scanner: input.scanner,
|
|
49
192
|
roots,
|
|
50
193
|
files,
|
|
194
|
+
...sourceUnits(input.sourceUnits, filePaths),
|
|
51
195
|
...operationEvidence(input, filePaths),
|
|
196
|
+
...httpEvidence(input.httpEndpoints, input.httpRequests, input.operations && new Set(input.operations.map((operation) => operation.id))),
|
|
52
197
|
diagnostics: [...new Map(input.diagnostics.map((diagnostic) => [
|
|
53
198
|
diagnosticKey(diagnostic),
|
|
54
199
|
diagnostic
|
|
@@ -57,6 +202,23 @@ function createScanObservation(input) {
|
|
|
57
202
|
})
|
|
58
203
|
};
|
|
59
204
|
}
|
|
205
|
+
function sourceUnits(input, paths) {
|
|
206
|
+
if (input === undefined)
|
|
207
|
+
return {};
|
|
208
|
+
const units = array(input, "sourceUnits").map((entry) => {
|
|
209
|
+
const unit = object(entry, "source unit");
|
|
210
|
+
const primary = string(unit.primary, "source unit.primary");
|
|
211
|
+
const files = [...new Set(array(unit.files, "source unit.files").map((file) => string(file, "source unit.file")))].sort();
|
|
212
|
+
if (!files.includes(primary))
|
|
213
|
+
throw new Error(`source unit omits primary file: ${primary}`);
|
|
214
|
+
for (const file of files) {
|
|
215
|
+
if (!paths.has(file))
|
|
216
|
+
throw new Error(`source unit references unknown file: ${file}`);
|
|
217
|
+
}
|
|
218
|
+
return { primary, files };
|
|
219
|
+
});
|
|
220
|
+
return { sourceUnits: [...new Map(units.map((unit) => [compare(unit.primary, ...unit.files), unit])).values()].sort((a, b) => compare(a.primary, ...a.files).localeCompare(compare(b.primary, ...b.files))) };
|
|
221
|
+
}
|
|
60
222
|
function validateRoots(input) {
|
|
61
223
|
const roots = uniqueBy(input, (root) => root.id, "root id");
|
|
62
224
|
const byId = new Map(roots.map((root) => [root.id, root]));
|
|
@@ -87,22 +249,6 @@ function diagnosticLocation(value) {
|
|
|
87
249
|
...value.line === undefined ? {} : { line: Number(value.line) }
|
|
88
250
|
};
|
|
89
251
|
}
|
|
90
|
-
function object(value, label) {
|
|
91
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
92
|
-
throw new Error(`${label} must be an object`);
|
|
93
|
-
}
|
|
94
|
-
return value;
|
|
95
|
-
}
|
|
96
|
-
function string(value, label) {
|
|
97
|
-
if (typeof value !== "string")
|
|
98
|
-
throw new Error(`${label} must be a string`);
|
|
99
|
-
return value;
|
|
100
|
-
}
|
|
101
|
-
function array(value, label) {
|
|
102
|
-
if (!Array.isArray(value))
|
|
103
|
-
throw new Error(`${label} must be an array`);
|
|
104
|
-
return value;
|
|
105
|
-
}
|
|
106
252
|
function parseScanObservation(source) {
|
|
107
253
|
const value = object(JSON.parse(source), "observation");
|
|
108
254
|
if (value.schemaVersion !== 1)
|
|
@@ -116,6 +262,9 @@ function parseScanObservation(source) {
|
|
|
116
262
|
engineVersion: string(scanner.engineVersion, "scanner.engineVersion")
|
|
117
263
|
},
|
|
118
264
|
...parseOperations(value),
|
|
265
|
+
httpEndpoints: value.httpEndpoints,
|
|
266
|
+
httpRequests: value.httpRequests,
|
|
267
|
+
...sourceUnits(value.sourceUnits, new Set(array(value.files, "files").map((entry) => string(object(entry, "file").file, "file.file")))),
|
|
119
268
|
roots: array(value.roots, "roots").map((entry) => {
|
|
120
269
|
const root = object(entry, "root");
|
|
121
270
|
return {
|
|
@@ -259,11 +408,97 @@ function sourcePosition(position) {
|
|
|
259
408
|
return { position: Number(position) };
|
|
260
409
|
}
|
|
261
410
|
|
|
411
|
+
// plugins/scanners/observations.ts
|
|
412
|
+
function relocateObservation(observation, directory) {
|
|
413
|
+
const file = (value) => path2.posix.normalize(path2.posix.join(directory, value));
|
|
414
|
+
return {
|
|
415
|
+
...observation,
|
|
416
|
+
roots: observation.roots.map((root) => ({ ...root, ...root.file ? { file: file(root.file) } : {} })),
|
|
417
|
+
files: observation.files.map((source) => ({ ...source, file: file(source.file) })),
|
|
418
|
+
...observation.sourceUnits === undefined ? {} : {
|
|
419
|
+
sourceUnits: observation.sourceUnits.map((unit) => ({ primary: file(unit.primary), files: unit.files.map(file) }))
|
|
420
|
+
},
|
|
421
|
+
operations: observation.operations?.map((operation) => ({ ...operation, file: file(operation.file) })),
|
|
422
|
+
invocations: observation.invocations?.map((call) => ({
|
|
423
|
+
...call,
|
|
424
|
+
...call.binding ? { binding: { ...call.binding, file: file(call.binding.file) } } : {}
|
|
425
|
+
})),
|
|
426
|
+
diagnostics: observation.diagnostics.map((item) => ({ ...item, ...item.file ? { file: file(item.file) } : {} }))
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function combineObservations(parts) {
|
|
430
|
+
if (!parts.length)
|
|
431
|
+
return;
|
|
432
|
+
const roots = [];
|
|
433
|
+
const files = new Map;
|
|
434
|
+
const operations = [];
|
|
435
|
+
const invocations = [];
|
|
436
|
+
const diagnostics = [];
|
|
437
|
+
const sourceUnits = [];
|
|
438
|
+
const httpEndpoints = [];
|
|
439
|
+
const httpRequests = [];
|
|
440
|
+
for (const { key, observation } of parts) {
|
|
441
|
+
const id = (value) => JSON.stringify([key, value]);
|
|
442
|
+
roots.push(...observation.roots.map((root) => ({ ...root, id: id(root.id), ...root.parent ? { parent: id(root.parent) } : {} })));
|
|
443
|
+
for (const source of observation.files) {
|
|
444
|
+
const combined = files.get(source.file) ?? { file: source.file, roots: [], symbols: [] };
|
|
445
|
+
combined.roots.push(...source.roots.map(id));
|
|
446
|
+
combined.symbols.push(...source.symbols.map((symbol) => ({ ...symbol, id: id(symbol.id) })));
|
|
447
|
+
files.set(source.file, combined);
|
|
448
|
+
}
|
|
449
|
+
operations.push(...(observation.operations ?? []).map((operation) => ({ ...operation, id: id(operation.id) })));
|
|
450
|
+
invocations.push(...(observation.invocations ?? []).map((call) => ({ ...call, source: id(call.source), targets: call.targets.map(id) })));
|
|
451
|
+
httpEndpoints.push(...(observation.httpEndpoints ?? []).map((endpoint) => ({ ...endpoint, operation: id(endpoint.operation) })));
|
|
452
|
+
httpRequests.push(...(observation.httpRequests ?? []).map((request) => ({ ...request, operation: id(request.operation) })));
|
|
453
|
+
diagnostics.push(...observation.diagnostics);
|
|
454
|
+
sourceUnits.push(...observation.sourceUnits ?? []);
|
|
455
|
+
}
|
|
456
|
+
return createScanObservation({
|
|
457
|
+
scanner: parts[0].observation.scanner,
|
|
458
|
+
roots,
|
|
459
|
+
files: [...files.values()],
|
|
460
|
+
operations,
|
|
461
|
+
invocations,
|
|
462
|
+
diagnostics,
|
|
463
|
+
...parts.some((part) => part.observation.sourceUnits !== undefined) ? { sourceUnits } : {},
|
|
464
|
+
...parts.some((part) => part.observation.httpEndpoints !== undefined) ? { httpEndpoints } : {},
|
|
465
|
+
...parts.some((part) => part.observation.httpRequests !== undefined) ? { httpRequests } : {}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// plugins/scanners/project-scanner.ts
|
|
470
|
+
function projectScanner(scanner, select) {
|
|
471
|
+
return {
|
|
472
|
+
...scanner,
|
|
473
|
+
checkReadiness: async (root, settings = {}) => {
|
|
474
|
+
const projects = await select(root, settings);
|
|
475
|
+
if (!projects.length)
|
|
476
|
+
throw new Error(`${scanner.id}: No supported project declaration was found.`);
|
|
477
|
+
for (const project of projects)
|
|
478
|
+
await scanner.checkReadiness?.(project, settings);
|
|
479
|
+
},
|
|
480
|
+
scan: async (root, settings = {}) => {
|
|
481
|
+
const parts = [];
|
|
482
|
+
for (const project of await select(root, settings)) {
|
|
483
|
+
const observation = await scanner.scan(project, settings);
|
|
484
|
+
const key = path3.relative(root, project).split(path3.sep).join("/");
|
|
485
|
+
if (observation)
|
|
486
|
+
parts.push({ key, observation: relocateObservation(observation, key) });
|
|
487
|
+
}
|
|
488
|
+
return combineObservations(parts);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
262
493
|
// plugins/scanners/go/src/adapter.ts
|
|
494
|
+
import { execFile as execFile2 } from "child_process";
|
|
495
|
+
import { access, realpath } from "fs/promises";
|
|
496
|
+
import path4 from "path";
|
|
497
|
+
import { fileURLToPath } from "url";
|
|
263
498
|
var packagedWorker = fileURLToPath(new URL(`../dist/${process.platform}-${process.arch}/worker${process.platform === "win32" ? ".exe" : ""}`, import.meta.url));
|
|
264
|
-
function run(command, args, root, env = process.env) {
|
|
499
|
+
function run(command, args, root, { env = process.env, input } = {}) {
|
|
265
500
|
return new Promise((resolve, reject) => {
|
|
266
|
-
|
|
501
|
+
const child = execFile2(command, args, {
|
|
267
502
|
cwd: root,
|
|
268
503
|
env,
|
|
269
504
|
encoding: "utf8",
|
|
@@ -276,55 +511,54 @@ function run(command, args, root, env = process.env) {
|
|
|
276
511
|
else
|
|
277
512
|
resolve(stdout);
|
|
278
513
|
});
|
|
514
|
+
if (input !== undefined)
|
|
515
|
+
child.stdin?.end(input);
|
|
279
516
|
});
|
|
280
517
|
}
|
|
281
|
-
async function
|
|
282
|
-
const root = await realpath(repositoryRoot);
|
|
518
|
+
async function workerPath(options) {
|
|
283
519
|
const worker = options.worker ?? packagedWorker;
|
|
284
520
|
try {
|
|
285
521
|
await access(worker);
|
|
286
522
|
} catch {
|
|
287
523
|
throw new Error("GO_WORKER_MISSING: Install the packaged Go scanner for this platform, or build it with bun plugins/scanners/go/build.ts.");
|
|
288
524
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
throw new Error("GO_PROJECT_SCOPE: Run Groma at the supported Go module root containing go.mod.");
|
|
301
|
-
}
|
|
302
|
-
env.PATH = `${path.join(context.GOROOT, "bin")}${path.delimiter}${process.env.PATH ?? ""}`;
|
|
303
|
-
try {
|
|
304
|
-
await run(options.go ?? "go", ["list", "-mod=readonly", "-deps", "./..."], root, env);
|
|
305
|
-
} catch (error) {
|
|
306
|
-
throw new Error(`GO_PROJECT_PREPARATION: Prepare dependencies and generated source with the project's documented build commands (go mod download for module dependencies), then retry. Scan does not download tools or modules. ${error}`);
|
|
307
|
-
}
|
|
308
|
-
return { root, worker, env };
|
|
525
|
+
return worker;
|
|
526
|
+
}
|
|
527
|
+
async function checkGoReadiness(repositoryRoot, options = {}) {
|
|
528
|
+
const root = await realpath(repositoryRoot);
|
|
529
|
+
const worker = await workerPath(options);
|
|
530
|
+
await access(path4.join(root, "go.mod"));
|
|
531
|
+
return { root, worker };
|
|
532
|
+
}
|
|
533
|
+
async function readGoCodeStructure(repositoryRoot, references, options = {}) {
|
|
534
|
+
const worker = await workerPath(options);
|
|
535
|
+
return JSON.parse(await run(worker, ["outline", repositoryRoot], repositoryRoot, { input: JSON.stringify(references) }));
|
|
309
536
|
}
|
|
310
537
|
async function scanGoSource(repositoryRoot, options = {}) {
|
|
311
|
-
const { root, worker
|
|
538
|
+
const { root, worker } = await checkGoReadiness(repositoryRoot, options);
|
|
312
539
|
try {
|
|
313
|
-
return parseScanObservation(await run(worker, [root], root
|
|
540
|
+
return parseScanObservation(await run(worker, [root], root));
|
|
314
541
|
} catch (error) {
|
|
315
|
-
throw new Error(`
|
|
542
|
+
throw new Error(`GO_SOURCE_INVALID: No observation was produced. Check Go source syntax and module declarations. ${error}`);
|
|
316
543
|
}
|
|
317
544
|
}
|
|
318
545
|
|
|
319
546
|
// plugins/scanners/go/src/index.ts
|
|
320
|
-
|
|
547
|
+
async function goSources(root) {
|
|
548
|
+
const modules = (await projectFiles(root, (file) => path5.posix.basename(file) === "go.mod")).map((file) => path5.posix.dirname(file));
|
|
549
|
+
return projectFiles(root, (file) => file.endsWith(".go") && !file.endsWith("_test.go") && !file.split("/").some((part) => part === "testdata" || part.startsWith(".")) && modules.some((module) => module === "." || file.startsWith(`${module}/`)));
|
|
550
|
+
}
|
|
551
|
+
var scanner = {
|
|
321
552
|
id: "go",
|
|
322
553
|
watch: { include: ["**/*.go", "**/go.mod", "**/go.sum", "**/go.work"], exclude: [] },
|
|
554
|
+
listSourceFiles: goSources,
|
|
323
555
|
checkReadiness: async (root) => {
|
|
324
556
|
await checkGoReadiness(root);
|
|
325
557
|
},
|
|
558
|
+
readCodeStructure: readGoCodeStructure,
|
|
326
559
|
scan: scanGoSource
|
|
327
560
|
};
|
|
561
|
+
var src_default = projectScanner(scanner, async (root) => (await projectFiles(root, (file) => path5.posix.basename(file) === "go.mod")).map((file) => path5.dirname(path5.join(root, file))));
|
|
328
562
|
export {
|
|
329
563
|
src_default as default
|
|
330
564
|
};
|