@mnemonik/shared 7.119.2 → 7.120.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/dist/codeScanner.d.ts +87 -18
- package/dist/codeScanner.d.ts.map +1 -1
- package/dist/codeScanner.js +229 -74
- package/dist/codeScanner.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/indexClassifier.d.ts +178 -0
- package/dist/indexClassifier.d.ts.map +1 -0
- package/dist/indexClassifier.js +585 -0
- package/dist/indexClassifier.js.map +1 -0
- package/dist/indexJudge.d.ts +50 -0
- package/dist/indexJudge.d.ts.map +1 -0
- package/dist/indexJudge.js +116 -0
- package/dist/indexJudge.js.map +1 -0
- package/package.json +1 -1
- package/src/codeScanner.ts +305 -81
- package/src/index.ts +34 -0
- package/src/indexClassifier.ts +681 -0
- package/src/indexJudge.ts +149 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Judge a stored or pushed path the way the daemon judged it, from the rules
|
|
3
|
+
* the daemon uploaded (`projects.scanner_ignore_patterns`).
|
|
4
|
+
*
|
|
5
|
+
* The daemon walks the disk with every rule file in front of it. The server
|
|
6
|
+
* never sees the disk; it has the root .gitignore / .mnemonikignore lines (the
|
|
7
|
+
* privacy list) plus the tagged nested-ignore and .gitattributes lines the
|
|
8
|
+
* daemon uploads beside them (`indexClassifier.ts`, "Rule upload"). This
|
|
9
|
+
* module rebuilds the same precedence from those lines so the /scan/push
|
|
10
|
+
* backstop, file_push, the dropped-path marker bookkeeping and retirement all
|
|
11
|
+
* reach the daemon's answer:
|
|
12
|
+
*
|
|
13
|
+
* hard built-in directories, secret files, the root ignore patterns
|
|
14
|
+
* (makeIgnoreMatcher - unchanged, never overridable), and a nested
|
|
15
|
+
* ignore file that ignores the path;
|
|
16
|
+
* soft classifyForIndex: `!pattern` and .gitattributes overrides, then the
|
|
17
|
+
* soft defaults - path rules always, content rules when content is
|
|
18
|
+
* supplied.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import ignore, { type Ignore } from 'ignore';
|
|
22
|
+
import { AUTHORITY_FILE_MATCHERS, makeIgnoreMatcher } from './codeScanner.js';
|
|
23
|
+
import {
|
|
24
|
+
classifyForIndex,
|
|
25
|
+
decodeIndexRules,
|
|
26
|
+
parseGitattributes,
|
|
27
|
+
softDefaultForContent,
|
|
28
|
+
type GitattributesLayer,
|
|
29
|
+
type IgnoreFileVerdict,
|
|
30
|
+
type IndexExclusionReason,
|
|
31
|
+
} from './indexClassifier.js';
|
|
32
|
+
|
|
33
|
+
export type IndexJudgement =
|
|
34
|
+
| {
|
|
35
|
+
excluded: false;
|
|
36
|
+
/**
|
|
37
|
+
* Why the soft defaults did not apply: a project rule re-included the
|
|
38
|
+
* path, or it is an authority file (`AUTHORITY_FILE_MATCHERS`) - collected
|
|
39
|
+
* whole for doc-truth, never chunked, and never judged by the defaults on
|
|
40
|
+
* the daemon either. Content rules do not run for any of them.
|
|
41
|
+
*/
|
|
42
|
+
override?: 'ignore_file' | 'gitattributes' | 'authority';
|
|
43
|
+
}
|
|
44
|
+
| { excluded: true; kind: 'hard'; rule: string }
|
|
45
|
+
| { excluded: true; kind: 'soft'; reason: IndexExclusionReason; rule: string };
|
|
46
|
+
|
|
47
|
+
export interface IndexJudge {
|
|
48
|
+
/** Hard rules and the path-only soft defaults, with the project's overrides. */
|
|
49
|
+
judgePath(relPath: string): IndexJudgement;
|
|
50
|
+
/**
|
|
51
|
+
* `judgePath`, then the content rules when `content` (decoded, scrubbed
|
|
52
|
+
* text) is supplied and no project rule re-included the path.
|
|
53
|
+
*/
|
|
54
|
+
judge(relPath: string, content?: string): IndexJudgement;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface IgnoreLayer {
|
|
58
|
+
baseRel: string;
|
|
59
|
+
ig: Ignore;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function compile(lines: readonly string[]): Ignore | null {
|
|
63
|
+
const cleaned = lines.filter((line) => typeof line === 'string' && line.trim().length > 0);
|
|
64
|
+
if (cleaned.length === 0) return null;
|
|
65
|
+
try {
|
|
66
|
+
return ignore().add([...cleaned]);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Deepest layer with an opinion wins, as CodeScanner's walk resolves it. */
|
|
73
|
+
function verdictFor(path: string, layers: readonly IgnoreLayer[]): IgnoreFileVerdict {
|
|
74
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
75
|
+
const layer = layers[i];
|
|
76
|
+
if (!layer) continue;
|
|
77
|
+
let sub: string;
|
|
78
|
+
if (layer.baseRel === '') sub = path;
|
|
79
|
+
else if (path.startsWith(`${layer.baseRel}/`)) sub = path.slice(layer.baseRel.length + 1);
|
|
80
|
+
else continue;
|
|
81
|
+
try {
|
|
82
|
+
const res = layer.ig.test(sub);
|
|
83
|
+
if (res.ignored) return 'ignored';
|
|
84
|
+
if (res.unignored) return 'unignored';
|
|
85
|
+
} catch {
|
|
86
|
+
// `ignore` throws on paths it considers invalid; such a path has no verdict.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return 'unmatched';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const depth = (baseRel: string): number => (baseRel === '' ? 0 : baseRel.split('/').length);
|
|
93
|
+
|
|
94
|
+
export function makeIndexJudge(storedPatterns: readonly string[] = []): IndexJudge {
|
|
95
|
+
const rules = decodeIndexRules(storedPatterns);
|
|
96
|
+
// The privacy net is exactly what it was: root patterns, built-in dirs, secrets.
|
|
97
|
+
const isHardIgnored = makeIgnoreMatcher(rules.rootIgnore);
|
|
98
|
+
|
|
99
|
+
const layers: IgnoreLayer[] = [];
|
|
100
|
+
const root = compile(rules.rootIgnore);
|
|
101
|
+
if (root) layers.push({ baseRel: '', ig: root });
|
|
102
|
+
const nested = [...rules.nestedIgnore.entries()].sort(([a], [b]) => depth(a) - depth(b));
|
|
103
|
+
for (const [baseRel, lines] of nested) {
|
|
104
|
+
const ig = compile(lines);
|
|
105
|
+
if (ig) layers.push({ baseRel, ig });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const attributes: GitattributesLayer[] = [...rules.attributes.entries()].map(
|
|
109
|
+
([baseRel, lines]) => ({ baseRel, rules: parseGitattributes(lines.join('\n')) })
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const judgePath = (relPath: string): IndexJudgement => {
|
|
113
|
+
const path = relPath.replace(/\\/g, '/');
|
|
114
|
+
if (!path) return { excluded: false };
|
|
115
|
+
if (isHardIgnored(path)) return { excluded: true, kind: 'hard', rule: 'ignore rule' };
|
|
116
|
+
const verdict = verdictFor(path, layers);
|
|
117
|
+
if (verdict === 'ignored') {
|
|
118
|
+
return { excluded: true, kind: 'hard', rule: 'nested ignore file' };
|
|
119
|
+
}
|
|
120
|
+
if (AUTHORITY_FILE_MATCHERS.some((matches) => matches(path))) {
|
|
121
|
+
return { excluded: false, override: 'authority' };
|
|
122
|
+
}
|
|
123
|
+
const classification = classifyForIndex(path, {
|
|
124
|
+
ignoreFileVerdict: verdict,
|
|
125
|
+
attributes,
|
|
126
|
+
});
|
|
127
|
+
if (!classification.include) {
|
|
128
|
+
return {
|
|
129
|
+
excluded: true,
|
|
130
|
+
kind: 'soft',
|
|
131
|
+
reason: classification.reason,
|
|
132
|
+
rule: classification.rule,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return classification.override
|
|
136
|
+
? { excluded: false, override: classification.override }
|
|
137
|
+
: { excluded: false };
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
judgePath,
|
|
142
|
+
judge(relPath: string, content?: string): IndexJudgement {
|
|
143
|
+
const byPath = judgePath(relPath);
|
|
144
|
+
if (byPath.excluded || byPath.override || content === undefined) return byPath;
|
|
145
|
+
const byContent = softDefaultForContent(relPath, content);
|
|
146
|
+
return byContent ? { excluded: true, kind: 'soft', ...byContent } : byPath;
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|