@untangle-code/scanner 0.0.0-stage → 0.1.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/LICENSE +661 -0
- package/NOTICE +90 -0
- package/README.md +303 -2
- package/THIRD_PARTY_LICENSES.md +24 -0
- package/dist/binary-classifier.d.ts +45 -0
- package/dist/binary-classifier.d.ts.map +1 -0
- package/dist/binary-classifier.js +296 -0
- package/dist/binary-classifier.js.map +1 -0
- package/dist/concurrency.d.ts +8 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +64 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/discovery.d.ts +9 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +254 -0
- package/dist/discovery.js.map +1 -0
- package/dist/gitignore-pattern.d.ts +35 -0
- package/dist/gitignore-pattern.d.ts.map +1 -0
- package/dist/gitignore-pattern.js +155 -0
- package/dist/gitignore-pattern.js.map +1 -0
- package/dist/hashing.d.ts +14 -0
- package/dist/hashing.d.ts.map +1 -0
- package/dist/hashing.js +32 -0
- package/dist/hashing.js.map +1 -0
- package/dist/ignore-engine.d.ts +27 -0
- package/dist/ignore-engine.d.ts.map +1 -0
- package/dist/ignore-engine.js +130 -0
- package/dist/ignore-engine.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/language-classifier.d.ts +19 -0
- package/dist/language-classifier.d.ts.map +1 -0
- package/dist/language-classifier.js +114 -0
- package/dist/language-classifier.js.map +1 -0
- package/dist/node-fs/index.d.ts +10 -0
- package/dist/node-fs/index.d.ts.map +1 -0
- package/dist/node-fs/index.js +10 -0
- package/dist/node-fs/index.js.map +1 -0
- package/dist/node-fs/node-file-system.d.ts +19 -0
- package/dist/node-fs/node-file-system.d.ts.map +1 -0
- package/dist/node-fs/node-file-system.js +104 -0
- package/dist/node-fs/node-file-system.js.map +1 -0
- package/dist/node-fs/node-streaming-hasher.d.ts +4 -0
- package/dist/node-fs/node-streaming-hasher.d.ts.map +1 -0
- package/dist/node-fs/node-streaming-hasher.js +33 -0
- package/dist/node-fs/node-streaming-hasher.js.map +1 -0
- package/dist/port-options.d.ts +8 -0
- package/dist/port-options.d.ts.map +1 -0
- package/dist/port-options.js +4 -0
- package/dist/port-options.js.map +1 -0
- package/dist/semaphore.d.ts +18 -0
- package/dist/semaphore.d.ts.map +1 -0
- package/dist/semaphore.js +42 -0
- package/dist/semaphore.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -3
- package/src/binary-classifier.ts +365 -0
- package/src/concurrency.ts +78 -0
- package/src/discovery.ts +362 -0
- package/src/gitignore-pattern.ts +170 -0
- package/src/hashing.ts +37 -0
- package/src/ignore-engine.ts +157 -0
- package/src/index.ts +48 -0
- package/src/language-classifier.ts +137 -0
- package/src/node-fs/index.ts +9 -0
- package/src/node-fs/node-file-system.ts +137 -0
- package/src/node-fs/node-streaming-hasher.ts +35 -0
- package/src/port-options.ts +10 -0
- package/src/semaphore.ts +45 -0
- package/src/types.ts +121 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// MVP + Public Beta languages (spec.md §7.2) plus common structural/text
|
|
2
|
+
// formats the scanner needs to tell apart from opaque binaries.
|
|
3
|
+
const EXTENSION_LANGUAGE_MAP = {
|
|
4
|
+
ts: "typescript",
|
|
5
|
+
mts: "typescript",
|
|
6
|
+
cts: "typescript",
|
|
7
|
+
tsx: "tsx",
|
|
8
|
+
js: "javascript",
|
|
9
|
+
mjs: "javascript",
|
|
10
|
+
cjs: "javascript",
|
|
11
|
+
jsx: "jsx",
|
|
12
|
+
py: "python",
|
|
13
|
+
pyi: "python",
|
|
14
|
+
pyw: "python",
|
|
15
|
+
go: "go",
|
|
16
|
+
java: "java",
|
|
17
|
+
kt: "kotlin",
|
|
18
|
+
kts: "kotlin",
|
|
19
|
+
cs: "csharp",
|
|
20
|
+
rs: "rust",
|
|
21
|
+
php: "php",
|
|
22
|
+
phtml: "php",
|
|
23
|
+
rb: "ruby",
|
|
24
|
+
// WP-86: die Parser-Registry fuehrt diese Endungen als Ruby beziehungsweise
|
|
25
|
+
// PHP und Swift. Fehlten sie hier, meldete der Scanner "unknown" fuer eine
|
|
26
|
+
// Datei, die der Adapter danach doch tief analysiert — zwei Aussagen ueber
|
|
27
|
+
// dieselbe Datei, von denen eine falsch ist.
|
|
28
|
+
rake: "ruby",
|
|
29
|
+
gemspec: "ruby",
|
|
30
|
+
swift: "swift",
|
|
31
|
+
c: "c",
|
|
32
|
+
h: "c",
|
|
33
|
+
cpp: "cpp",
|
|
34
|
+
cc: "cpp",
|
|
35
|
+
cxx: "cpp",
|
|
36
|
+
hpp: "cpp",
|
|
37
|
+
hh: "cpp",
|
|
38
|
+
hxx: "cpp",
|
|
39
|
+
json: "json",
|
|
40
|
+
jsonc: "json",
|
|
41
|
+
yaml: "yaml",
|
|
42
|
+
yml: "yaml",
|
|
43
|
+
toml: "toml",
|
|
44
|
+
md: "markdown",
|
|
45
|
+
mdx: "markdown",
|
|
46
|
+
css: "css",
|
|
47
|
+
scss: "scss",
|
|
48
|
+
less: "less",
|
|
49
|
+
html: "html",
|
|
50
|
+
htm: "html",
|
|
51
|
+
vue: "vue",
|
|
52
|
+
svelte: "svelte",
|
|
53
|
+
sh: "shell",
|
|
54
|
+
bash: "shell",
|
|
55
|
+
zsh: "shell",
|
|
56
|
+
ksh: "shell",
|
|
57
|
+
sql: "sql",
|
|
58
|
+
graphql: "graphql",
|
|
59
|
+
gql: "graphql",
|
|
60
|
+
proto: "protobuf",
|
|
61
|
+
xml: "xml",
|
|
62
|
+
};
|
|
63
|
+
/** Extension-based language guess; `undefined` for extensionless or unknown extensions. */
|
|
64
|
+
export function detectLanguageByExtension(relativePath) {
|
|
65
|
+
const base = relativePath.slice(relativePath.lastIndexOf("/") + 1);
|
|
66
|
+
const dot = base.lastIndexOf(".");
|
|
67
|
+
// dot <= 0 covers "no extension" and dotfiles like ".gitignore".
|
|
68
|
+
if (dot <= 0) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const ext = base.slice(dot + 1).toLowerCase();
|
|
72
|
+
return EXTENSION_LANGUAGE_MAP[ext];
|
|
73
|
+
}
|
|
74
|
+
const SHEBANG_MARKERS = [
|
|
75
|
+
{ pattern: /^#!.*\bnode\b/, languageId: "javascript" },
|
|
76
|
+
{ pattern: /^#!.*\bdeno\b/, languageId: "typescript" },
|
|
77
|
+
{ pattern: /^#!.*\bpython[0-9.]*\b/, languageId: "python" },
|
|
78
|
+
{ pattern: /^#!.*\b(?:bash|sh|zsh|dash|ksh)\b/, languageId: "shell" },
|
|
79
|
+
{ pattern: /^#!.*\bruby\b/, languageId: "ruby" },
|
|
80
|
+
{ pattern: /^#!.*\bperl\b/, languageId: "perl" },
|
|
81
|
+
];
|
|
82
|
+
const CONTENT_MARKERS = [
|
|
83
|
+
{ pattern: /^<\?php/, languageId: "php" },
|
|
84
|
+
{ pattern: /^<\?xml/, languageId: "xml" },
|
|
85
|
+
{ pattern: /^\s*<!DOCTYPE html/i, languageId: "html" },
|
|
86
|
+
];
|
|
87
|
+
/** Content-based language guess for extensionless files (shebang or leading markers). */
|
|
88
|
+
export function detectLanguageFromContent(text) {
|
|
89
|
+
const head = text.slice(0, 256);
|
|
90
|
+
for (const marker of SHEBANG_MARKERS) {
|
|
91
|
+
if (marker.pattern.test(head)) {
|
|
92
|
+
return marker.languageId;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const marker of CONTENT_MARKERS) {
|
|
96
|
+
if (marker.pattern.test(head)) {
|
|
97
|
+
return marker.languageId;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
/** Combines extension and content sniffing; `languageId` is "unknown" if neither matches. */
|
|
103
|
+
export function detectLanguage(relativePath, sampleText) {
|
|
104
|
+
const byExtension = detectLanguageByExtension(relativePath);
|
|
105
|
+
if (byExtension !== undefined) {
|
|
106
|
+
return { languageId: byExtension, detectedBy: "extension" };
|
|
107
|
+
}
|
|
108
|
+
const byContent = detectLanguageFromContent(sampleText);
|
|
109
|
+
if (byContent !== undefined) {
|
|
110
|
+
return { languageId: byContent, detectedBy: "content" };
|
|
111
|
+
}
|
|
112
|
+
return { languageId: "unknown", detectedBy: "unknown" };
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=language-classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language-classifier.js","sourceRoot":"","sources":["../src/language-classifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@untangle-code/scanner/node-fs` — the Node.js-specific half of the
|
|
3
|
+
* scanner: a `FileSystemPort` adapter and a genuinely streaming
|
|
4
|
+
* `ContentHasher`. Kept out of the package's main entry point so that
|
|
5
|
+
* entry point stays testable against any `FileSystemPort` (e.g. an
|
|
6
|
+
* in-memory one) without pulling in `node:fs` (see README, "Entscheidungen").
|
|
7
|
+
*/
|
|
8
|
+
export { NodeFileSystem } from "./node-file-system.js";
|
|
9
|
+
export { createNodeStreamingHasher } from "./node-streaming-hasher.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node-fs/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@untangle-code/scanner/node-fs` — the Node.js-specific half of the
|
|
3
|
+
* scanner: a `FileSystemPort` adapter and a genuinely streaming
|
|
4
|
+
* `ContentHasher`. Kept out of the package's main entry point so that
|
|
5
|
+
* entry point stays testable against any `FileSystemPort` (e.g. an
|
|
6
|
+
* in-memory one) without pulling in `node:fs` (see README, "Entscheidungen").
|
|
7
|
+
*/
|
|
8
|
+
export { NodeFileSystem } from "./node-file-system.js";
|
|
9
|
+
export { createNodeStreamingHasher } from "./node-streaming-hasher.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node-fs/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DirEntryV1, FileStatV1, FileSystemPort, PortCallOptions } from "@untangle-code/domain";
|
|
2
|
+
/** Production `FileSystemPort` adapter backed by `node:fs/promises`. */
|
|
3
|
+
export declare class NodeFileSystem implements FileSystemPort {
|
|
4
|
+
stat(path: string, options?: PortCallOptions): Promise<FileStatV1 | null>;
|
|
5
|
+
lstat(path: string, options?: PortCallOptions): Promise<FileStatV1 | null>;
|
|
6
|
+
readFile(path: string, options?: PortCallOptions): Promise<Uint8Array>;
|
|
7
|
+
readTextFile(path: string, options?: PortCallOptions): Promise<string>;
|
|
8
|
+
readDir(path: string, options?: PortCallOptions): Promise<DirEntryV1[]>;
|
|
9
|
+
realPath(path: string, options?: PortCallOptions): Promise<string>;
|
|
10
|
+
writeFile(path: string, data: Uint8Array, options?: PortCallOptions): Promise<void>;
|
|
11
|
+
mkdir(path: string, options?: PortCallOptions & {
|
|
12
|
+
recursive?: boolean;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
rename(from: string, to: string, options?: PortCallOptions): Promise<void>;
|
|
15
|
+
remove(path: string, options?: PortCallOptions & {
|
|
16
|
+
recursive?: boolean;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=node-file-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-file-system.d.ts","sourceRoot":"","sources":["../../src/node-fs/node-file-system.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,eAAe,EAChB,MAAM,uBAAuB,CAAC;AAyC/B,wEAAwE;AACxE,qBAAa,cAAe,YAAW,cAAc;IACtC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAazE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAa1E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IAKtE,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAItE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAevE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAKlE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAInF,KAAK,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAClD,OAAO,CAAC,IAAI,CAAC;IAKH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1E,MAAM,CACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAClD,OAAO,CAAC,IAAI,CAAC;CAIjB"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js implementation of `FileSystemPort` (domain). This is the only
|
|
3
|
+
* file in the package allowed to import `node:fs` — the core (`../index.ts`)
|
|
4
|
+
* stays testable against any port implementation, this adapter is what
|
|
5
|
+
* production code actually wires up.
|
|
6
|
+
*/
|
|
7
|
+
import * as nodeFs from "node:fs/promises";
|
|
8
|
+
import * as nodePath from "node:path";
|
|
9
|
+
function kindOf(stats) {
|
|
10
|
+
if (stats.isSymbolicLink()) {
|
|
11
|
+
return "symlink";
|
|
12
|
+
}
|
|
13
|
+
if (stats.isDirectory()) {
|
|
14
|
+
return "directory";
|
|
15
|
+
}
|
|
16
|
+
if (stats.isFile()) {
|
|
17
|
+
return "file";
|
|
18
|
+
}
|
|
19
|
+
return "other";
|
|
20
|
+
}
|
|
21
|
+
function toFileStat(path, stats) {
|
|
22
|
+
return {
|
|
23
|
+
path,
|
|
24
|
+
sizeBytes: stats.size,
|
|
25
|
+
mtimeEpochMs: Math.trunc(stats.mtimeMs),
|
|
26
|
+
kind: kindOf(stats),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function isNotFoundError(error) {
|
|
30
|
+
return (typeof error === "object" &&
|
|
31
|
+
error !== null &&
|
|
32
|
+
"code" in error &&
|
|
33
|
+
error.code === "ENOENT");
|
|
34
|
+
}
|
|
35
|
+
/** Production `FileSystemPort` adapter backed by `node:fs/promises`. */
|
|
36
|
+
export class NodeFileSystem {
|
|
37
|
+
async stat(path, options) {
|
|
38
|
+
options?.signal?.throwIfAborted();
|
|
39
|
+
try {
|
|
40
|
+
const stats = await nodeFs.stat(path);
|
|
41
|
+
return toFileStat(path, stats);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (isNotFoundError(error)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async lstat(path, options) {
|
|
51
|
+
options?.signal?.throwIfAborted();
|
|
52
|
+
try {
|
|
53
|
+
const stats = await nodeFs.lstat(path);
|
|
54
|
+
return toFileStat(path, stats);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (isNotFoundError(error)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async readFile(path, options) {
|
|
64
|
+
const buffer = await nodeFs.readFile(path, { signal: options?.signal });
|
|
65
|
+
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
66
|
+
}
|
|
67
|
+
async readTextFile(path, options) {
|
|
68
|
+
return await nodeFs.readFile(path, { encoding: "utf8", signal: options?.signal });
|
|
69
|
+
}
|
|
70
|
+
async readDir(path, options) {
|
|
71
|
+
options?.signal?.throwIfAborted();
|
|
72
|
+
const entries = await nodeFs.readdir(path, { withFileTypes: true });
|
|
73
|
+
return entries.map((entry) => {
|
|
74
|
+
const kind = entry.isSymbolicLink()
|
|
75
|
+
? "symlink"
|
|
76
|
+
: entry.isDirectory()
|
|
77
|
+
? "directory"
|
|
78
|
+
: entry.isFile()
|
|
79
|
+
? "file"
|
|
80
|
+
: "other";
|
|
81
|
+
return { name: entry.name, path: nodePath.join(path, entry.name), kind };
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async realPath(path, options) {
|
|
85
|
+
options?.signal?.throwIfAborted();
|
|
86
|
+
return await nodeFs.realpath(path);
|
|
87
|
+
}
|
|
88
|
+
async writeFile(path, data, options) {
|
|
89
|
+
await nodeFs.writeFile(path, data, { signal: options?.signal });
|
|
90
|
+
}
|
|
91
|
+
async mkdir(path, options) {
|
|
92
|
+
options?.signal?.throwIfAborted();
|
|
93
|
+
await nodeFs.mkdir(path, { recursive: options?.recursive ?? false });
|
|
94
|
+
}
|
|
95
|
+
async rename(from, to, options) {
|
|
96
|
+
options?.signal?.throwIfAborted();
|
|
97
|
+
await nodeFs.rename(from, to);
|
|
98
|
+
}
|
|
99
|
+
async remove(path, options) {
|
|
100
|
+
options?.signal?.throwIfAborted();
|
|
101
|
+
await nodeFs.rm(path, { recursive: options?.recursive ?? false, force: true });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=node-file-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-file-system.js","sourceRoot":"","sources":["../../src/node-fs/node-file-system.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-streaming-hasher.d.ts","sourceRoot":"","sources":["../../src/node-fs/node-streaming-hasher.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,2EAA2E;AAC3E,wBAAgB,yBAAyB,IAAI,aAAa,CAkBzD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming SHA-256 content hasher for the Node adapter.
|
|
3
|
+
*
|
|
4
|
+
* `FileSystemPort.readFile` (domain) only exposes whole-buffer reads, so
|
|
5
|
+
* the port-based default hasher (`createPortContentHasher`, in the pure
|
|
6
|
+
* core) must materialize a file's bytes to hash them. This implementation
|
|
7
|
+
* hashes straight off `fs.createReadStream` instead, without ever holding
|
|
8
|
+
* the whole file in memory — the genuine "streaming hashing for large
|
|
9
|
+
* files" the WP-05 order asks for (see README, "Entscheidungen").
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { createReadStream } from "node:fs";
|
|
13
|
+
/** `ContentHasher` that streams the file instead of buffering it whole. */
|
|
14
|
+
export function createNodeStreamingHasher() {
|
|
15
|
+
return {
|
|
16
|
+
hashFile(absolutePath, _sizeBytes, options) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const hash = createHash("sha256");
|
|
19
|
+
const stream = createReadStream(absolutePath, { signal: options?.signal });
|
|
20
|
+
stream.on("data", (chunk) => {
|
|
21
|
+
hash.update(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
22
|
+
});
|
|
23
|
+
stream.on("error", (error) => {
|
|
24
|
+
reject(error);
|
|
25
|
+
});
|
|
26
|
+
stream.on("end", () => {
|
|
27
|
+
resolve(hash.digest("hex"));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=node-streaming-hasher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-streaming-hasher.js","sourceRoot":"","sources":["../../src/node-fs/node-streaming-hasher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAI3C,2EAA2E;AAC3E,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,OAAO;YACxC,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;gBAClC,MAAM,MAAM,GAAG,gBAAgB,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC3E,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE;oBAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBACtE,CAAC,CAAC,CAAC;gBACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC3B,MAAM,CAAC,KAAK,CAAC,CAAC;gBAChB,CAAC,CAAC,CAAC;gBACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;oBACpB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC9B,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a `PortCallOptions` from an optional `AbortSignal` without ever
|
|
3
|
+
* setting `signal: undefined` explicitly — required because domain's
|
|
4
|
+
* `PortCallOptions` is checked under `exactOptionalPropertyTypes`.
|
|
5
|
+
*/
|
|
6
|
+
import type { PortCallOptions } from "@untangle-code/domain";
|
|
7
|
+
export declare function portOptions(signal: AbortSignal | undefined): PortCallOptions | undefined;
|
|
8
|
+
//# sourceMappingURL=port-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-options.d.ts","sourceRoot":"","sources":["../src/port-options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CAExF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-options.js","sourceRoot":"","sources":["../src/port-options.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,WAAW,CAAC,MAA+B;IACzD,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal counting semaphore used to bound I/O parallelism during
|
|
3
|
+
* discovery (spec.md §6.1; architecture.md §13.1 "bounded queues").
|
|
4
|
+
*
|
|
5
|
+
* Deliberately hand-rolled per WP-05 order ("ein einfacher, eigenen
|
|
6
|
+
* Semaphor/Pool") instead of pulling in a scheduling dependency.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Semaphore {
|
|
9
|
+
private available;
|
|
10
|
+
private readonly waiters;
|
|
11
|
+
constructor(concurrency: number);
|
|
12
|
+
/** Resolves once a slot is free; returns the matching release function. */
|
|
13
|
+
acquire(): Promise<() => void>;
|
|
14
|
+
private release;
|
|
15
|
+
/** Number of currently free slots — exposed for tests only. */
|
|
16
|
+
get availableCount(): number;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=semaphore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semaphore.d.ts","sourceRoot":"","sources":["../src/semaphore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;gBAE9B,WAAW,EAAE,MAAM;IAOtC,2EAA2E;IACpE,OAAO,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;IAarC,OAAO,CAAC,OAAO;IAQf,+DAA+D;IAC/D,IAAW,cAAc,IAAI,MAAM,CAElC;CACF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal counting semaphore used to bound I/O parallelism during
|
|
3
|
+
* discovery (spec.md §6.1; architecture.md §13.1 "bounded queues").
|
|
4
|
+
*
|
|
5
|
+
* Deliberately hand-rolled per WP-05 order ("ein einfacher, eigenen
|
|
6
|
+
* Semaphor/Pool") instead of pulling in a scheduling dependency.
|
|
7
|
+
*/
|
|
8
|
+
export class Semaphore {
|
|
9
|
+
available;
|
|
10
|
+
waiters = [];
|
|
11
|
+
constructor(concurrency) {
|
|
12
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
13
|
+
throw new RangeError(`Semaphore concurrency must be a positive integer, got ${concurrency}`);
|
|
14
|
+
}
|
|
15
|
+
this.available = concurrency;
|
|
16
|
+
}
|
|
17
|
+
/** Resolves once a slot is free; returns the matching release function. */
|
|
18
|
+
acquire() {
|
|
19
|
+
if (this.available > 0) {
|
|
20
|
+
this.available -= 1;
|
|
21
|
+
return Promise.resolve(() => this.release());
|
|
22
|
+
}
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
this.waiters.push(() => {
|
|
25
|
+
this.available -= 1;
|
|
26
|
+
resolve(() => this.release());
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
release() {
|
|
31
|
+
this.available += 1;
|
|
32
|
+
const next = this.waiters.shift();
|
|
33
|
+
if (next !== undefined) {
|
|
34
|
+
next();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Number of currently free slots — exposed for tests only. */
|
|
38
|
+
get availableCount() {
|
|
39
|
+
return this.available;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=semaphore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semaphore.js","sourceRoot":"","sources":["../src/semaphore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,OAAO,SAAS;IACZ,SAAS,CAAS;IACT,OAAO,GAAsB,EAAE,CAAC;IAEjD,YAAmB,WAAmB;QACpC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,UAAU,CAAC,yDAAyD,WAAW,EAAE,CAAC,CAAC;QAC/F,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC;IAC/B,CAAC;IAED,2EAA2E;IACpE,OAAO;QACZ,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;YACpB,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,IAAI,OAAO,CAAa,CAAC,OAAO,EAAE,EAAE;YACzC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;gBACrB,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;gBACpB,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAChC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,IAAI,EAAE,CAAC;QACT,CAAC;IACH,CAAC;IAED,+DAA+D;IAC/D,IAAW,cAAc;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;CACF"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scanner-local contracts (spec.md §6.1–§6.3, §12.1; architecture.md §6.4).
|
|
3
|
+
*
|
|
4
|
+
* `ScannedFileV1` is a WP-05-local type, not an import from
|
|
5
|
+
* `@untangle-code/domain` — see README, "Entscheidungen" (Entscheidung 1)
|
|
6
|
+
* for the full reasoning. In short: docs/BUILD_PLAN.md §3's Contract-Register
|
|
7
|
+
* (the authoritative, exhaustive list of what MUST live in domain) requires
|
|
8
|
+
* `FileManifestV1` for a file's tracked analysis state but does not register
|
|
9
|
+
* a raw pre-analysis scan output shape at all; the name deliberately avoids
|
|
10
|
+
* "FileSnapshot" (architecture.md §6.1's informal domain-entity name for
|
|
11
|
+
* this concept) so it cannot be mistaken for that domain contract by name
|
|
12
|
+
* alone. Downstream packages (parser-registry, chunker) consume it
|
|
13
|
+
* positionally, not by importing domain types for it.
|
|
14
|
+
*/
|
|
15
|
+
import type { FileSystemPort, PortCallOptions } from "@untangle-code/domain";
|
|
16
|
+
/** Coarse text-encoding guess from a byte sample (BOM + byte-range heuristics). */
|
|
17
|
+
export type TextEncodingGuess = "utf8" | "utf16le" | "utf16be" | "ascii" | "unknown";
|
|
18
|
+
/** How a file's language id was determined. */
|
|
19
|
+
export type LanguageDetectionSource = "extension" | "content" | "unknown";
|
|
20
|
+
/** Why a discovered path was excluded from the yielded candidate stream. */
|
|
21
|
+
export type ExclusionReason = "security" | "config-exclude" | "untangleignore" | "gitignore" | "default-exclude" | "binary" | "size-limit" | "symlink-cycle" | "symlink-escape" | "unreadable";
|
|
22
|
+
/**
|
|
23
|
+
* One file discovered and accepted for downstream analysis (spec.md §12.1
|
|
24
|
+
* comparison fields: path, size, mtime as hint, hash as source of truth).
|
|
25
|
+
*/
|
|
26
|
+
export interface ScannedFileV1 {
|
|
27
|
+
/** "/"-normalized path relative to the scanned repository root. */
|
|
28
|
+
relativePath: string;
|
|
29
|
+
/** Absolute path to read content from (post symlink resolution). */
|
|
30
|
+
absolutePath: string;
|
|
31
|
+
sizeBytes: number;
|
|
32
|
+
/** Fast change hint only — never authoritative (spec.md §12.1). */
|
|
33
|
+
mtimeEpochMs: number;
|
|
34
|
+
/** SHA-256 hex of the raw file content; sole "unchanged" authority. */
|
|
35
|
+
contentHash: string;
|
|
36
|
+
languageId: string;
|
|
37
|
+
languageDetectedBy: LanguageDetectionSource;
|
|
38
|
+
isMinified: boolean;
|
|
39
|
+
encoding: TextEncodingGuess;
|
|
40
|
+
hasBom: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** One path the scanner decided not to yield, with its reason. */
|
|
43
|
+
export interface ScanSkipV1 {
|
|
44
|
+
relativePath: string;
|
|
45
|
+
reason: ExclusionReason;
|
|
46
|
+
detail?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Pluggable content hasher (spec.md §12.1 hash pipeline).
|
|
50
|
+
*
|
|
51
|
+
* `FileSystemPort` (domain) exposes only whole-buffer reads, so the default,
|
|
52
|
+
* port-based implementation cannot avoid materializing a file's bytes. A
|
|
53
|
+
* Node-specific streaming implementation is provided from `./node-fs` for
|
|
54
|
+
* callers that want to hash large files without buffering them fully — see
|
|
55
|
+
* README, "Entscheidungen".
|
|
56
|
+
*/
|
|
57
|
+
export interface ContentHasher {
|
|
58
|
+
hashFile(absolutePath: string, sizeBytes: number, options?: PortCallOptions): Promise<string>;
|
|
59
|
+
}
|
|
60
|
+
export interface IgnoreConfig {
|
|
61
|
+
/** RepositoryConfig.include (spec.md §4.3) — overrides default excludes only. */
|
|
62
|
+
configInclude?: readonly string[];
|
|
63
|
+
/** RepositoryConfig.exclude (spec.md §4.3) — layer 2, spec.md §6.2. */
|
|
64
|
+
configExclude?: readonly string[];
|
|
65
|
+
/** File name searched for at every directory level. Default ".untangleignore". */
|
|
66
|
+
untangleIgnoreFileName?: string;
|
|
67
|
+
/** File name searched for at every directory level. Default ".gitignore". */
|
|
68
|
+
gitIgnoreFileName?: string;
|
|
69
|
+
/** Extra patterns appended to the overridable default-exclude layer. */
|
|
70
|
+
additionalDefaultExcludes?: readonly string[];
|
|
71
|
+
}
|
|
72
|
+
export interface IgnoreDecision {
|
|
73
|
+
ignored: boolean;
|
|
74
|
+
reason?: ExclusionReason;
|
|
75
|
+
}
|
|
76
|
+
export interface BinaryClassification {
|
|
77
|
+
isBinary: boolean;
|
|
78
|
+
hasNullByte: boolean;
|
|
79
|
+
nonPrintableRatio: number;
|
|
80
|
+
encoding: TextEncodingGuess;
|
|
81
|
+
hasBom: boolean;
|
|
82
|
+
}
|
|
83
|
+
export interface ScanOptions {
|
|
84
|
+
fs: FileSystemPort;
|
|
85
|
+
/** Workspace roots for `assertWithinWorkspace` (architecture.md §23.1). */
|
|
86
|
+
roots: readonly string[];
|
|
87
|
+
/** Absolute path to the repository directory to scan. */
|
|
88
|
+
repoRoot: string;
|
|
89
|
+
ignore?: IgnoreConfig;
|
|
90
|
+
/** Reject before a full read via `stat` (spec.md §6.1). Default 5 MiB. */
|
|
91
|
+
maxFileSizeBytes?: number;
|
|
92
|
+
/** Bytes inspected for binary/minified/language-content classification. Default 8 KiB. */
|
|
93
|
+
sampleBytes?: number;
|
|
94
|
+
/** Bounded parallelism for the per-file processing stage. Default 8. */
|
|
95
|
+
concurrency?: number;
|
|
96
|
+
/** Non-printable-byte ratio above which a sample is classified binary. Default 0.3. */
|
|
97
|
+
nonPrintableThreshold?: number;
|
|
98
|
+
hasher?: ContentHasher;
|
|
99
|
+
onSkip?: (skip: ScanSkipV1) => void;
|
|
100
|
+
signal?: AbortSignal;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@untangle-code/scanner",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Untangle scanner: recursive file discovery, ignore rules, symlink safety, binary/language classification and the content-hash pipeline.",
|
|
7
|
+
"license": "AGPL-3.0-only",
|
|
8
|
+
"homepage": "https://github.com/lboel/untangle",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/lboel/untangle/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/lboel/untangle.git",
|
|
15
|
+
"directory": "packages/scanner"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": "^22.13.0 || >=24.0.0"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./node-fs": {
|
|
29
|
+
"types": "./dist/node-fs/index.d.ts",
|
|
30
|
+
"default": "./dist/node-fs/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"!dist/**/*.test.*",
|
|
36
|
+
"!dist/**/test-helpers.*",
|
|
37
|
+
"!dist/**/test-support.*",
|
|
38
|
+
"!dist/**/test-support/**",
|
|
39
|
+
"!dist/**/testing/**",
|
|
40
|
+
"!dist/**/__tests__/**",
|
|
41
|
+
"src",
|
|
42
|
+
"!src/**/*.test.ts",
|
|
43
|
+
"!src/**/test-helpers.ts",
|
|
44
|
+
"!src/**/test-support.ts",
|
|
45
|
+
"!src/**/test-support/**",
|
|
46
|
+
"!src/**/testing/**",
|
|
47
|
+
"LICENSE",
|
|
48
|
+
"NOTICE",
|
|
49
|
+
"README.md",
|
|
50
|
+
"THIRD_PARTY_LICENSES.md"
|
|
51
|
+
],
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@untangle-code/domain": "0.1.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "22.19.7",
|
|
57
|
+
"typescript": "5.9.3",
|
|
58
|
+
"vitest": "3.2.7"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "node ../../scripts/tsc-build.mjs tsconfig.json",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
65
|
+
}
|
|
6
66
|
}
|