claudeup 4.18.0 → 4.22.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/bin/claudeup.js +43 -19
- package/package.json +14 -19
- package/scripts/build-binaries.ts +77 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +43 -0
- package/src/__tests__/alias-adopt.test.ts +364 -0
- package/src/__tests__/alias-parser.test.ts +92 -0
- package/src/__tests__/alias-shell-writer.test.ts +7 -0
- package/src/__tests__/alias-store.test.ts +77 -0
- package/src/__tests__/cli-router.test.ts +65 -0
- package/src/__tests__/conventions-integration.test.ts +745 -0
- package/src/__tests__/conventions-manager.test.ts +1172 -0
- package/src/__tests__/doctor-bins.test.ts +35 -0
- package/src/__tests__/doctor.test.ts +450 -0
- package/src/__tests__/dual-write-prevention.test.ts +14 -4
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/gitignore-prerun.test.ts +1 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +98 -0
- package/src/__tests__/manifest.test.ts +136 -0
- package/src/__tests__/marketplace-refresh.test.ts +267 -0
- package/src/__tests__/plugin-requires.test.ts +133 -0
- package/src/__tests__/plugin-setup.test.ts +118 -0
- package/src/__tests__/profile-command.test.ts +116 -0
- package/src/__tests__/profile-materializer.test.ts +82 -0
- package/src/__tests__/profile-sync.test.ts +136 -0
- package/src/__tests__/registry-version-resolution.test.ts +74 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +218 -0
- package/src/__tests__/symlink-manager.test.ts +99 -0
- package/src/__tests__/toolchain.test.ts +56 -0
- package/src/cli/claude.ts +21 -0
- package/src/cli/doctor.ts +132 -0
- package/src/cli/install.ts +360 -0
- package/src/cli/profile.ts +166 -0
- package/src/cli/router.ts +107 -0
- package/src/cli/update.ts +85 -0
- package/src/data/alias-flags.ts +11 -1
- package/src/data/cli-tools.ts +7 -7
- package/src/data/gitignore-defaults.ts +4 -0
- package/src/data/marketplaces.ts +12 -0
- package/src/data/predefined-profiles.ts +3 -4
- package/src/main.tsx +11 -154
- package/src/prerunner/index.ts +62 -27
- package/src/services/alias-shell-writer.ts +382 -8
- package/src/services/alias-store.ts +60 -0
- package/src/services/claude-cli.ts +19 -22
- package/src/services/claude-settings.ts +99 -26
- package/src/services/conventions-manager.ts +865 -0
- package/src/services/doctor-bins.ts +49 -0
- package/src/services/doctor.ts +509 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/manifest.ts +166 -0
- package/src/services/marketplace-refresh.ts +173 -0
- package/src/services/plugin-manager.ts +7 -1
- package/src/services/plugin-requires.ts +215 -0
- package/src/services/plugin-setup.ts +61 -4
- package/src/services/plugin-version-check.ts +9 -4
- package/src/services/profile-materializer.ts +61 -0
- package/src/services/profile-sync.ts +150 -0
- package/src/services/resolver.ts +293 -0
- package/src/services/symlink-manager.ts +146 -0
- package/src/services/toolchain.ts +97 -0
- package/src/services/version-check.ts +10 -11
- package/src/types/index.ts +138 -31
- package/src/ui/App.tsx +16 -11
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/modals/ConfirmModal.tsx +1 -1
- package/src/ui/screens/AliasScreen.tsx +491 -359
- package/src/ui/screens/CliToolsScreen.tsx +11 -3
- package/src/ui/screens/PluginsScreen.tsx +3 -1
- package/src/ui/screens/index.ts +0 -1
- package/src/ui/state/reducer.ts +6 -102
- package/src/ui/state/types.ts +8 -35
- package/src/utils/command-utils.ts +23 -0
- package/src/utils/file-locking.ts +144 -0
- package/src/data/alias-flags.js +0 -196
- package/src/data/cli-tools.js +0 -123
- package/src/data/gitignore-defaults.js +0 -24
- package/src/data/gitignore-reasons.js +0 -97
- package/src/data/gitignore-templates.js +0 -21
- package/src/data/marketplaces.js +0 -138
- package/src/data/mcp-servers.js +0 -509
- package/src/data/predefined-profiles.js +0 -248
- package/src/data/settings-catalog.js +0 -625
- package/src/data/skill-repos.js +0 -160
- package/src/data/statuslines.js +0 -159
- package/src/data/statuslines.ts +0 -188
- package/src/index.js +0 -4
- package/src/index.ts +0 -5
- package/src/main.js +0 -170
- package/src/prerunner/index.js +0 -252
- package/src/services/alias-settings.js +0 -51
- package/src/services/alias-shell-writer.js +0 -764
- package/src/services/alias-store.js +0 -77
- package/src/services/claude-cli.js +0 -189
- package/src/services/claude-runner.js +0 -28
- package/src/services/claude-settings.js +0 -1223
- package/src/services/gitignore-detector.js +0 -155
- package/src/services/gitignore-fixer.js +0 -231
- package/src/services/gitignore-prerun.js +0 -46
- package/src/services/gitignore-resolver.js +0 -143
- package/src/services/gitignore-service.js +0 -117
- package/src/services/local-marketplace.js +0 -339
- package/src/services/marketplace-fetcher.js +0 -96
- package/src/services/marketplace-sync.js +0 -94
- package/src/services/mcp-registry.js +0 -87
- package/src/services/plugin-manager.js +0 -473
- package/src/services/plugin-mcp-config.js +0 -177
- package/src/services/plugin-setup.js +0 -444
- package/src/services/plugin-version-check.js +0 -262
- package/src/services/profiles.js +0 -161
- package/src/services/settings-manager.js +0 -243
- package/src/services/skills-manager.js +0 -393
- package/src/services/skillsmp-client.js +0 -87
- package/src/services/update-cache.js +0 -52
- package/src/services/version-check.js +0 -99
- package/src/types/gitignore.js +0 -6
- package/src/types/index.js +0 -1
- package/src/ui/App.js +0 -346
- package/src/ui/adapters/pluginsAdapter.js +0 -139
- package/src/ui/adapters/settingsAdapter.js +0 -111
- package/src/ui/adapters/skillsAdapter.js +0 -154
- package/src/ui/components/CategoryHeader.js +0 -9
- package/src/ui/components/EmptyFilterState.js +0 -4
- package/src/ui/components/ScopeIndicator.js +0 -30
- package/src/ui/components/ScrollableList.js +0 -36
- package/src/ui/components/SearchInput.js +0 -19
- package/src/ui/components/StyledText.js +0 -39
- package/src/ui/components/TabBar.js +0 -19
- package/src/ui/components/layout/FooterHints.js +0 -29
- package/src/ui/components/layout/Panel.js +0 -6
- package/src/ui/components/layout/ProgressBar.js +0 -14
- package/src/ui/components/layout/ScopeTabs.js +0 -6
- package/src/ui/components/layout/ScreenLayout.js +0 -16
- package/src/ui/components/layout/index.js +0 -5
- package/src/ui/components/modals/ConfirmModal.js +0 -14
- package/src/ui/components/modals/InputModal.js +0 -5
- package/src/ui/components/modals/LoadingModal.js +0 -17
- package/src/ui/components/modals/MessageModal.js +0 -16
- package/src/ui/components/modals/ModalContainer.js +0 -137
- package/src/ui/components/modals/SelectModal.js +0 -18
- package/src/ui/components/modals/VersionMismatchModal.js +0 -36
- package/src/ui/components/modals/index.js +0 -6
- package/src/ui/components/primitives/ActionHints.js +0 -13
- package/src/ui/components/primitives/DetailSection.js +0 -7
- package/src/ui/components/primitives/KeyValueLine.js +0 -8
- package/src/ui/components/primitives/ListCategoryRow.js +0 -8
- package/src/ui/components/primitives/MetaText.js +0 -8
- package/src/ui/components/primitives/ScopeDetail.js +0 -32
- package/src/ui/components/primitives/ScopeSquares.js +0 -11
- package/src/ui/components/primitives/SelectableRow.js +0 -5
- package/src/ui/components/primitives/index.js +0 -8
- package/src/ui/hooks/index.js +0 -4
- package/src/ui/hooks/useAsyncData.js +0 -77
- package/src/ui/hooks/useGitignoreModal.js +0 -74
- package/src/ui/hooks/useKeyboard.js +0 -13
- package/src/ui/hooks/useKeyboardHandler.js +0 -39
- package/src/ui/hooks/useMismatchModal.js +0 -77
- package/src/ui/registry.js +0 -1
- package/src/ui/renderers/cliToolRenderers.js +0 -54
- package/src/ui/renderers/gitignoreRenderers.js +0 -46
- package/src/ui/renderers/mcpRenderers.js +0 -26
- package/src/ui/renderers/pluginRenderers.js +0 -124
- package/src/ui/renderers/profileRenderers.js +0 -177
- package/src/ui/renderers/settingsRenderers.js +0 -73
- package/src/ui/renderers/skillRenderers.js +0 -138
- package/src/ui/screens/AliasScreen.js +0 -1008
- package/src/ui/screens/CliToolsScreen.js +0 -338
- package/src/ui/screens/EnvVarsScreen.js +0 -152
- package/src/ui/screens/GitignoreScreen.js +0 -328
- package/src/ui/screens/McpRegistryScreen.js +0 -238
- package/src/ui/screens/McpScreen.js +0 -176
- package/src/ui/screens/ModelSelectorScreen.js +0 -296
- package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
- package/src/ui/screens/PluginsScreen.js +0 -766
- package/src/ui/screens/ProfilesScreen.js +0 -298
- package/src/ui/screens/SkillsScreen.js +0 -549
- package/src/ui/screens/StatusLineScreen.js +0 -206
- package/src/ui/screens/StatusLineScreen.tsx +0 -416
- package/src/ui/screens/index.js +0 -10
- package/src/ui/state/AnimationContext.js +0 -34
- package/src/ui/state/AppContext.js +0 -162
- package/src/ui/state/DimensionsContext.js +0 -71
- package/src/ui/state/reducer.js +0 -543
- package/src/ui/state/types.js +0 -1
- package/src/ui/theme.js +0 -47
- package/src/utils/clipboard.js +0 -56
- package/src/utils/command-utils.js +0 -19
- package/src/utils/fuzzy-search.js +0 -101
- package/src/utils/string-utils.js +0 -62
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Detect violations of the resolved gitignore manifest against a working
|
|
3
|
-
* tree. Emits one Violation per offending path.
|
|
4
|
-
*
|
|
5
|
-
* Strategy:
|
|
6
|
-
* - For ignore rules: ask `git ls-files` whether the path (or anything
|
|
7
|
-
* under it for directory-style rules) is currently tracked. If so,
|
|
8
|
-
* it's "tracked-but-should-ignore" (destructive). Then check whether
|
|
9
|
-
* the existing .gitignore covers the path; if not, that's
|
|
10
|
-
* "missing-from-gitignore" (safe additive fix).
|
|
11
|
-
* - For track rules: ask `git check-ignore` whether the path is being
|
|
12
|
-
* excluded by the current .gitignore. If yes, "ignored-but-should-track"
|
|
13
|
-
* (destructive — must remove the .gitignore line). If the path exists
|
|
14
|
-
* on disk but isn't tracked and isn't ignored, "untracked-and-should-track".
|
|
15
|
-
*
|
|
16
|
-
* Glob expansion is intentionally limited: we treat patterns as literals
|
|
17
|
-
* with directory-suffix awareness ("foo/" matches anything under foo/).
|
|
18
|
-
* Full gitignore-syntax glob expansion is deferred — git itself handles
|
|
19
|
-
* the heavy lifting via ls-files / check-ignore.
|
|
20
|
-
*/
|
|
21
|
-
import { spawn } from "node:child_process";
|
|
22
|
-
import { existsSync } from "node:fs";
|
|
23
|
-
import { join } from "node:path";
|
|
24
|
-
export async function detectViolations(cwd, resolved) {
|
|
25
|
-
if (!(await isGitRepo(cwd)))
|
|
26
|
-
return [];
|
|
27
|
-
const violations = [];
|
|
28
|
-
const trackedFiles = await listTrackedFiles(cwd);
|
|
29
|
-
const trackedSet = new Set(trackedFiles);
|
|
30
|
-
for (const rule of resolved.rules) {
|
|
31
|
-
if (rule.action === "ignore") {
|
|
32
|
-
const tracked = matchesTrackedFile(rule.pattern, trackedSet);
|
|
33
|
-
if (tracked.length > 0) {
|
|
34
|
-
for (const path of tracked) {
|
|
35
|
-
violations.push({
|
|
36
|
-
kind: "tracked-but-should-ignore",
|
|
37
|
-
path,
|
|
38
|
-
source: rule.source,
|
|
39
|
-
severity: "destructive",
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
// Not tracked — check if .gitignore covers it. If not, suggest
|
|
45
|
-
// a safe additive fix (append to .gitignore).
|
|
46
|
-
const ignored = await pathIsIgnored(cwd, rule.pattern);
|
|
47
|
-
if (!ignored) {
|
|
48
|
-
violations.push({
|
|
49
|
-
kind: "missing-from-gitignore",
|
|
50
|
-
path: rule.pattern,
|
|
51
|
-
source: rule.source,
|
|
52
|
-
severity: "safe",
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
// action === "track"
|
|
59
|
-
const ignored = await pathIsIgnored(cwd, rule.pattern);
|
|
60
|
-
if (ignored) {
|
|
61
|
-
violations.push({
|
|
62
|
-
kind: "ignored-but-should-track",
|
|
63
|
-
path: rule.pattern,
|
|
64
|
-
source: rule.source,
|
|
65
|
-
severity: "destructive",
|
|
66
|
-
});
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
const isTracked = trackedSet.has(rule.pattern);
|
|
70
|
-
const existsOnDisk = existsSync(join(cwd, rule.pattern));
|
|
71
|
-
if (existsOnDisk && !isTracked) {
|
|
72
|
-
violations.push({
|
|
73
|
-
kind: "untracked-and-should-track",
|
|
74
|
-
path: rule.pattern,
|
|
75
|
-
source: rule.source,
|
|
76
|
-
severity: "destructive",
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return violations;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Match a pattern against the tracked-files set. Directory-style patterns
|
|
85
|
-
* (trailing "/") match every tracked path under that prefix.
|
|
86
|
-
*/
|
|
87
|
-
function matchesTrackedFile(pattern, tracked) {
|
|
88
|
-
if (pattern.endsWith("/")) {
|
|
89
|
-
const prefix = pattern;
|
|
90
|
-
return Array.from(tracked).filter((p) => p.startsWith(prefix));
|
|
91
|
-
}
|
|
92
|
-
if (pattern.includes("*")) {
|
|
93
|
-
// Very limited literal-prefix glob handling. Full expansion is git's job.
|
|
94
|
-
const star = pattern.indexOf("*");
|
|
95
|
-
const prefix = pattern.slice(0, star);
|
|
96
|
-
return Array.from(tracked).filter((p) => p.startsWith(prefix));
|
|
97
|
-
}
|
|
98
|
-
return tracked.has(pattern) ? [pattern] : [];
|
|
99
|
-
}
|
|
100
|
-
async function isGitRepo(cwd) {
|
|
101
|
-
try {
|
|
102
|
-
await runGit(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async function listTrackedFiles(cwd) {
|
|
110
|
-
try {
|
|
111
|
-
const out = await runGit(["ls-files"], cwd);
|
|
112
|
-
return out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return [];
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
async function pathIsIgnored(cwd, path) {
|
|
119
|
-
try {
|
|
120
|
-
// git check-ignore exits 0 when path IS ignored, 1 when NOT, >1 on error.
|
|
121
|
-
await runGit(["check-ignore", "--quiet", "--", path], cwd);
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
catch (err) {
|
|
125
|
-
// exit code 1 = not ignored (expected), anything else swallowed.
|
|
126
|
-
if (err instanceof GitError && err.code === 1)
|
|
127
|
-
return false;
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
class GitError extends Error {
|
|
132
|
-
code;
|
|
133
|
-
stderr;
|
|
134
|
-
constructor(code, stderr) {
|
|
135
|
-
super(`git exited ${code}: ${stderr}`);
|
|
136
|
-
this.code = code;
|
|
137
|
-
this.stderr = stderr;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function runGit(args, cwd) {
|
|
141
|
-
return new Promise((resolve, reject) => {
|
|
142
|
-
const proc = spawn("git", args, { cwd });
|
|
143
|
-
let stdout = "";
|
|
144
|
-
let stderr = "";
|
|
145
|
-
proc.stdout.on("data", (b) => { stdout += b.toString(); });
|
|
146
|
-
proc.stderr.on("data", (b) => { stderr += b.toString(); });
|
|
147
|
-
proc.on("error", reject);
|
|
148
|
-
proc.on("close", (code) => {
|
|
149
|
-
if (code === 0)
|
|
150
|
-
resolve(stdout);
|
|
151
|
-
else
|
|
152
|
-
reject(new GitError(code ?? -1, stderr));
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Apply fixes for gitignore violations. Two flavors:
|
|
3
|
-
*
|
|
4
|
-
* - `applySafeFixes`: additive — appends missing patterns to .gitignore
|
|
5
|
-
* under a "# Added by claudeup" header. Idempotent (re-running is a no-op
|
|
6
|
-
* because patterns are already present).
|
|
7
|
-
*
|
|
8
|
-
* - `applyDestructiveFix`: index-mutating — runs `git rm --cached <path>`
|
|
9
|
-
* for tracked-but-should-ignore, `git add <path>` (after making sure
|
|
10
|
-
* gitignore has any needed `!` exceptions) for ignored-but-should-track /
|
|
11
|
-
* untracked-and-should-track.
|
|
12
|
-
* Each call handles ONE violation; the caller is responsible for confirming
|
|
13
|
-
* each one with the user.
|
|
14
|
-
*
|
|
15
|
-
* `formatViolationSummary` is a pure helper for prerun warning + TUI.
|
|
16
|
-
*/
|
|
17
|
-
import { readFile, writeFile, appendFile } from "node:fs/promises";
|
|
18
|
-
import { existsSync } from "node:fs";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
import { spawn } from "node:child_process";
|
|
21
|
-
import { getGitignoreRuleReason } from "../data/gitignore-reasons.js";
|
|
22
|
-
const CLAUDEUP_HEADER = "# Added by claudeup";
|
|
23
|
-
export async function applySafeFixes(cwd, violations) {
|
|
24
|
-
const safe = violations.filter((v) => v.severity === "safe" && v.kind === "missing-from-gitignore");
|
|
25
|
-
if (safe.length === 0) {
|
|
26
|
-
return { appended: [], alreadyPresent: [] };
|
|
27
|
-
}
|
|
28
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
29
|
-
const existing = existsSync(gitignorePath)
|
|
30
|
-
? await readFile(gitignorePath, "utf8")
|
|
31
|
-
: "";
|
|
32
|
-
const lines = new Set(existing.split("\n").map((l) => l.trim()).filter(Boolean));
|
|
33
|
-
const appended = [];
|
|
34
|
-
const alreadyPresent = [];
|
|
35
|
-
for (const v of safe) {
|
|
36
|
-
if (lines.has(v.path)) {
|
|
37
|
-
alreadyPresent.push(v.path);
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
appended.push(v.path);
|
|
41
|
-
lines.add(v.path);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (appended.length === 0)
|
|
45
|
-
return { appended, alreadyPresent };
|
|
46
|
-
const block = [
|
|
47
|
-
"",
|
|
48
|
-
CLAUDEUP_HEADER,
|
|
49
|
-
...appended.flatMap(formatGitignoreEntry),
|
|
50
|
-
"",
|
|
51
|
-
].join("\n");
|
|
52
|
-
if (existing.length > 0 && !existing.endsWith("\n")) {
|
|
53
|
-
await appendFile(gitignorePath, "\n" + block);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
await appendFile(gitignorePath, block);
|
|
57
|
-
}
|
|
58
|
-
return { appended, alreadyPresent };
|
|
59
|
-
}
|
|
60
|
-
export async function applyDestructiveFix(cwd, violation, rulePattern = violation.path) {
|
|
61
|
-
switch (violation.kind) {
|
|
62
|
-
case "tracked-but-should-ignore": {
|
|
63
|
-
await runGit(["rm", "--cached", "--", violation.path], cwd);
|
|
64
|
-
// Then make sure .gitignore has the entry so it stays out
|
|
65
|
-
await appendIfMissing(cwd, rulePattern);
|
|
66
|
-
return {
|
|
67
|
-
applied: true,
|
|
68
|
-
message: `Removed ${violation.path} from index and added ${rulePattern} to .gitignore`,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
case "ignored-but-should-track": {
|
|
72
|
-
await stripGitignoreLine(cwd, violation.path);
|
|
73
|
-
await appendTrackExceptionsIfNeeded(cwd, violation.path);
|
|
74
|
-
if (!existsSync(join(cwd, violation.path))) {
|
|
75
|
-
return {
|
|
76
|
-
applied: true,
|
|
77
|
-
message: `Made ${violation.path} trackable; file does not exist yet`,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
try {
|
|
81
|
-
await runGit(["add", "--", violation.path], cwd);
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
return {
|
|
85
|
-
applied: false,
|
|
86
|
-
message: `Removed .gitignore line but git add failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
applied: true,
|
|
91
|
-
message: `Removed .gitignore line and staged ${violation.path}`,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
case "untracked-and-should-track": {
|
|
95
|
-
await appendTrackExceptionsIfNeeded(cwd, violation.path);
|
|
96
|
-
await runGit(["add", "--", violation.path], cwd);
|
|
97
|
-
return { applied: true, message: `Staged ${violation.path}` };
|
|
98
|
-
}
|
|
99
|
-
case "missing-from-gitignore": {
|
|
100
|
-
// Treat as a safe append for symmetry — caller can route through here too.
|
|
101
|
-
await appendIfMissing(cwd, rulePattern);
|
|
102
|
-
return { applied: true, message: `Added ${rulePattern} to .gitignore` };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
export function formatViolationSummary(violations) {
|
|
107
|
-
if (violations.length === 0)
|
|
108
|
-
return "No gitignore issues detected.";
|
|
109
|
-
const byKind = new Map();
|
|
110
|
-
for (const v of violations) {
|
|
111
|
-
const arr = byKind.get(v.kind) ?? [];
|
|
112
|
-
arr.push(v);
|
|
113
|
-
byKind.set(v.kind, arr);
|
|
114
|
-
}
|
|
115
|
-
const labels = {
|
|
116
|
-
"tracked-but-should-ignore": "tracked but should be ignored",
|
|
117
|
-
"ignored-but-should-track": "ignored but should be tracked",
|
|
118
|
-
"untracked-and-should-track": "exist but not tracked (should be)",
|
|
119
|
-
"missing-from-gitignore": "missing from .gitignore",
|
|
120
|
-
};
|
|
121
|
-
const lines = [];
|
|
122
|
-
for (const [kind, vs] of byKind.entries()) {
|
|
123
|
-
lines.push(` ${labels[kind]}: ${vs.length}`);
|
|
124
|
-
for (const v of vs.slice(0, 5)) {
|
|
125
|
-
lines.push(` - ${v.path}`);
|
|
126
|
-
}
|
|
127
|
-
if (vs.length > 5)
|
|
128
|
-
lines.push(` … and ${vs.length - 5} more`);
|
|
129
|
-
}
|
|
130
|
-
return lines.join("\n");
|
|
131
|
-
}
|
|
132
|
-
async function appendIfMissing(cwd, pattern) {
|
|
133
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
134
|
-
const existing = existsSync(gitignorePath)
|
|
135
|
-
? await readFile(gitignorePath, "utf8")
|
|
136
|
-
: "";
|
|
137
|
-
const lines = existing.split("\n").map((l) => l.trim());
|
|
138
|
-
if (lines.includes(pattern))
|
|
139
|
-
return;
|
|
140
|
-
// Only emit the "# Added by claudeup" header once — when fixing several
|
|
141
|
-
// entries in one pass (F), the file already has the header from the first.
|
|
142
|
-
const needsHeader = !lines.includes(CLAUDEUP_HEADER);
|
|
143
|
-
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
144
|
-
const header = needsHeader ? `${CLAUDEUP_HEADER}\n` : "";
|
|
145
|
-
const block = `${prefix}${header}${formatGitignoreEntry(pattern).join("\n")}\n`;
|
|
146
|
-
await appendFile(gitignorePath, block);
|
|
147
|
-
}
|
|
148
|
-
function formatGitignoreEntry(pattern) {
|
|
149
|
-
return [`# ${getGitignoreRuleReason("ignore", pattern)}`, pattern];
|
|
150
|
-
}
|
|
151
|
-
async function appendTrackExceptionsIfNeeded(cwd, path) {
|
|
152
|
-
if (!(await pathIsIgnored(cwd, path)))
|
|
153
|
-
return;
|
|
154
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
155
|
-
const existing = existsSync(gitignorePath)
|
|
156
|
-
? await readFile(gitignorePath, "utf8")
|
|
157
|
-
: "";
|
|
158
|
-
const lines = new Set(existing.split("\n").map((line) => line.trim()).filter(Boolean));
|
|
159
|
-
const additions = unignorePatternsForPath(path).filter((line) => !lines.has(line));
|
|
160
|
-
if (additions.length === 0)
|
|
161
|
-
return;
|
|
162
|
-
const block = [
|
|
163
|
-
existing.length > 0 && !existing.endsWith("\n") ? "\n" : "",
|
|
164
|
-
CLAUDEUP_HEADER,
|
|
165
|
-
`# ${getGitignoreRuleReason("track", path)}`,
|
|
166
|
-
...additions,
|
|
167
|
-
"",
|
|
168
|
-
].join("\n");
|
|
169
|
-
await appendFile(gitignorePath, block);
|
|
170
|
-
}
|
|
171
|
-
function unignorePatternsForPath(path) {
|
|
172
|
-
const normalized = path.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
173
|
-
if (!normalized)
|
|
174
|
-
return [];
|
|
175
|
-
const parts = normalized.split("/");
|
|
176
|
-
const patterns = [];
|
|
177
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
178
|
-
patterns.push(`!/${parts.slice(0, i + 1).join("/")}/`);
|
|
179
|
-
}
|
|
180
|
-
patterns.push(`!/${normalized}`);
|
|
181
|
-
return patterns;
|
|
182
|
-
}
|
|
183
|
-
async function stripGitignoreLine(cwd, pattern) {
|
|
184
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
185
|
-
if (!existsSync(gitignorePath))
|
|
186
|
-
return;
|
|
187
|
-
const content = await readFile(gitignorePath, "utf8");
|
|
188
|
-
const filtered = content
|
|
189
|
-
.split("\n")
|
|
190
|
-
.filter((line) => line.trim() !== pattern)
|
|
191
|
-
.join("\n");
|
|
192
|
-
if (filtered !== content) {
|
|
193
|
-
await writeFile(gitignorePath, filtered);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
async function pathIsIgnored(cwd, path) {
|
|
197
|
-
try {
|
|
198
|
-
await runGit(["check-ignore", "--quiet", "--", path], cwd);
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
catch (err) {
|
|
202
|
-
if (err instanceof GitError && err.code === 1)
|
|
203
|
-
return false;
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
class GitError extends Error {
|
|
208
|
-
code;
|
|
209
|
-
stderr;
|
|
210
|
-
constructor(code, stderr) {
|
|
211
|
-
super(`git exited ${code}: ${stderr}`);
|
|
212
|
-
this.code = code;
|
|
213
|
-
this.stderr = stderr;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
function runGit(args, cwd) {
|
|
217
|
-
return new Promise((resolve, reject) => {
|
|
218
|
-
const proc = spawn("git", args, { cwd });
|
|
219
|
-
let stdout = "";
|
|
220
|
-
let stderr = "";
|
|
221
|
-
proc.stdout.on("data", (b) => { stdout += b.toString(); });
|
|
222
|
-
proc.stderr.on("data", (b) => { stderr += b.toString(); });
|
|
223
|
-
proc.on("error", reject);
|
|
224
|
-
proc.on("close", (code) => {
|
|
225
|
-
if (code === 0)
|
|
226
|
-
resolve(stdout);
|
|
227
|
-
else
|
|
228
|
-
reject(new GitError(code ?? -1, stderr));
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prerunner integration for the gitignore manager.
|
|
3
|
-
*
|
|
4
|
-
* Called from `prerunner/index.ts` (Step 0.9) on every claudeup invocation.
|
|
5
|
-
* Resolves the manifest, runs detection, and prints a concise warning when
|
|
6
|
-
* violations exist. Does NOT auto-apply fixes — the user must open the
|
|
7
|
-
* Git State tab and confirm. This mirrors the plugin-version-mismatch flow
|
|
8
|
-
* where prerun *warns* and the TUI provides the interactive fix dialog.
|
|
9
|
-
*/
|
|
10
|
-
import { resolveGitignore } from "./gitignore-resolver.js";
|
|
11
|
-
import { detectViolations } from "./gitignore-detector.js";
|
|
12
|
-
import { formatViolationSummary } from "./gitignore-fixer.js";
|
|
13
|
-
export async function checkGitignore(cwd) {
|
|
14
|
-
const warnings = [];
|
|
15
|
-
try {
|
|
16
|
-
const resolved = await resolveGitignore(cwd, {
|
|
17
|
-
logger: (m) => warnings.push(m),
|
|
18
|
-
});
|
|
19
|
-
const violations = await detectViolations(cwd, resolved);
|
|
20
|
-
return {
|
|
21
|
-
violationCount: violations.length,
|
|
22
|
-
warnings,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
warnings.push(`[gitignore] check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
27
|
-
return { violationCount: 0, warnings };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* One-line summary suitable for prerun stderr. Returns null when there's
|
|
32
|
-
* nothing to warn about.
|
|
33
|
-
*/
|
|
34
|
-
export function formatPrerunWarning(result) {
|
|
35
|
-
if (result.violationCount === 0)
|
|
36
|
-
return null;
|
|
37
|
-
return `⚠ ${result.violationCount} git state issue(s) detected. Run: claudeup → Git State tab to review and fix`;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Verbose multi-line summary, used by the Git State tab when the user wants
|
|
41
|
-
* to know what's wrong before deciding what to do.
|
|
42
|
-
*/
|
|
43
|
-
export async function describeViolations(cwd, violations) {
|
|
44
|
-
void cwd;
|
|
45
|
-
return formatViolationSummary(violations);
|
|
46
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Three-tier gitignore manifest resolver.
|
|
3
|
-
*
|
|
4
|
-
* Precedence (lowest → highest, last-wins):
|
|
5
|
-
* 1. Built-in defaults (shipped with claudeup)
|
|
6
|
-
* 2. Global manifest at ~/.claude/gitignore.json (team-level, manually shared)
|
|
7
|
-
* 3. Project manifest at <cwd>/.claude/gitignore.json
|
|
8
|
-
*
|
|
9
|
-
* `extends` references are resolved against BUILTIN_TEMPLATES. Within a single
|
|
10
|
-
* tier, the same pattern cannot appear in both `ignore` and `track` — that
|
|
11
|
-
* throws. Across tiers, the higher-precedence tier wins; losers are recorded
|
|
12
|
-
* in `conflicts` so the TUI can show "global says ignore .env, project
|
|
13
|
-
* overrides to track."
|
|
14
|
-
*/
|
|
15
|
-
import { readFile } from "node:fs/promises";
|
|
16
|
-
import { join } from "node:path";
|
|
17
|
-
import { homedir } from "node:os";
|
|
18
|
-
import { existsSync } from "node:fs";
|
|
19
|
-
import { BUILTIN_DEFAULTS } from "../data/gitignore-defaults.js";
|
|
20
|
-
import { BUILTIN_TEMPLATES } from "../data/gitignore-templates.js";
|
|
21
|
-
const TIER_ORDER = ["builtin", "global", "project"];
|
|
22
|
-
export async function resolveGitignore(projectCwd, opts = {}) {
|
|
23
|
-
const home = opts.homeDir ?? homedir();
|
|
24
|
-
const log = opts.logger ?? (() => { });
|
|
25
|
-
const tierManifests = new Map();
|
|
26
|
-
// 1. Built-in (always present)
|
|
27
|
-
tierManifests.set("builtin", BUILTIN_DEFAULTS);
|
|
28
|
-
// 2. Global ~/.claude/gitignore.json
|
|
29
|
-
const globalPath = join(home, ".claude", "gitignore.json");
|
|
30
|
-
const globalManifest = await readManifest(globalPath, log);
|
|
31
|
-
if (globalManifest)
|
|
32
|
-
tierManifests.set("global", globalManifest);
|
|
33
|
-
// 3. Project .claude/gitignore.json
|
|
34
|
-
const projectPath = join(projectCwd, ".claude", "gitignore.json");
|
|
35
|
-
const projectManifest = await readManifest(projectPath, log);
|
|
36
|
-
if (projectManifest)
|
|
37
|
-
tierManifests.set("project", projectManifest);
|
|
38
|
-
// Apply `extends` for each tier (templates count as part of their tier)
|
|
39
|
-
for (const tier of TIER_ORDER) {
|
|
40
|
-
const m = tierManifests.get(tier);
|
|
41
|
-
if (!m?.extends?.length)
|
|
42
|
-
continue;
|
|
43
|
-
const merged = {
|
|
44
|
-
ignore: [...m.ignore],
|
|
45
|
-
track: [...m.track],
|
|
46
|
-
};
|
|
47
|
-
for (const tplName of m.extends) {
|
|
48
|
-
const tpl = BUILTIN_TEMPLATES[tplName];
|
|
49
|
-
if (!tpl) {
|
|
50
|
-
log(`[gitignore] unknown template "${tplName}" in ${tier} manifest`);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
merged.ignore.push(...tpl.ignore);
|
|
54
|
-
merged.track.push(...tpl.track);
|
|
55
|
-
}
|
|
56
|
-
tierManifests.set(tier, merged);
|
|
57
|
-
}
|
|
58
|
-
// Validate intra-tier consistency
|
|
59
|
-
for (const [tier, m] of tierManifests.entries()) {
|
|
60
|
-
const ig = new Set(m.ignore);
|
|
61
|
-
for (const t of m.track) {
|
|
62
|
-
if (ig.has(t)) {
|
|
63
|
-
throw new Error(`Intra-tier conflict in ${tier} manifest: "${t}" is in both ignore and track`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// Merge across tiers — last-wins, track conflicts
|
|
68
|
-
const ruleByPattern = new Map();
|
|
69
|
-
for (const tier of TIER_ORDER) {
|
|
70
|
-
const m = tierManifests.get(tier);
|
|
71
|
-
if (!m)
|
|
72
|
-
continue;
|
|
73
|
-
for (const p of dedupe(m.ignore)) {
|
|
74
|
-
pushRule(ruleByPattern, p, "ignore", tier);
|
|
75
|
-
}
|
|
76
|
-
for (const p of dedupe(m.track)) {
|
|
77
|
-
pushRule(ruleByPattern, p, "track", tier);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const rules = [];
|
|
81
|
-
const conflicts = [];
|
|
82
|
-
for (const [pattern, entry] of ruleByPattern.entries()) {
|
|
83
|
-
rules.push({ pattern, action: entry.action, source: entry.source });
|
|
84
|
-
const losers = entry.history.filter((h) => h.tier !== entry.source || h.action !== entry.action);
|
|
85
|
-
if (losers.length > 0 && hasActionDisagreement(entry.history)) {
|
|
86
|
-
conflicts.push({
|
|
87
|
-
pattern,
|
|
88
|
-
winner: entry.source,
|
|
89
|
-
losers: losers.filter((l) => l.action !== entry.action),
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return { rules, conflicts };
|
|
94
|
-
}
|
|
95
|
-
function pushRule(map, pattern, action, tier) {
|
|
96
|
-
const existing = map.get(pattern);
|
|
97
|
-
if (existing) {
|
|
98
|
-
existing.history.push({ tier, action });
|
|
99
|
-
existing.action = action;
|
|
100
|
-
existing.source = tier;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
map.set(pattern, { action, source: tier, history: [{ tier, action }] });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function hasActionDisagreement(history) {
|
|
107
|
-
const actions = new Set(history.map((h) => h.action));
|
|
108
|
-
return actions.size > 1;
|
|
109
|
-
}
|
|
110
|
-
function dedupe(xs) {
|
|
111
|
-
return Array.from(new Set(xs));
|
|
112
|
-
}
|
|
113
|
-
async function readManifest(path, log) {
|
|
114
|
-
if (!existsSync(path))
|
|
115
|
-
return null;
|
|
116
|
-
try {
|
|
117
|
-
const raw = await readFile(path, "utf8");
|
|
118
|
-
const parsed = JSON.parse(raw);
|
|
119
|
-
if (!isManifest(parsed)) {
|
|
120
|
-
log(`[gitignore] ${path} is not a valid manifest (expected { ignore: string[], track: string[] })`);
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
return parsed;
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
log(`[gitignore] failed to read ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function isManifest(x) {
|
|
131
|
-
if (!x || typeof x !== "object")
|
|
132
|
-
return false;
|
|
133
|
-
const m = x;
|
|
134
|
-
if (!Array.isArray(m.ignore) || !m.ignore.every((p) => typeof p === "string"))
|
|
135
|
-
return false;
|
|
136
|
-
if (!Array.isArray(m.track) || !m.track.every((p) => typeof p === "string"))
|
|
137
|
-
return false;
|
|
138
|
-
if (m.extends !== undefined) {
|
|
139
|
-
if (!Array.isArray(m.extends) || !m.extends.every((p) => typeof p === "string"))
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TUI-facing wrapper around the gitignore core services. Loads everything
|
|
3
|
-
* the Gitignore screen needs in one call, and exposes a small set of
|
|
4
|
-
* action helpers (apply safe fixes, apply one destructive fix, apply a
|
|
5
|
-
* built-in template).
|
|
6
|
-
*/
|
|
7
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
import { join, dirname } from "node:path";
|
|
10
|
-
import { resolveGitignore } from "./gitignore-resolver.js";
|
|
11
|
-
import { detectViolations } from "./gitignore-detector.js";
|
|
12
|
-
import { applySafeFixes as coreApplySafeFixes, applyDestructiveFix as coreApplyDestructiveFix, } from "./gitignore-fixer.js";
|
|
13
|
-
import { BUILTIN_TEMPLATES } from "../data/gitignore-templates.js";
|
|
14
|
-
export async function loadGitignoreState(cwd) {
|
|
15
|
-
const warnings = [];
|
|
16
|
-
const resolved = await resolveGitignore(cwd, {
|
|
17
|
-
logger: (m) => warnings.push(m),
|
|
18
|
-
});
|
|
19
|
-
const violations = await detectViolations(cwd, resolved);
|
|
20
|
-
return { resolved, violations, warnings };
|
|
21
|
-
}
|
|
22
|
-
export async function applyAllSafeFixes(cwd, violations) {
|
|
23
|
-
return coreApplySafeFixes(cwd, violations);
|
|
24
|
-
}
|
|
25
|
-
export async function applyDestructiveFix(cwd, violation, rulePattern) {
|
|
26
|
-
return coreApplyDestructiveFix(cwd, violation, rulePattern);
|
|
27
|
-
}
|
|
28
|
-
export async function ensureProjectManifestForEdit(cwd, resolved) {
|
|
29
|
-
const projectManifestPath = join(cwd, ".claude", "gitignore.json");
|
|
30
|
-
if (existsSync(projectManifestPath)) {
|
|
31
|
-
const existing = await readFile(projectManifestPath, "utf8");
|
|
32
|
-
if (existing.trim().length > 0)
|
|
33
|
-
return projectManifestPath;
|
|
34
|
-
}
|
|
35
|
-
const manifest = { ignore: [], track: [] };
|
|
36
|
-
for (const rule of resolved.rules) {
|
|
37
|
-
if (rule.action === "ignore")
|
|
38
|
-
manifest.ignore.push(rule.pattern);
|
|
39
|
-
else
|
|
40
|
-
manifest.track.push(rule.pattern);
|
|
41
|
-
}
|
|
42
|
-
await mkdir(dirname(projectManifestPath), { recursive: true });
|
|
43
|
-
await writeFile(projectManifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
44
|
-
return projectManifestPath;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Merge a built-in template into the project manifest at .claude/gitignore.json.
|
|
48
|
-
* Conflicts (a pattern that already exists with the opposite action) are
|
|
49
|
-
* skipped and reported back to the caller.
|
|
50
|
-
*/
|
|
51
|
-
export async function applyTemplate(cwd, templateName) {
|
|
52
|
-
const tpl = BUILTIN_TEMPLATES[templateName];
|
|
53
|
-
if (!tpl)
|
|
54
|
-
throw new Error(`Unknown template: ${templateName}`);
|
|
55
|
-
const projectManifestPath = join(cwd, ".claude", "gitignore.json");
|
|
56
|
-
const existing = await readManifestOrEmpty(projectManifestPath);
|
|
57
|
-
const existingIgnore = new Set(existing.ignore);
|
|
58
|
-
const existingTrack = new Set(existing.track);
|
|
59
|
-
const conflicts = [];
|
|
60
|
-
const newIgnore = [];
|
|
61
|
-
const newTrack = [];
|
|
62
|
-
for (const p of tpl.ignore) {
|
|
63
|
-
if (existingTrack.has(p)) {
|
|
64
|
-
conflicts.push(p);
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (!existingIgnore.has(p)) {
|
|
68
|
-
newIgnore.push(p);
|
|
69
|
-
existingIgnore.add(p);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (const p of tpl.track) {
|
|
73
|
-
if (existingIgnore.has(p)) {
|
|
74
|
-
conflicts.push(p);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (!existingTrack.has(p)) {
|
|
78
|
-
newTrack.push(p);
|
|
79
|
-
existingTrack.add(p);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const merged = {
|
|
83
|
-
ignore: [...existing.ignore, ...newIgnore],
|
|
84
|
-
track: [...existing.track, ...newTrack],
|
|
85
|
-
extends: existing.extends,
|
|
86
|
-
};
|
|
87
|
-
await mkdir(dirname(projectManifestPath), { recursive: true });
|
|
88
|
-
await writeFile(projectManifestPath, JSON.stringify(merged, null, 2) + "\n");
|
|
89
|
-
return {
|
|
90
|
-
appliedTo: projectManifestPath,
|
|
91
|
-
added: { ignore: newIgnore, track: newTrack },
|
|
92
|
-
conflicts,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
async function readManifestOrEmpty(path) {
|
|
96
|
-
if (!existsSync(path))
|
|
97
|
-
return { ignore: [], track: [] };
|
|
98
|
-
try {
|
|
99
|
-
const raw = await readFile(path, "utf8");
|
|
100
|
-
const parsed = JSON.parse(raw);
|
|
101
|
-
if (!isManifest(parsed))
|
|
102
|
-
return { ignore: [], track: [] };
|
|
103
|
-
return parsed;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return { ignore: [], track: [] };
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function isManifest(x) {
|
|
110
|
-
if (!x || typeof x !== "object")
|
|
111
|
-
return false;
|
|
112
|
-
const m = x;
|
|
113
|
-
return (Array.isArray(m.ignore) &&
|
|
114
|
-
m.ignore.every((p) => typeof p === "string") &&
|
|
115
|
-
Array.isArray(m.track) &&
|
|
116
|
-
m.track.every((p) => typeof p === "string"));
|
|
117
|
-
}
|