@vibe-agent-toolkit/utils 0.2.0-rc.1 → 0.2.0-rc.10
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/README.md +69 -23
- package/dist/asset-reference.js +3 -11
- package/dist/asset-reference.js.map +1 -1
- package/dist/compare-code-units.d.ts +28 -0
- package/dist/compare-code-units.d.ts.map +1 -0
- package/dist/compare-code-units.js +33 -0
- package/dist/compare-code-units.js.map +1 -0
- package/dist/crawl-timing.d.ts +657 -0
- package/dist/crawl-timing.d.ts.map +1 -0
- package/dist/crawl-timing.js +765 -0
- package/dist/crawl-timing.js.map +1 -0
- package/dist/crawl.d.ts +2 -4
- package/dist/crawl.d.ts.map +1 -1
- package/dist/crawl.js +2 -4
- package/dist/crawl.js.map +1 -1
- package/dist/dirent-kind.d.ts +83 -0
- package/dist/dirent-kind.d.ts.map +1 -0
- package/dist/dirent-kind.js +129 -0
- package/dist/dirent-kind.js.map +1 -0
- package/dist/entrypoint.d.ts +54 -0
- package/dist/entrypoint.d.ts.map +1 -0
- package/dist/entrypoint.js +71 -0
- package/dist/entrypoint.js.map +1 -0
- package/dist/env-flag.d.ts +51 -0
- package/dist/env-flag.d.ts.map +1 -0
- package/dist/env-flag.js +62 -0
- package/dist/env-flag.js.map +1 -0
- package/dist/errors/errno.d.ts +51 -0
- package/dist/errors/errno.d.ts.map +1 -0
- package/dist/errors/errno.js +112 -0
- package/dist/errors/errno.js.map +1 -0
- package/dist/errors/vat-error.d.ts +60 -0
- package/dist/errors/vat-error.d.ts.map +1 -0
- package/dist/errors/vat-error.js +86 -0
- package/dist/errors/vat-error.js.map +1 -0
- package/dist/file-crawler.d.ts +60 -0
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +133 -29
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs/file-hash.d.ts.map +1 -1
- package/dist/fs/file-hash.js +0 -1
- package/dist/fs/file-hash.js.map +1 -1
- package/dist/fs-utils.d.ts +422 -191
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +534 -204
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +5 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +9 -7
- package/dist/fs.js.map +1 -1
- package/dist/git-run.d.ts +167 -0
- package/dist/git-run.d.ts.map +1 -0
- package/dist/git-run.js +154 -0
- package/dist/git-run.js.map +1 -0
- package/dist/git-snapshot.d.ts +199 -0
- package/dist/git-snapshot.d.ts.map +1 -0
- package/dist/git-snapshot.js +250 -0
- package/dist/git-snapshot.js.map +1 -0
- package/dist/git-tracker.d.ts +166 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +250 -22
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts +84 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +208 -82
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +13 -4
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +12 -4
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +15 -6
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/glob/glob-pattern.js +5 -5
- package/dist/glob/glob-pattern.js.map +1 -1
- package/dist/index.d.ts +32 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -50
- package/dist/index.js.map +1 -1
- package/dist/listing-refusal.d.ts +112 -0
- package/dist/listing-refusal.d.ts.map +1 -0
- package/dist/listing-refusal.js +105 -0
- package/dist/listing-refusal.js.map +1 -0
- package/dist/path-containment.d.ts +64 -0
- package/dist/path-containment.d.ts.map +1 -0
- package/dist/path-containment.js +111 -0
- package/dist/path-containment.js.map +1 -0
- package/dist/path-core.d.ts +113 -18
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +139 -23
- package/dist/path-core.js.map +1 -1
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +42 -47
- package/dist/path-utils.js.map +1 -1
- package/dist/path.d.ts +1 -1
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +1 -1
- package/dist/path.js.map +1 -1
- package/dist/process.d.ts +7 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +7 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +15 -9
- package/dist/project-utils.js.map +1 -1
- package/dist/project.d.ts +7 -5
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -5
- package/dist/project.js.map +1 -1
- package/dist/safe-exec.d.ts +40 -11
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +111 -25
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts +2 -2
- package/dist/skill-targets.js +2 -2
- package/dist/skill-test/auth-resolver.d.ts +10 -2
- package/dist/skill-test/auth-resolver.d.ts.map +1 -1
- package/dist/skill-test/auth-resolver.js +26 -11
- package/dist/skill-test/auth-resolver.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +30 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +71 -5
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts +41 -2
- package/dist/skill-test/transcript.d.ts.map +1 -1
- package/dist/skill-test/transcript.js +55 -10
- package/dist/skill-test/transcript.js.map +1 -1
- package/dist/spawn-hardened.js +0 -1
- package/dist/spawn-hardened.js.map +1 -1
- package/dist/stdio-blocking.js +13 -14
- package/dist/stdio-blocking.js.map +1 -1
- package/dist/test-helpers.d.ts +135 -100
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +224 -146
- package/dist/test-helpers.js.map +1 -1
- package/dist/testing/executables.d.ts +32 -0
- package/dist/testing/executables.d.ts.map +1 -0
- package/dist/testing/executables.js +70 -0
- package/dist/testing/executables.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts +79 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -0
- package/dist/testing/hostile-tree.js +164 -0
- package/dist/testing/hostile-tree.js.map +1 -0
- package/dist/testing/platform-gates.d.ts +31 -0
- package/dist/testing/platform-gates.d.ts.map +1 -0
- package/dist/testing/platform-gates.js +31 -0
- package/dist/testing/platform-gates.js.map +1 -0
- package/dist/testing/temp-dir.d.ts +255 -0
- package/dist/testing/temp-dir.d.ts.map +1 -0
- package/dist/testing/temp-dir.js +363 -0
- package/dist/testing/temp-dir.js.map +1 -0
- package/dist/testing.d.ts +94 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +119 -3
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +197 -0
- package/dist/text-content.d.ts.map +1 -0
- package/dist/text-content.js +354 -0
- package/dist/text-content.js.map +1 -0
- package/dist/text-file.d.ts +45 -0
- package/dist/text-file.d.ts.map +1 -0
- package/dist/text-file.js +51 -0
- package/dist/text-file.js.map +1 -0
- package/dist/text.d.ts +16 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +16 -0
- package/dist/text.js.map +1 -0
- package/dist/timing-dump.d.ts +136 -0
- package/dist/timing-dump.d.ts.map +1 -0
- package/dist/timing-dump.js +229 -0
- package/dist/timing-dump.js.map +1 -0
- package/dist/windows-shell.d.ts +0 -1
- package/dist/windows-shell.d.ts.map +1 -1
- package/dist/windows-shell.js +0 -1
- package/dist/windows-shell.js.map +1 -1
- package/dist/zod-introspection.js +2 -2
- package/dist/zod-introspection.js.map +1 -1
- package/eslint/README.md +223 -49
- package/eslint/index.cjs +111 -97
- package/eslint/index.d.cts +16 -6
- package/eslint/rules/callee-name.cjs +23 -0
- package/eslint/rules/commands-import-boundary.cjs +148 -0
- package/eslint/rules/dead-import.cjs +1 -1
- package/eslint/rules/dirent-type-needs-symlink-check.cjs +282 -0
- package/eslint/rules/eslint-rule-factory.cjs +5 -2
- package/eslint/rules/exempt-path-matcher.cjs +2 -1
- package/eslint/rules/explicit-zod-strictness.cjs +120 -0
- package/eslint/rules/no-bare-dynamic-import-path.cjs +5 -1
- package/eslint/rules/no-bare-executable-spawn.cjs +70 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +213 -0
- package/eslint/rules/no-blind-catch.cjs +171 -0
- package/eslint/rules/no-child-process-execSync.cjs +8 -0
- package/eslint/rules/no-decaying-referent.cjs +217 -0
- package/eslint/rules/no-dotdot-containment.cjs +160 -0
- package/eslint/rules/no-file-url-string-concat.cjs +4 -1
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +228 -0
- package/eslint/rules/no-fs-mkdirSync.cjs +8 -0
- package/eslint/rules/no-fs-promises-cp.cjs +7 -0
- package/eslint/rules/no-fs-realpathSync.cjs +8 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +29 -18
- package/eslint/rules/no-io-in-unit-tier.cjs +129 -0
- package/eslint/rules/no-literal-process-exit.cjs +129 -0
- package/eslint/rules/no-manual-path-normalize.cjs +136 -107
- package/eslint/rules/no-os-tmpdir.cjs +8 -0
- package/eslint/rules/no-path-operations-in-comparisons.cjs +7 -2
- package/eslint/rules/no-path-sep-in-strings.cjs +7 -2
- package/eslint/rules/no-path-startswith.cjs +7 -2
- package/eslint/rules/no-process-exit-in-phase.cjs +127 -0
- package/eslint/rules/no-raw-node-path.cjs +563 -0
- package/eslint/rules/no-raw-text-decode.cjs +305 -0
- package/eslint/rules/no-registry-count-pin.cjs +255 -0
- package/eslint/rules/no-self-package-import.cjs +168 -0
- package/eslint/rules/no-test-scoped-functions.cjs +10 -3
- package/eslint/rules/no-unix-shell-commands.cjs +6 -2
- package/eslint/rules/no-unsafe-root-join.cjs +23 -2
- package/eslint/rules/no-url-pathname-for-fs.cjs +5 -1
- package/eslint/rules/no-version-literal.cjs +151 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +9 -0
- package/eslint/rules/require-justified-skip.cjs +8 -2
- package/eslint/rules/safe-import.cjs +1 -1
- package/package.json +16 -16
- package/dist/link-auth/build-headers.d.ts +0 -34
- package/dist/link-auth/build-headers.d.ts.map +0 -1
- package/dist/link-auth/build-headers.js +0 -58
- package/dist/link-auth/build-headers.js.map +0 -1
- package/dist/link-auth/expand-macro.d.ts +0 -38
- package/dist/link-auth/expand-macro.d.ts.map +0 -1
- package/dist/link-auth/expand-macro.js +0 -133
- package/dist/link-auth/expand-macro.js.map +0 -1
- package/dist/link-auth/macros.yaml +0 -50
- package/dist/link-auth/resolve-token.d.ts +0 -83
- package/dist/link-auth/resolve-token.d.ts.map +0 -1
- package/dist/link-auth/resolve-token.js +0 -101
- package/dist/link-auth/resolve-token.js.map +0 -1
- package/dist/link-auth/resolve.d.ts +0 -102
- package/dist/link-auth/resolve.d.ts.map +0 -1
- package/dist/link-auth/resolve.js +0 -66
- package/dist/link-auth/resolve.js.map +0 -1
- package/dist/link-auth/rewrite.d.ts +0 -52
- package/dist/link-auth/rewrite.d.ts.map +0 -1
- package/dist/link-auth/rewrite.js +0 -102
- package/dist/link-auth/rewrite.js.map +0 -1
- package/dist/link-auth/select-provider.d.ts +0 -30
- package/dist/link-auth/select-provider.d.ts.map +0 -1
- package/dist/link-auth/select-provider.js +0 -55
- package/dist/link-auth/select-provider.js.map +0 -1
- package/dist/link-auth/template.d.ts +0 -40
- package/dist/link-auth/template.d.ts.map +0 -1
- package/dist/link-auth/template.js +0 -89
- package/dist/link-auth/template.js.map +0 -1
- package/dist/link-auth/transforms.d.ts +0 -46
- package/dist/link-auth/transforms.d.ts.map +0 -1
- package/dist/link-auth/transforms.js +0 -52
- package/dist/link-auth/transforms.js.map +0 -1
- package/dist/template-entry.d.ts +0 -10
- package/dist/template-entry.d.ts.map +0 -1
- package/dist/template-entry.js +0 -10
- package/dist/template-entry.js.map +0 -1
- package/dist/template.d.ts +0 -7
- package/dist/template.d.ts.map +0 -1
- package/dist/template.js +0 -18
- package/dist/template.js.map +0 -1
- package/eslint/rules/no-path-join.cjs +0 -14
- package/eslint/rules/no-path-relative.cjs +0 -14
- package/eslint/rules/no-path-resolve.cjs +0 -14
- package/eslint/rules/path-function-rule-factory.cjs +0 -496
package/dist/git-tracker.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import { dirname } from 'node:path';
|
|
9
|
+
import { CRAWL_SHARED_GIT_TRACKER_ID, crawlTimingStart, recordSharedPass, } from './crawl-timing.js';
|
|
10
|
+
import { peekGitTreeSnapshot } from './git-snapshot.js';
|
|
9
11
|
import { gitLsFiles, isGitIgnored } from './git-utils.js';
|
|
10
12
|
import { safePath, toForwardSlash } from './path-utils.js';
|
|
11
13
|
/**
|
|
@@ -42,6 +44,33 @@ export class GitTracker {
|
|
|
42
44
|
activeSet = new Set();
|
|
43
45
|
/** Absolute paths of every directory that contains at least one active-set file. */
|
|
44
46
|
activeAncestors = new Set();
|
|
47
|
+
/**
|
|
48
|
+
* Lowercased absolute path → the root-relative path spelled the way git
|
|
49
|
+
* spelled it. Built from the very same `git ls-files` output `activeSet`
|
|
50
|
+
* comes from, so it costs one extra `Map` and no extra git invocation.
|
|
51
|
+
*/
|
|
52
|
+
indexPaths = new Map();
|
|
53
|
+
/**
|
|
54
|
+
* Absolute, forward-slashed directories the active-set listing could not
|
|
55
|
+
* OPEN, so nothing beneath them is in the set — read off `git ls-files
|
|
56
|
+
* --others`' stderr through the listing's `unreadable` policy (`degrade`).
|
|
57
|
+
*
|
|
58
|
+
* 🚨 Beneath one of these the active set has no opinion. Its rule is "absent
|
|
59
|
+
* from the set and present on disk ⇒ ignored", and that rule is sound only
|
|
60
|
+
* where the listing LOOKED: an untracked, non-ignored file under a `--x`
|
|
61
|
+
* directory is absent because git could not read the directory, not because
|
|
62
|
+
* a pattern excludes it, and `git check-ignore` — which answers from the
|
|
63
|
+
* patterns and needs no listing — says so. Before this was recorded the
|
|
64
|
+
* tracker called such a file gitignored, silently, and every consumer of
|
|
65
|
+
* that answer (the leak judge, the link-graph walker, audit's distributed
|
|
66
|
+
* lane) was one refused `opendir` from a wrong verdict.
|
|
67
|
+
*
|
|
68
|
+
* The directory ITSELF is non-ignored by construction — git opened it
|
|
69
|
+
* precisely because no pattern excluded it — so it and its ancestors are
|
|
70
|
+
* entered into {@link activeAncestors}: a walker must not prune what the
|
|
71
|
+
* listing could not see into.
|
|
72
|
+
*/
|
|
73
|
+
unlistableDirectories = [];
|
|
45
74
|
initialized = false;
|
|
46
75
|
activeSetPopulated = false;
|
|
47
76
|
/** Whether `git ls-files` actually answered during {@link initialize}. */
|
|
@@ -61,27 +90,121 @@ export class GitTracker {
|
|
|
61
90
|
* With `includeUntracked: false`, only tracked files are pre-populated.
|
|
62
91
|
* Untracked non-ignored files will miss the cache and fall through to
|
|
63
92
|
* `git check-ignore` via {@link isIgnored}.
|
|
93
|
+
*
|
|
94
|
+
* ## This is the one bracket in the crawl-timing seam's `shared` stratum
|
|
95
|
+
*
|
|
96
|
+
* The `git ls-files` spawn below is preparation BOTH crawlers consume and
|
|
97
|
+
* NEITHER owns — the incumbent link walk and the projection's contributors are
|
|
98
|
+
* each handed a tracker by their caller — so it is charged to
|
|
99
|
+
* {@link CRAWL_SHARED_GIT_TRACKER_ID}, in a stratum belonging to no arm.
|
|
100
|
+
*
|
|
101
|
+
* The bracket is here, inside the class, and not at the six sites that build a
|
|
102
|
+
* tracker, for the reason `crawl-timing.ts` gives about `ResourceRegistry`: six
|
|
103
|
+
* copies are six chances to disagree, and a seventh site added later would
|
|
104
|
+
* silently go uncharged. Here, every caller is covered by construction — which
|
|
105
|
+
* includes `@vibe-agent-toolkit/discovery`, a package that could not have filed
|
|
106
|
+
* a row from its own call site at all, since it depends on `utils` alone.
|
|
107
|
+
*
|
|
108
|
+
* The early return above is deliberately OUTSIDE it: a re-entrant call does no
|
|
109
|
+
* work, and charging it would inflate `calls` with questions rather than
|
|
110
|
+
* spawns.
|
|
64
111
|
*/
|
|
65
112
|
async initialize(options) {
|
|
66
113
|
if (this.initialized) {
|
|
67
114
|
return;
|
|
68
115
|
}
|
|
116
|
+
const startedAt = crawlTimingStart();
|
|
69
117
|
const includeUntracked = options?.includeUntracked ?? true;
|
|
70
|
-
const files = gitLsFiles({
|
|
118
|
+
const files = this.activePathsFromOpenSnapshot(includeUntracked) ?? gitLsFiles({
|
|
71
119
|
cwd: this.projectRoot,
|
|
72
120
|
...(includeUntracked ? { includeUntracked: true } : {}),
|
|
121
|
+
// Degrade, and RECORD: a directory git could not open is a shorter list
|
|
122
|
+
// nothing can tell from a complete one — see {@link unlistableDirectories}
|
|
123
|
+
// for how the tracker keeps that gap honest. Only the untracked listing
|
|
124
|
+
// walks the tree, so the tracked-only one never calls this.
|
|
125
|
+
unreadable: {
|
|
126
|
+
degrade: (refusal) => {
|
|
127
|
+
this.unlistableDirectories.push(refusal.directory);
|
|
128
|
+
},
|
|
129
|
+
},
|
|
73
130
|
});
|
|
74
131
|
if (files !== null) {
|
|
75
132
|
for (const relativePath of files) {
|
|
76
133
|
const absolutePath = safePath.resolve(this.projectRoot, relativePath);
|
|
77
134
|
this.cache.set(absolutePath, false); // false = not ignored
|
|
78
135
|
this.activeSet.add(absolutePath);
|
|
136
|
+
this.indexPaths.set(absolutePath.toLowerCase(), toForwardSlash(relativePath));
|
|
79
137
|
}
|
|
80
138
|
this.populateAncestorSet();
|
|
81
139
|
}
|
|
82
140
|
this.gitAnswered = files !== null;
|
|
83
141
|
this.activeSetPopulated = includeUntracked && files !== null;
|
|
84
142
|
this.initialized = true;
|
|
143
|
+
// After the state above is settled, so a throw from the seam could never
|
|
144
|
+
// leave a half-initialized tracker; and charged even when git did not answer,
|
|
145
|
+
// because a failed `git ls-files` still spawned a process and still cost the
|
|
146
|
+
// command the time it took to fail.
|
|
147
|
+
recordSharedPass(CRAWL_SHARED_GIT_TRACKER_ID, startedAt);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The active set read off a snapshot this process has ALREADY taken — or
|
|
151
|
+
* `null` to say "ask git yourself".
|
|
152
|
+
*
|
|
153
|
+
* ## Why this is the same question, not a similar one
|
|
154
|
+
*
|
|
155
|
+
* `git ls-files --cached --others --exclude-standard` returns
|
|
156
|
+
* `tracked ∪ (untracked ∧ ¬ignored)`. A snapshot is `git add --all` **without**
|
|
157
|
+
* `--force` staged into a throwaway index, whose membership is that same set
|
|
158
|
+
* by construction — the exclusion of ignored paths is `--force`'s absence in
|
|
159
|
+
* both cases. So where a snapshot is already in hand, the spawn this method
|
|
160
|
+
* replaces would re-derive a set the process is already holding.
|
|
161
|
+
*
|
|
162
|
+
* It is deliberately a PEEK ({@link peekGitTreeSnapshot}) and never a take: a
|
|
163
|
+
* snapshot costs a `git add --all`, so causing one to avoid an `ls-files`
|
|
164
|
+
* would be a large loss dressed as a saving. On the incumbent walk no snapshot
|
|
165
|
+
* is ever taken, the peek misses, and this tracker spawns exactly as it always
|
|
166
|
+
* has.
|
|
167
|
+
*
|
|
168
|
+
* ## The one divergence, and why it is an improvement
|
|
169
|
+
*
|
|
170
|
+
* `--cached` reads the REAL index, so a tracked file deleted from the working
|
|
171
|
+
* tree is still listed. `git add --all` stages that deletion, so the snapshot
|
|
172
|
+
* omits it. The set is therefore not identical — it is the same set minus
|
|
173
|
+
* paths that do not exist, which is what {@link isIgnoredByActiveSet} already
|
|
174
|
+
* documents the active set to be ("it can only ever contain paths that
|
|
175
|
+
* EXIST"). The snapshot-sourced set honours that sentence more exactly than
|
|
176
|
+
* the spawn does.
|
|
177
|
+
*
|
|
178
|
+
* @param includeUntracked - The caller's requested membership
|
|
179
|
+
* @returns Root-relative paths in `git ls-files` shape, or `null` when no
|
|
180
|
+
* snapshot is available or the request is one a snapshot cannot answer
|
|
181
|
+
*/
|
|
182
|
+
activePathsFromOpenSnapshot(includeUntracked) {
|
|
183
|
+
// A snapshot cannot express the tracked-ONLY set: `add --all` stages
|
|
184
|
+
// untracked-not-ignored files too, and nothing in the result marks which
|
|
185
|
+
// entries were already tracked. Declining is the only correct answer.
|
|
186
|
+
if (!includeUntracked)
|
|
187
|
+
return null;
|
|
188
|
+
const snapshot = peekGitTreeSnapshot(this.projectRoot);
|
|
189
|
+
if (snapshot === undefined)
|
|
190
|
+
return null;
|
|
191
|
+
const relativePaths = [];
|
|
192
|
+
for (const entry of snapshot.entries) {
|
|
193
|
+
// A snapshot covers the whole REPOSITORY, which may be an ancestor of this
|
|
194
|
+
// tracker's root — `git ls-files` run at that root scopes its listing to
|
|
195
|
+
// it, so anything above is not this tracker's business and must not enter
|
|
196
|
+
// the active set. Everything below is kept verbatim, symlinks and
|
|
197
|
+
// submodule gitlinks included, because `--cached` lists those too.
|
|
198
|
+
const relativePath = toForwardSlash(safePath.relative(this.normalizedProjectRoot, entry.absolutePath));
|
|
199
|
+
// A leading `..` SEGMENT, not a `../` prefix: compared as a path segment,
|
|
200
|
+
// a file legitimately named `..hidden` is kept rather than silently
|
|
201
|
+
// dropped as if it were an escape.
|
|
202
|
+
if (relativePath.length === 0 || relativePath.split('/')[0] === '..') {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
relativePaths.push(relativePath);
|
|
206
|
+
}
|
|
207
|
+
return relativePaths;
|
|
85
208
|
}
|
|
86
209
|
/**
|
|
87
210
|
* Did git actually answer, or is this tracker an empty shell?
|
|
@@ -109,24 +232,49 @@ export class GitTracker {
|
|
|
109
232
|
* `hasActiveDescendant` / `isIgnoredByActiveSet` ancestor lookup misses on Windows.
|
|
110
233
|
*/
|
|
111
234
|
populateAncestorSet() {
|
|
112
|
-
const root = this.normalizedProjectRoot;
|
|
113
235
|
for (const absolutePath of this.activeSet) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const parent = toForwardSlash(dirname(current));
|
|
122
|
-
if (parent === current) {
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
current = parent;
|
|
126
|
-
}
|
|
236
|
+
this.recordAncestorsFrom(toForwardSlash(dirname(absolutePath)));
|
|
237
|
+
}
|
|
238
|
+
// A directory the listing could not open is itself non-ignored (git opened
|
|
239
|
+
// it because no pattern excluded it) and may hold active files nobody could
|
|
240
|
+
// list — so it counts as an ancestor from ITSELF down, not from its parent.
|
|
241
|
+
for (const directory of this.unlistableDirectories) {
|
|
242
|
+
this.recordAncestorsFrom(directory);
|
|
127
243
|
}
|
|
128
244
|
// projectRoot itself is always an implicit ancestor of everything under it.
|
|
129
|
-
this.activeAncestors.add(
|
|
245
|
+
this.activeAncestors.add(this.normalizedProjectRoot);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Record `start` and every directory above it up to (not including) the
|
|
249
|
+
* project root as active ancestors, stopping at the first one already known
|
|
250
|
+
* — everything above it necessarily is too.
|
|
251
|
+
*
|
|
252
|
+
* @param start - Forward-slashed absolute directory to begin at
|
|
253
|
+
*/
|
|
254
|
+
recordAncestorsFrom(start) {
|
|
255
|
+
const root = this.normalizedProjectRoot;
|
|
256
|
+
let current = start;
|
|
257
|
+
while (current !== root && current.length > root.length) {
|
|
258
|
+
if (this.activeAncestors.has(current)) {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
this.activeAncestors.add(current);
|
|
262
|
+
const parent = toForwardSlash(dirname(current));
|
|
263
|
+
if (parent === current) {
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
current = parent;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Whether the active set can say anything about a path: it cannot beneath a
|
|
271
|
+
* directory the listing was refused — see {@link unlistableDirectories}.
|
|
272
|
+
*
|
|
273
|
+
* @param normalizedAbsolutePath - Forward-slashed absolute path
|
|
274
|
+
* @returns True when the path lies strictly beneath a refused directory
|
|
275
|
+
*/
|
|
276
|
+
beneathUnlistable(normalizedAbsolutePath) {
|
|
277
|
+
return this.unlistableDirectories.some((directory) => normalizedAbsolutePath.startsWith(`${directory}/`));
|
|
130
278
|
}
|
|
131
279
|
/**
|
|
132
280
|
* Returns true if the given absolute path IS an active-set file OR is an
|
|
@@ -145,7 +293,9 @@ export class GitTracker {
|
|
|
145
293
|
return true;
|
|
146
294
|
}
|
|
147
295
|
const normalized = safePath.resolve(absolutePath);
|
|
148
|
-
|
|
296
|
+
// Beneath a refused directory nothing was listed, so "no active descendant"
|
|
297
|
+
// would be a claim the listing never earned.
|
|
298
|
+
return this.activeSet.has(normalized) || this.activeAncestors.has(normalized) || this.beneathUnlistable(normalized);
|
|
149
299
|
}
|
|
150
300
|
/**
|
|
151
301
|
* Fast O(1) ignore check against the pre-populated active set.
|
|
@@ -153,7 +303,9 @@ export class GitTracker {
|
|
|
153
303
|
* For paths INSIDE the project root **that exist on disk**, membership in the
|
|
154
304
|
* active set is authoritative: such a path is ignored iff it is not in the
|
|
155
305
|
* active set AND not an ancestor of any active-set path. No `git
|
|
156
|
-
* check-ignore` spawn.
|
|
306
|
+
* check-ignore` spawn. The one exception is a path beneath a directory the
|
|
307
|
+
* listing could not OPEN — see {@link unlistableDirectories} — where absence
|
|
308
|
+
* says nothing and the question goes to `git check-ignore`.
|
|
157
309
|
*
|
|
158
310
|
* The existence qualifier is load-bearing, not a caveat. The active set is
|
|
159
311
|
* built from `git ls-files`, so it can only ever contain paths that EXIST — a
|
|
@@ -167,7 +319,38 @@ export class GitTracker {
|
|
|
167
319
|
* so a repeated miss on the same path stays O(1).
|
|
168
320
|
*
|
|
169
321
|
* For paths OUTSIDE the project root, falls back to {@link isIgnored} so
|
|
170
|
-
* legacy behavior is preserved.
|
|
322
|
+
* legacy behavior is preserved. That fallback is the expensive one, and its
|
|
323
|
+
* cost does not resemble the in-root cost at all.
|
|
324
|
+
*
|
|
325
|
+
* ## ⚠️ The out-of-root fallback costs an order of magnitude more per path
|
|
326
|
+
*
|
|
327
|
+
* Measured in the D9 parity fixture: an out-of-root path costs
|
|
328
|
+
* **185–427 ms**, against **12–28 ms** for every in-repo path — ≈7–36× on
|
|
329
|
+
* those ranges.
|
|
330
|
+
*
|
|
331
|
+
* The mechanism is the delegation above plus what git does with it.
|
|
332
|
+
* {@link isIgnored} runs `git check-ignore` with the PROJECT ROOT as cwd; git
|
|
333
|
+
* exits 128 for a path outside that repository; and {@link isGitIgnored}'s
|
|
334
|
+
* exit-128 recovery walk then spawns `check-ignore` ONCE PER ANCESTOR up to
|
|
335
|
+
* `/`. No ancestor of an out-of-root path is ever the project root that would
|
|
336
|
+
* stop the walk, and every one of those ancestors is outside the repository
|
|
337
|
+
* too — so each also exits 128 and the walk never breaks early either. The
|
|
338
|
+
* verdict is cached, so the price is paid once per DISTINCT out-of-root path
|
|
339
|
+
* rather than once per call.
|
|
340
|
+
*
|
|
341
|
+
* The consequence for a caller choosing between the two oracles: switching
|
|
342
|
+
* from {@link isIgnored} to this method removes the spawn population **only
|
|
343
|
+
* for in-root paths**. A lane feeding out-of-root paths in bulk keeps the
|
|
344
|
+
* per-path spawn in full. So a comment asserting that the out-of-project case
|
|
345
|
+
* is rare, safe or cheap is asserting something this measurement contradicts:
|
|
346
|
+
* it is rare in some lanes, and it is never cheap.
|
|
347
|
+
*
|
|
348
|
+
* The exit-128 walk itself is documented in {@link isGitIgnored} — but only
|
|
349
|
+
* for the two cases already answered there: the SYMLINK case it exists to
|
|
350
|
+
* serve, and the NO-REPOSITORY case, which an early return now short-circuits
|
|
351
|
+
* before any spawn. The IN-REPO OUT-OF-ROOT path is the third case, it is
|
|
352
|
+
* documented nowhere there, and it is the one that still pays the walk in
|
|
353
|
+
* full. That is why the number lives here.
|
|
171
354
|
*
|
|
172
355
|
* Requires {@link initialize} with `includeUntracked: true` (the default).
|
|
173
356
|
* When initialized without untracked files, this method delegates to
|
|
@@ -175,8 +358,21 @@ export class GitTracker {
|
|
|
175
358
|
* possible per-path spawn.
|
|
176
359
|
*
|
|
177
360
|
* @param absolutePath - Absolute path to check
|
|
361
|
+
* @param knownToExist - The caller's own answer to the existence question, when
|
|
362
|
+
* it has already asked. Supplying it skips this method's `existsSync`, which
|
|
363
|
+
* is otherwise paid once per path that is absent from the active set — i.e.
|
|
364
|
+
* once per ignored path, and the projection's `filesystem` extent enumerates
|
|
365
|
+
* all of them (11,108 calls on an 8,496-path adopter tree).
|
|
366
|
+
*
|
|
367
|
+
* **It must mean what `existsSync` means: `stat` succeeds, following
|
|
368
|
+
* symlinks.** A caller holding only an `lstat` result has a DIFFERENT fact —
|
|
369
|
+
* `lstat` succeeds on a dangling symlink where `existsSync` returns false —
|
|
370
|
+
* and must narrow it to `exists && symlinkResolves !== false` rather than
|
|
371
|
+
* pass the `lstat` boolean through, or dangling symlinks silently stop
|
|
372
|
+
* falling back to `git check-ignore` and start reporting as ignored.
|
|
373
|
+
* Omit it and nothing changes.
|
|
178
374
|
*/
|
|
179
|
-
isIgnoredByActiveSet(absolutePath) {
|
|
375
|
+
isIgnoredByActiveSet(absolutePath, knownToExist) {
|
|
180
376
|
if (!this.activeSetPopulated) {
|
|
181
377
|
return this.isIgnored(absolutePath);
|
|
182
378
|
}
|
|
@@ -188,10 +384,16 @@ export class GitTracker {
|
|
|
188
384
|
if (this.activeSet.has(normalized) || this.activeAncestors.has(normalized)) {
|
|
189
385
|
return false;
|
|
190
386
|
}
|
|
387
|
+
// Absent from the set beneath a directory the listing could not open: the
|
|
388
|
+
// set never looked there, so absence is not evidence — see
|
|
389
|
+
// {@link unlistableDirectories}. Only `git check-ignore` can answer.
|
|
390
|
+
if (this.beneathUnlistable(normalized)) {
|
|
391
|
+
return this.isIgnored(absolutePath);
|
|
392
|
+
}
|
|
191
393
|
// Absent from the active set. That means "ignored" only for a path that is
|
|
192
394
|
// actually there; otherwise the set has no opinion and git must be asked.
|
|
193
|
-
|
|
194
|
-
if (!
|
|
395
|
+
const present = knownToExist ?? existsSync(normalized);
|
|
396
|
+
if (!present) {
|
|
195
397
|
return this.isIgnored(absolutePath);
|
|
196
398
|
}
|
|
197
399
|
return true;
|
|
@@ -228,6 +430,30 @@ export class GitTracker {
|
|
|
228
430
|
this.cache.set(cacheKey, ignored);
|
|
229
431
|
return ignored;
|
|
230
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* The spelling git records for a path, or `null` if git has no record of it.
|
|
435
|
+
*
|
|
436
|
+
* This is the casing oracle, not another ignore check. On a case-insensitive
|
|
437
|
+
* filesystem `docs/Readme.md` and `docs/README.md` are one inode with two
|
|
438
|
+
* spellings, and Node's two `realpath` implementations disagree about which
|
|
439
|
+
* one they hand back — so anything that derives an identity from a path needs
|
|
440
|
+
* a single authoritative spelling, and git's is it wherever git has one.
|
|
441
|
+
*
|
|
442
|
+
* The lookup key is lowercased, which is the point: the caller asks with
|
|
443
|
+
* whatever casing it observed and gets back the casing git holds.
|
|
444
|
+
*
|
|
445
|
+
* Answers only from the pre-populated set — never spawns. A path git does not
|
|
446
|
+
* know (untracked-and-ignored, non-existent, outside the project root, or any
|
|
447
|
+
* path at all when `git ls-files` did not answer) returns `null`, and the
|
|
448
|
+
* caller falls back to the on-disk casing.
|
|
449
|
+
*
|
|
450
|
+
* @param absolutePath - Absolute path to look up
|
|
451
|
+
* @returns Root-relative, forward-slashed path as git spells it — relative to
|
|
452
|
+
* THIS tracker's project root — or `null` when git has no record of it
|
|
453
|
+
*/
|
|
454
|
+
indexPathFor(absolutePath) {
|
|
455
|
+
return this.indexPaths.get(safePath.resolve(absolutePath).toLowerCase()) ?? null;
|
|
456
|
+
}
|
|
231
457
|
/**
|
|
232
458
|
* Get cache statistics.
|
|
233
459
|
*/
|
|
@@ -245,6 +471,8 @@ export class GitTracker {
|
|
|
245
471
|
this.cache.clear();
|
|
246
472
|
this.activeSet.clear();
|
|
247
473
|
this.activeAncestors.clear();
|
|
474
|
+
this.indexPaths.clear();
|
|
475
|
+
this.unlistableDirectories.length = 0;
|
|
248
476
|
this.initialized = false;
|
|
249
477
|
this.activeSetPopulated = false;
|
|
250
478
|
this.gitAnswered = false;
|
package/dist/git-tracker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-tracker.js","sourceRoot":"","sources":["../src/git-tracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAqB3D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,OAAO,UAAU;IACJ,WAAW,CAAS;IACpB,qBAAqB,CAAS;IAC9B,KAAK,GAAyB,IAAI,GAAG,EAAE,CAAC;IACzD,6FAA6F;IAC5E,SAAS,GAAgB,IAAI,GAAG,EAAE,CAAC;IACpD,oFAAoF;IACnE,eAAe,GAAgB,IAAI,GAAG,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"git-tracker.js","sourceRoot":"","sources":["../src/git-tracker.ts"],"names":[],"mappings":"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"}
|
package/dist/git-utils.d.ts
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
* Centralized git command wrapper.
|
|
3
3
|
* All git commands should go through this module for consistency and testability.
|
|
4
4
|
*/
|
|
5
|
+
import { type UnreadablePolicy } from './listing-refusal.js';
|
|
6
|
+
/**
|
|
7
|
+
* The directories a git listing was REFUSED, read off its stderr.
|
|
8
|
+
*
|
|
9
|
+
* Pure. Returns each refused directory once, worktree-relative and without
|
|
10
|
+
* git's trailing slash, with the errno its reason maps to. A directory git
|
|
11
|
+
* could not open because it no longer exists is not returned: that is the
|
|
12
|
+
* `absent` outcome of {@link listingFailure}, decided there and not here, so
|
|
13
|
+
* the two routes classify a vanished directory the same way.
|
|
14
|
+
*
|
|
15
|
+
* @param stderr - What the listing wrote to stderr
|
|
16
|
+
* @returns The refusals, in the order git printed them, deduplicated
|
|
17
|
+
*/
|
|
18
|
+
export declare function unlistableDirectoriesIn(stderr: string): {
|
|
19
|
+
directory: string;
|
|
20
|
+
code: string;
|
|
21
|
+
}[];
|
|
22
|
+
/**
|
|
23
|
+
* The policy every working-tree listing here REQUIRES.
|
|
24
|
+
*
|
|
25
|
+
* A refused directory is otherwise invisible: git's exit status is 0 and its
|
|
26
|
+
* stdout is simply shorter. Each directory git could not open is settled as
|
|
27
|
+
* the SAME {@link DirectoryRefusal} the filesystem walk produces
|
|
28
|
+
* (`file-crawler.ts`) — absolute, forward-slashed, with `transient` derived
|
|
29
|
+
* beside the errno list in `fs-utils.ts` — under the same
|
|
30
|
+
* {@link UnreadablePolicy}, so a caller decides once for both routes.
|
|
31
|
+
*
|
|
32
|
+
* 🪤 This was `onUnreadable?`, and when it was omitted the refusals git printed
|
|
33
|
+
* were DROPPED — not thrown, as the walk did, but read off stderr and ignored.
|
|
34
|
+
* The one lane whose only witness to the gap is stderr was the one lane that
|
|
35
|
+
* defaulted to silence. There is no default now.
|
|
36
|
+
*/
|
|
37
|
+
export interface GitListingOptions {
|
|
38
|
+
/** What to do with a directory git could not open while walking the working tree. */
|
|
39
|
+
unreadable: UnreadablePolicy;
|
|
40
|
+
}
|
|
5
41
|
/**
|
|
6
42
|
* Find the git repository root by walking up from the given directory.
|
|
7
43
|
*
|
|
@@ -27,6 +63,8 @@ export declare function gitFindRoot(startDir: string): string | null;
|
|
|
27
63
|
* @param options.cwd - Working directory (git repository root or subdirectory)
|
|
28
64
|
* @param options.patterns - Optional glob patterns to filter files (e.g., '*.md', 'docs/**\/*.ts')
|
|
29
65
|
* @param options.includeUntracked - Include untracked files that aren't gitignored (default: false)
|
|
66
|
+
* @param options.unreadable - See {@link GitListingOptions}. Only the untracked
|
|
67
|
+
* listing walks the working tree, so only it can have skipped a directory
|
|
30
68
|
* @returns Array of file paths relative to the git root, or null if not in a git repo
|
|
31
69
|
*
|
|
32
70
|
* @example
|
|
@@ -38,11 +76,56 @@ export declare function gitFindRoot(startDir: string): string | null;
|
|
|
38
76
|
* const allFiles = gitLsFiles({ cwd: '/project', includeUntracked: true });
|
|
39
77
|
* ```
|
|
40
78
|
*/
|
|
41
|
-
export declare function gitLsFiles(options: {
|
|
79
|
+
export declare function gitLsFiles(options: GitListingOptions & {
|
|
42
80
|
cwd: string;
|
|
43
81
|
patterns?: string[];
|
|
44
82
|
includeUntracked?: boolean;
|
|
45
83
|
}): string[] | null;
|
|
84
|
+
/**
|
|
85
|
+
* List paths git does NOT track, with each wholly-untracked directory collapsed
|
|
86
|
+
* to a single entry.
|
|
87
|
+
*
|
|
88
|
+
* This is the **prune list**, not a file list, and the distinction is the whole
|
|
89
|
+
* value. `--others --ignored --exclude-standard` alone returns every ignored file
|
|
90
|
+
* individually: measured on an 8,496-path adopter working tree that is 533,557
|
|
91
|
+
* paths in 1.19 s — worse than the crawl it was meant to replace, because
|
|
92
|
+
* `.turbo/cache` alone contributed 418,518 of them. Adding `--directory`
|
|
93
|
+
* collapses each wholly-ignored directory to one entry: **369 entries in 60 ms**.
|
|
94
|
+
* A caller can then decide per directory whether to descend, and skip a
|
|
95
|
+
* half-million paths by name without ever entering them.
|
|
96
|
+
*
|
|
97
|
+
* A collapsed directory entry is returned with a trailing `/`, exactly as git
|
|
98
|
+
* spells it. That is how a caller tells "this whole subtree" from "this one
|
|
99
|
+
* file", so it is deliberately not normalized away here.
|
|
100
|
+
*
|
|
101
|
+
* @param options - Configuration options
|
|
102
|
+
* @param options.cwd - Working directory inside the repository
|
|
103
|
+
* @param options.ignored - Restrict to ignored paths. Off, the listing is
|
|
104
|
+
* untracked-but-not-ignored paths — which is the only way to see an EMPTY
|
|
105
|
+
* untracked directory, since a directory with no files in it is invisible to
|
|
106
|
+
* `ls-files` and to any tree object
|
|
107
|
+
* @param options.directory - Collapse a wholly-untracked directory to one entry
|
|
108
|
+
* @param options.unreadable - See {@link GitListingOptions}. ⚠️ The two
|
|
109
|
+
* listings meet different refusals: without `--ignored`, git collapses a
|
|
110
|
+
* WHOLLY-untracked directory without opening it, but it still opens a
|
|
111
|
+
* non-ignored directory that holds tracked files (to look for untracked
|
|
112
|
+
* siblings) and warns when it cannot; the `ignored` listing opens every
|
|
113
|
+
* non-ignored directory and so meets every refusal the first one does and
|
|
114
|
+
* more (measured on git 2.50)
|
|
115
|
+
* @returns Paths relative to the git root, or null if git did not answer
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* // The prune list: where the ignored territory is, without enumerating it.
|
|
120
|
+
* const prune = gitLsOthers({ cwd: root, ignored: true, directory: true });
|
|
121
|
+
* // → ['dist/', 'node_modules/', '.turbo/', 'notes.local.md']
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export declare function gitLsOthers(options: GitListingOptions & {
|
|
125
|
+
cwd: string;
|
|
126
|
+
ignored?: boolean;
|
|
127
|
+
directory?: boolean;
|
|
128
|
+
}): string[] | null;
|
|
46
129
|
/**
|
|
47
130
|
* Check if a file path is ignored by git
|
|
48
131
|
*
|
package/dist/git-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../src/git-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../src/git-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAA0C,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAmDrG;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAW7F;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,iBAAiB;IAChC,qFAAqF;IACrF,UAAU,EAAE,gBAAgB,CAAC;CAC9B;AAkDD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkB3D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,MAAM,EAAE,GAAG,IAAI,CAqBlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,GAAG,MAAM,EAAE,GAAG,IAAI,CAiBlB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,OAAO,CAiDnF"}
|