claudeup 6.2.1 → 6.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/bin/claudeup.js +6 -1
- package/package.json +6 -6
- package/src/__tests__/alias-adopt.test.ts +331 -329
- package/src/__tests__/alias-parser.test.ts +384 -376
- package/src/__tests__/alias-shell-writer.test.ts +638 -625
- package/src/__tests__/alias-store.test.ts +143 -143
- package/src/__tests__/catalog-cache-store.test.ts +265 -265
- package/src/__tests__/catalog-notice.test.ts +123 -123
- package/src/__tests__/cli-apply-seams.test.ts +12 -8
- package/src/__tests__/cli-router.test.ts +132 -3
- package/src/__tests__/cli-update-view.test.ts +27 -8
- package/src/__tests__/community-staleness.test.ts +0 -2
- package/src/__tests__/component-badge.test.ts +4 -4
- package/src/__tests__/content-drift.test.ts +5 -1
- package/src/__tests__/conventions-integration.test.ts +774 -689
- package/src/__tests__/conventions-manager.test.ts +1216 -1152
- package/src/__tests__/doctor-bins.test.ts +14 -6
- package/src/__tests__/doctor.test.ts +438 -425
- package/src/__tests__/dual-write-prevention.test.ts +277 -282
- package/src/__tests__/enabled-not-installed.test.ts +71 -63
- package/src/__tests__/file-locking.test.ts +196 -196
- package/src/__tests__/gap-fill-versions.test.ts +318 -311
- package/src/__tests__/github-budget.test.ts +170 -164
- package/src/__tests__/gitignore-detector.test.ts +160 -152
- package/src/__tests__/gitignore-fixer.test.ts +285 -247
- package/src/__tests__/gitignore-prerun.test.ts +57 -57
- package/src/__tests__/gitignore-resolver.test.ts +173 -146
- package/src/__tests__/gitignore-service.test.ts +121 -119
- package/src/__tests__/install-command.test.ts +3 -3
- package/src/__tests__/install-plan.test.ts +13 -7
- package/src/__tests__/manifest.test.ts +16 -5
- package/src/__tests__/markdown-renderer.test.ts +0 -1
- package/src/__tests__/marketplace-badge.test.ts +111 -90
- package/src/__tests__/marketplace-version.test.ts +64 -61
- package/src/__tests__/plugin-manager-fallback.test.ts +249 -239
- package/src/__tests__/plugin-requires.test.ts +18 -4
- package/src/__tests__/plugin-setup.test.ts +12 -12
- package/src/__tests__/profile-command.test.ts +1 -7
- package/src/__tests__/profile-materializer.test.ts +31 -8
- package/src/__tests__/profile-sync.test.ts +23 -5
- package/src/__tests__/registry-version-resolution.test.ts +61 -47
- package/src/__tests__/resolve-executable.test.ts +1 -1
- package/src/__tests__/resolver.test.ts +40 -15
- package/src/__tests__/scope-action.test.ts +52 -50
- package/src/__tests__/scope-squares.test.tsx +4 -5
- package/src/__tests__/skill-install-files.test.ts +111 -103
- package/src/__tests__/symlink-manager.test.ts +27 -10
- package/src/__tests__/theme-env.test.ts +281 -0
- package/src/__tests__/theme-resolve.test.ts +380 -0
- package/src/__tests__/update-apply.test.ts +1 -3
- package/src/__tests__/useGitignoreModal.test.ts +69 -61
- package/src/__tests__/version-snapshot.test.ts +181 -165
- package/src/cli/ansi.ts +23 -15
- package/src/cli/doctor.ts +10 -9
- package/src/cli/install.ts +3 -3
- package/src/cli/profile.ts +5 -4
- package/src/cli/prompt.ts +0 -1
- package/src/cli/router.ts +69 -2
- package/src/cli/update-view.ts +11 -6
- package/src/cli/update.ts +9 -4
- package/src/cli/upgrade.ts +6 -4
- package/src/data/alias-flags.ts +260 -260
- package/src/data/gitignore-defaults.ts +20 -20
- package/src/data/gitignore-reasons.ts +86 -92
- package/src/data/gitignore-templates.ts +14 -14
- package/src/data/predefined-profiles.ts +8 -9
- package/src/data/skill-repos.ts +43 -35
- package/src/main.tsx +29 -6
- package/src/opentui.d.ts +1 -3
- package/src/prerunner/index.ts +7 -8
- package/src/services/alias-settings.ts +20 -23
- package/src/services/alias-shell-writer.ts +800 -803
- package/src/services/alias-store.ts +77 -77
- package/src/services/claude-settings.ts +27 -18
- package/src/services/content-drift.ts +13 -3
- package/src/services/conventions-manager.ts +692 -617
- package/src/services/doctor.ts +464 -403
- package/src/services/gitignore-detector.ts +113 -103
- package/src/services/gitignore-fixer.ts +204 -189
- package/src/services/gitignore-prerun.ts +26 -26
- package/src/services/gitignore-resolver.ts +162 -144
- package/src/services/gitignore-service.ts +120 -117
- package/src/services/install-plan.ts +4 -4
- package/src/services/manifest.ts +1 -1
- package/src/services/marketplace-catalog-git.ts +5 -2
- package/src/services/marketplace-fetcher.ts +241 -235
- package/src/services/plugin-manager.ts +17 -20
- package/src/services/plugin-mcp-config.ts +2 -2
- package/src/services/plugin-requires.ts +9 -6
- package/src/services/plugin-setup.ts +7 -9
- package/src/services/plugin-version-check.ts +4 -4
- package/src/services/profile-materializer.ts +9 -5
- package/src/services/profile-sync.ts +12 -4
- package/src/services/profiles.ts +3 -3
- package/src/services/resolver.ts +10 -6
- package/src/services/settings-manager.ts +82 -46
- package/src/services/skills-manager.ts +11 -9
- package/src/services/symlink-manager.ts +2 -5
- package/src/services/toolchain.ts +3 -3
- package/src/services/update-cache.ts +1 -1
- package/src/services/update-engine.ts +4 -4
- package/src/services/update-plan.ts +9 -11
- package/src/services/version-snapshot.ts +88 -88
- package/src/types/gitignore.ts +28 -28
- package/src/ui/App.tsx +36 -36
- package/src/ui/adapters/settingsAdapter.ts +2 -2
- package/src/ui/components/CategoryHeader.tsx +1 -1
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +3 -1
- package/src/ui/components/ScrollableList.tsx +2 -1
- package/src/ui/components/StyledText.tsx +1 -1
- package/src/ui/components/layout/FooterHints.tsx +5 -3
- package/src/ui/components/layout/Panel.tsx +2 -2
- package/src/ui/components/layout/ScreenLayout.tsx +5 -4
- package/src/ui/components/modals/InputModal.tsx +7 -1
- package/src/ui/components/modals/ModalContainer.tsx +3 -3
- package/src/ui/components/modals/SelectModal.tsx +1 -1
- package/src/ui/components/primitives/DetailSection.tsx +1 -1
- package/src/ui/components/primitives/KeyValueLine.tsx +1 -1
- package/src/ui/components/primitives/ScopeSquares.tsx +2 -2
- package/src/ui/hooks/index.ts +0 -1
- package/src/ui/hooks/useGitignoreModal.ts +1 -5
- package/src/ui/hooks/useMismatchModal.ts +4 -10
- package/src/ui/renderers/cliToolRenderers.tsx +2 -2
- package/src/ui/renderers/gitignoreRenderers.tsx +2 -2
- package/src/ui/renderers/mcpRenderers.tsx +1 -1
- package/src/ui/renderers/pluginRenderers.tsx +3 -9
- package/src/ui/renderers/profileRenderers.tsx +9 -8
- package/src/ui/renderers/settingsRenderers.tsx +4 -4
- package/src/ui/renderers/skillRenderers.tsx +14 -14
- package/src/ui/screens/AliasScreen.tsx +31 -34
- package/src/ui/screens/CliToolsScreen.tsx +14 -13
- package/src/ui/screens/EnvVarsScreen.tsx +8 -8
- package/src/ui/screens/GitignoreScreen.tsx +15 -14
- package/src/ui/screens/McpRegistryScreen.tsx +10 -7
- package/src/ui/screens/McpScreen.tsx +10 -10
- package/src/ui/screens/PluginsScreen.tsx +17 -21
- package/src/ui/screens/ProfilesScreen.tsx +25 -24
- package/src/ui/screens/SkillsScreen.tsx +16 -14
- package/src/ui/state/AppContext.tsx +4 -8
- package/src/ui/state/DimensionsContext.tsx +3 -2
- package/src/ui/theme-mode.ts +20 -38
- package/src/ui/theme-resolve.ts +148 -0
- package/src/utils/clipboard.ts +5 -5
- package/src/utils/command-utils.ts +2 -2
- package/src/utils/config-dir.ts +1 -3
- package/src/utils/file-locking.ts +88 -86
- package/src/ui/hooks/useAsyncData.ts +0 -127
- package/src/ui/state/AnimationContext.tsx +0 -76
|
@@ -15,251 +15,266 @@
|
|
|
15
15
|
* `formatViolationSummary` is a pure helper for prerun warning + TUI.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { spawn } from "node:child_process";
|
|
19
19
|
import { existsSync } from "node:fs";
|
|
20
|
+
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
20
21
|
import { join } from "node:path";
|
|
21
|
-
import { spawn } from "node:child_process";
|
|
22
22
|
import { getGitignoreRuleReason } from "../data/gitignore-reasons.js";
|
|
23
23
|
import type { Violation } from "../types/gitignore.js";
|
|
24
24
|
|
|
25
25
|
const CLAUDEUP_HEADER = "# Added by claudeup";
|
|
26
26
|
|
|
27
27
|
export interface SafeFixResult {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
appended: string[];
|
|
29
|
+
alreadyPresent: string[];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export async function applySafeFixes(
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
cwd: string,
|
|
34
|
+
violations: Violation[],
|
|
35
35
|
): Promise<SafeFixResult> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const safe = violations.filter(
|
|
37
|
+
(v) => v.severity === "safe" && v.kind === "missing-from-gitignore",
|
|
38
|
+
);
|
|
39
|
+
if (safe.length === 0) {
|
|
40
|
+
return { appended: [], alreadyPresent: [] };
|
|
41
|
+
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
44
|
+
const existing = existsSync(gitignorePath)
|
|
45
|
+
? await readFile(gitignorePath, "utf8")
|
|
46
|
+
: "";
|
|
47
|
+
const lines = new Set(
|
|
48
|
+
existing
|
|
49
|
+
.split("\n")
|
|
50
|
+
.map((l) => l.trim())
|
|
51
|
+
.filter(Boolean),
|
|
52
|
+
);
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
const appended: string[] = [];
|
|
55
|
+
const alreadyPresent: string[] = [];
|
|
56
|
+
for (const v of safe) {
|
|
57
|
+
if (lines.has(v.path)) {
|
|
58
|
+
alreadyPresent.push(v.path);
|
|
59
|
+
} else {
|
|
60
|
+
appended.push(v.path);
|
|
61
|
+
lines.add(v.path);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
if (appended.length === 0) return { appended, alreadyPresent };
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const block = [
|
|
68
|
+
"",
|
|
69
|
+
CLAUDEUP_HEADER,
|
|
70
|
+
...appended.flatMap(formatGitignoreEntry),
|
|
71
|
+
"",
|
|
72
|
+
].join("\n");
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
if (existing.length > 0 && !existing.endsWith("\n")) {
|
|
75
|
+
await appendFile(gitignorePath, `\n${block}`);
|
|
76
|
+
} else {
|
|
77
|
+
await appendFile(gitignorePath, block);
|
|
78
|
+
}
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
return { appended, alreadyPresent };
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
export interface DestructiveFixResult {
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
applied: boolean;
|
|
85
|
+
message: string;
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
export async function applyDestructiveFix(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
cwd: string,
|
|
90
|
+
violation: Violation,
|
|
91
|
+
rulePattern = violation.path,
|
|
89
92
|
): Promise<DestructiveFixResult> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
93
|
+
switch (violation.kind) {
|
|
94
|
+
case "tracked-but-should-ignore": {
|
|
95
|
+
await runGit(["rm", "--cached", "--", violation.path], cwd);
|
|
96
|
+
// Then make sure .gitignore has the entry so it stays out
|
|
97
|
+
await appendIfMissing(cwd, rulePattern);
|
|
98
|
+
return {
|
|
99
|
+
applied: true,
|
|
100
|
+
message: `Removed ${violation.path} from index and added ${rulePattern} to .gitignore`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
case "ignored-but-should-track": {
|
|
104
|
+
await stripGitignoreLine(cwd, violation.path);
|
|
105
|
+
await appendTrackExceptionsIfNeeded(cwd, violation.path);
|
|
106
|
+
if (!existsSync(join(cwd, violation.path))) {
|
|
107
|
+
return {
|
|
108
|
+
applied: true,
|
|
109
|
+
message: `Made ${violation.path} trackable; file does not exist yet`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
await runGit(["add", "--", violation.path], cwd);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return {
|
|
116
|
+
applied: false,
|
|
117
|
+
message: `Removed .gitignore line but git add failed: ${
|
|
118
|
+
err instanceof Error ? err.message : String(err)
|
|
119
|
+
}`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
applied: true,
|
|
124
|
+
message: `Removed .gitignore line and staged ${violation.path}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
case "untracked-and-should-track": {
|
|
128
|
+
await appendTrackExceptionsIfNeeded(cwd, violation.path);
|
|
129
|
+
await runGit(["add", "--", violation.path], cwd);
|
|
130
|
+
return { applied: true, message: `Staged ${violation.path}` };
|
|
131
|
+
}
|
|
132
|
+
case "missing-from-gitignore": {
|
|
133
|
+
// Treat as a safe append for symmetry — caller can route through here too.
|
|
134
|
+
await appendIfMissing(cwd, rulePattern);
|
|
135
|
+
return { applied: true, message: `Added ${rulePattern} to .gitignore` };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
export function formatViolationSummary(violations: Violation[]): string {
|
|
138
|
-
|
|
141
|
+
if (violations.length === 0) return "No gitignore issues detected.";
|
|
139
142
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
const byKind = new Map<string, Violation[]>();
|
|
144
|
+
for (const v of violations) {
|
|
145
|
+
const arr = byKind.get(v.kind) ?? [];
|
|
146
|
+
arr.push(v);
|
|
147
|
+
byKind.set(v.kind, arr);
|
|
148
|
+
}
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
const labels: Record<Violation["kind"], string> = {
|
|
151
|
+
"tracked-but-should-ignore": "tracked but should be ignored",
|
|
152
|
+
"ignored-but-should-track": "ignored but should be tracked",
|
|
153
|
+
"untracked-and-should-track": "exist but not tracked (should be)",
|
|
154
|
+
"missing-from-gitignore": "missing from .gitignore",
|
|
155
|
+
};
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
const lines: string[] = [];
|
|
158
|
+
for (const [kind, vs] of byKind.entries()) {
|
|
159
|
+
lines.push(` ${labels[kind as Violation["kind"]]}: ${vs.length}`);
|
|
160
|
+
for (const v of vs.slice(0, 5)) {
|
|
161
|
+
lines.push(` - ${v.path}`);
|
|
162
|
+
}
|
|
163
|
+
if (vs.length > 5) lines.push(` … and ${vs.length - 5} more`);
|
|
164
|
+
}
|
|
165
|
+
return lines.join("\n");
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
async function appendIfMissing(cwd: string, pattern: string): Promise<void> {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
170
|
+
const existing = existsSync(gitignorePath)
|
|
171
|
+
? await readFile(gitignorePath, "utf8")
|
|
172
|
+
: "";
|
|
173
|
+
const lines = existing.split("\n").map((l) => l.trim());
|
|
174
|
+
if (lines.includes(pattern)) return;
|
|
175
|
+
// Only emit the "# Added by claudeup" header once — when fixing several
|
|
176
|
+
// entries in one pass (F), the file already has the header from the first.
|
|
177
|
+
const needsHeader = !lines.includes(CLAUDEUP_HEADER);
|
|
178
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
179
|
+
const header = needsHeader ? `${CLAUDEUP_HEADER}\n` : "";
|
|
180
|
+
const block = `${prefix}${header}${formatGitignoreEntry(pattern).join("\n")}\n`;
|
|
181
|
+
await appendFile(gitignorePath, block);
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
function formatGitignoreEntry(pattern: string): string[] {
|
|
182
|
-
|
|
185
|
+
return [`# ${getGitignoreRuleReason("ignore", pattern)}`, pattern];
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
async function appendTrackExceptionsIfNeeded(
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
cwd: string,
|
|
190
|
+
path: string,
|
|
188
191
|
): Promise<void> {
|
|
189
|
-
|
|
192
|
+
if (!(await pathIsIgnored(cwd, path))) return;
|
|
190
193
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
195
|
+
const existing = existsSync(gitignorePath)
|
|
196
|
+
? await readFile(gitignorePath, "utf8")
|
|
197
|
+
: "";
|
|
198
|
+
const lines = new Set(
|
|
199
|
+
existing
|
|
200
|
+
.split("\n")
|
|
201
|
+
.map((line) => line.trim())
|
|
202
|
+
.filter(Boolean),
|
|
203
|
+
);
|
|
204
|
+
const additions = unignorePatternsForPath(path).filter(
|
|
205
|
+
(line) => !lines.has(line),
|
|
206
|
+
);
|
|
207
|
+
if (additions.length === 0) return;
|
|
200
208
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
const block = [
|
|
210
|
+
existing.length > 0 && !existing.endsWith("\n") ? "\n" : "",
|
|
211
|
+
CLAUDEUP_HEADER,
|
|
212
|
+
`# ${getGitignoreRuleReason("track", path)}`,
|
|
213
|
+
...additions,
|
|
214
|
+
"",
|
|
215
|
+
].join("\n");
|
|
216
|
+
await appendFile(gitignorePath, block);
|
|
209
217
|
}
|
|
210
218
|
|
|
211
219
|
function unignorePatternsForPath(path: string): string[] {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
const normalized = path.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
221
|
+
if (!normalized) return [];
|
|
222
|
+
const parts = normalized.split("/");
|
|
223
|
+
const patterns: string[] = [];
|
|
224
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
225
|
+
patterns.push(`!/${parts.slice(0, i + 1).join("/")}/`);
|
|
226
|
+
}
|
|
227
|
+
patterns.push(`!/${normalized}`);
|
|
228
|
+
return patterns;
|
|
221
229
|
}
|
|
222
230
|
|
|
223
231
|
async function stripGitignoreLine(cwd: string, pattern: string): Promise<void> {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
233
|
+
if (!existsSync(gitignorePath)) return;
|
|
234
|
+
const content = await readFile(gitignorePath, "utf8");
|
|
235
|
+
const filtered = content
|
|
236
|
+
.split("\n")
|
|
237
|
+
.filter((line) => line.trim() !== pattern)
|
|
238
|
+
.join("\n");
|
|
239
|
+
if (filtered !== content) {
|
|
240
|
+
await writeFile(gitignorePath, filtered);
|
|
241
|
+
}
|
|
234
242
|
}
|
|
235
243
|
|
|
236
244
|
async function pathIsIgnored(cwd: string, path: string): Promise<boolean> {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
245
|
+
try {
|
|
246
|
+
await runGit(["check-ignore", "--quiet", "--", path], cwd);
|
|
247
|
+
return true;
|
|
248
|
+
} catch (err) {
|
|
249
|
+
if (err instanceof GitError && err.code === 1) return false;
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
244
252
|
}
|
|
245
253
|
|
|
246
254
|
class GitError extends Error {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
255
|
+
constructor(
|
|
256
|
+
public code: number,
|
|
257
|
+
public stderr: string,
|
|
258
|
+
) {
|
|
259
|
+
super(`git exited ${code}: ${stderr}`);
|
|
260
|
+
}
|
|
250
261
|
}
|
|
251
262
|
|
|
252
263
|
function runGit(args: string[], cwd: string): Promise<string> {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
const proc = spawn("git", args, { cwd });
|
|
266
|
+
let stdout = "";
|
|
267
|
+
let stderr = "";
|
|
268
|
+
proc.stdout.on("data", (b) => {
|
|
269
|
+
stdout += b.toString();
|
|
270
|
+
});
|
|
271
|
+
proc.stderr.on("data", (b) => {
|
|
272
|
+
stderr += b.toString();
|
|
273
|
+
});
|
|
274
|
+
proc.on("error", reject);
|
|
275
|
+
proc.on("close", (code) => {
|
|
276
|
+
if (code === 0) resolve(stdout);
|
|
277
|
+
else reject(new GitError(code ?? -1, stderr));
|
|
278
|
+
});
|
|
279
|
+
});
|
|
265
280
|
}
|
|
@@ -8,33 +8,33 @@
|
|
|
8
8
|
* where prerun *warns* and the TUI provides the interactive fix dialog.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import type { Violation } from "../types/gitignore.js";
|
|
12
12
|
import { detectViolations } from "./gitignore-detector.js";
|
|
13
13
|
import { formatViolationSummary } from "./gitignore-fixer.js";
|
|
14
|
-
import
|
|
14
|
+
import { resolveGitignore } from "./gitignore-resolver.js";
|
|
15
15
|
|
|
16
16
|
export interface PrerunResult {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
violationCount: number;
|
|
18
|
+
warnings: string[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export async function checkGitignore(cwd: string): Promise<PrerunResult> {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
const warnings: string[] = [];
|
|
23
|
+
try {
|
|
24
|
+
const resolved = await resolveGitignore(cwd, {
|
|
25
|
+
logger: (m) => warnings.push(m),
|
|
26
|
+
});
|
|
27
|
+
const violations = await detectViolations(cwd, resolved);
|
|
28
|
+
return {
|
|
29
|
+
violationCount: violations.length,
|
|
30
|
+
warnings,
|
|
31
|
+
};
|
|
32
|
+
} catch (err) {
|
|
33
|
+
warnings.push(
|
|
34
|
+
`[gitignore] check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
35
|
+
);
|
|
36
|
+
return { violationCount: 0, warnings };
|
|
37
|
+
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -42,8 +42,8 @@ export async function checkGitignore(cwd: string): Promise<PrerunResult> {
|
|
|
42
42
|
* nothing to warn about.
|
|
43
43
|
*/
|
|
44
44
|
export function formatPrerunWarning(result: PrerunResult): string | null {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
if (result.violationCount === 0) return null;
|
|
46
|
+
return `⚠ ${result.violationCount} git state issue(s) detected. Run: claudeup → Git State tab to review and fix`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
@@ -51,9 +51,9 @@ export function formatPrerunWarning(result: PrerunResult): string | null {
|
|
|
51
51
|
* to know what's wrong before deciding what to do.
|
|
52
52
|
*/
|
|
53
53
|
export async function describeViolations(
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
cwd: string,
|
|
55
|
+
violations: Violation[],
|
|
56
56
|
): Promise<string> {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
void cwd;
|
|
58
|
+
return formatViolationSummary(violations);
|
|
59
59
|
}
|