skillrepo 2.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +276 -145
- package/bin/skillrepo.mjs +224 -36
- package/package.json +6 -3
- package/src/commands/add.mjs +176 -0
- package/src/commands/get.mjs +116 -0
- package/src/commands/init.mjs +589 -143
- package/src/commands/list.mjs +176 -0
- package/src/commands/remove.mjs +162 -0
- package/src/commands/search.mjs +188 -0
- package/src/commands/session-sync.mjs +152 -0
- package/src/commands/uninstall.mjs +484 -0
- package/src/commands/update.mjs +184 -0
- package/src/lib/artifact-registry.mjs +265 -0
- package/src/lib/cli-config.mjs +230 -0
- package/src/lib/config.mjs +238 -0
- package/src/lib/detect-ides.mjs +0 -19
- package/src/lib/errors.mjs +264 -0
- package/src/lib/file-write.mjs +705 -0
- package/src/lib/fs-utils.mjs +83 -1
- package/src/lib/http.mjs +817 -37
- package/src/lib/identifier.mjs +153 -0
- package/src/lib/mcp-merge.mjs +275 -0
- package/src/lib/mergers/gitignore.mjs +73 -18
- package/src/lib/mergers/session-hook.mjs +298 -0
- package/src/lib/paths.mjs +67 -17
- package/src/lib/prompt.mjs +11 -44
- package/src/lib/removers/claude-mcp.mjs +67 -0
- package/src/lib/removers/cursor-mcp.mjs +60 -0
- package/src/lib/removers/env-local.mjs +55 -0
- package/src/lib/removers/gitignore.mjs +108 -0
- package/src/lib/removers/settings.mjs +183 -0
- package/src/lib/removers/vscode-mcp.mjs +87 -0
- package/src/lib/removers/windsurf-mcp.mjs +65 -0
- package/src/lib/sync.mjs +305 -0
- package/src/test/commands/add.test.mjs +285 -0
- package/src/test/commands/get.test.mjs +176 -0
- package/src/test/commands/init.test.mjs +697 -0
- package/src/test/commands/list.test.mjs +172 -0
- package/src/test/commands/remove.test.mjs +234 -0
- package/src/test/commands/search.test.mjs +204 -0
- package/src/test/commands/session-sync.test.mjs +350 -0
- package/src/test/commands/uninstall.test.mjs +768 -0
- package/src/test/commands/update.test.mjs +322 -0
- package/src/test/detect-ides.test.mjs +9 -14
- package/src/test/dispatcher.test.mjs +224 -0
- package/src/test/e2e/cli-commands.test.mjs +576 -0
- package/src/test/e2e/mock-server.mjs +364 -22
- package/src/test/helpers/capture-stream.mjs +48 -0
- package/src/test/integration/file-write.integration.test.mjs +279 -0
- package/src/test/lib/artifact-registry.test.mjs +268 -0
- package/src/test/lib/cli-config.test.mjs +407 -0
- package/src/test/lib/config.test.mjs +257 -0
- package/src/test/lib/errors.test.mjs +359 -0
- package/src/test/lib/file-write.test.mjs +784 -0
- package/src/test/lib/http.test.mjs +1198 -0
- package/src/test/lib/identifier.test.mjs +157 -0
- package/src/test/lib/mcp-merge.test.mjs +345 -0
- package/src/test/lib/paths.test.mjs +83 -0
- package/src/test/lib/sync.test.mjs +514 -0
- package/src/test/mergers/gitignore.test.mjs +145 -20
- package/src/test/mergers/session-hook.test.mjs +745 -0
- package/src/test/mergers/uninstall-claude-mcp.test.mjs +145 -0
- package/src/test/mergers/uninstall-cursor-mcp.test.mjs +108 -0
- package/src/test/mergers/uninstall-env-local.test.mjs +144 -0
- package/src/test/mergers/uninstall-gitignore.test.mjs +209 -0
- package/src/test/mergers/uninstall-settings.test.mjs +285 -0
- package/src/test/mergers/uninstall-vscode-mcp.test.mjs +215 -0
- package/src/test/mergers/uninstall-windsurf-mcp.test.mjs +122 -0
- package/src/lib/write-configs.mjs +0 -202
- package/src/test/e2e/HANDOFF.md +0 -223
- package/src/test/e2e/cli-init.test.mjs +0 -213
- package/src/test/e2e/payload-factory.mjs +0 -22
|
@@ -1,9 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for src/lib/mergers/gitignore.mjs (PR4 of #646).
|
|
3
|
+
*
|
|
4
|
+
* The v2.0.0 merger added a single `.claude/rules/skillrepo-*.md`
|
|
5
|
+
* pattern for the now-deleted rules delivery flow. PR4 rewrote it to
|
|
6
|
+
* add the three init-required paths: .env.local, .claude/skills/,
|
|
7
|
+
* and .claude/settings.local.json. These tests cover:
|
|
8
|
+
*
|
|
9
|
+
* - fresh .gitignore creation (all three entries in one block)
|
|
10
|
+
* - append when .gitignore exists but is missing entries
|
|
11
|
+
* - skip when every entry is already present
|
|
12
|
+
* - partial skip when some entries are present and some aren't
|
|
13
|
+
* - idempotent second run
|
|
14
|
+
* - line-ending preservation (CRLF vs LF)
|
|
15
|
+
* - line-exact matching (a comment containing ".env.local" does NOT
|
|
16
|
+
* satisfy the check)
|
|
17
|
+
*/
|
|
18
|
+
|
|
1
19
|
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
2
20
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtempSync,
|
|
21
|
+
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from "node:fs";
|
|
4
22
|
import { join } from "node:path";
|
|
5
23
|
import { tmpdir } from "node:os";
|
|
6
24
|
|
|
25
|
+
import { mergeGitignore } from "../../lib/mergers/gitignore.mjs";
|
|
26
|
+
|
|
7
27
|
let originalCwd;
|
|
8
28
|
let tempDir;
|
|
9
29
|
|
|
@@ -18,46 +38,151 @@ afterEach(() => {
|
|
|
18
38
|
rmSync(tempDir, { recursive: true, force: true });
|
|
19
39
|
});
|
|
20
40
|
|
|
21
|
-
describe("mergeGitignore", () => {
|
|
22
|
-
it("creates .gitignore when
|
|
23
|
-
const { mergeGitignore } = await import("../../lib/mergers/gitignore.mjs");
|
|
41
|
+
describe("mergeGitignore — creation", () => {
|
|
42
|
+
it("creates .gitignore with all three entries when file does not exist", () => {
|
|
24
43
|
const result = mergeGitignore();
|
|
25
44
|
|
|
26
45
|
assert.equal(result.action, "created");
|
|
46
|
+
assert.deepEqual(result.added.sort(), [
|
|
47
|
+
".claude/settings.local.json",
|
|
48
|
+
".claude/skills/",
|
|
49
|
+
".env.local",
|
|
50
|
+
]);
|
|
51
|
+
|
|
27
52
|
const content = readFileSync(join(tempDir, ".gitignore"), "utf-8");
|
|
28
|
-
assert.
|
|
29
|
-
assert.
|
|
53
|
+
assert.match(content, /^# SkillRepo CLI/m);
|
|
54
|
+
assert.match(content, /^\.env\.local$/m);
|
|
55
|
+
assert.match(content, /^\.claude\/skills\/$/m);
|
|
56
|
+
assert.match(content, /^\.claude\/settings\.local\.json$/m);
|
|
30
57
|
});
|
|
58
|
+
});
|
|
31
59
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
60
|
+
describe("mergeGitignore — append to existing file", () => {
|
|
61
|
+
it("appends all three entries when none are present", () => {
|
|
35
62
|
writeFileSync(join(tempDir, ".gitignore"), "node_modules/\n.env\n");
|
|
36
63
|
const result = mergeGitignore();
|
|
37
64
|
|
|
38
65
|
assert.equal(result.action, "updated");
|
|
66
|
+
assert.equal(result.added.length, 3);
|
|
67
|
+
|
|
39
68
|
const content = readFileSync(join(tempDir, ".gitignore"), "utf-8");
|
|
69
|
+
// Original content preserved at the start
|
|
40
70
|
assert.ok(content.startsWith("node_modules/\n.env\n"));
|
|
41
|
-
|
|
71
|
+
// All three entries present
|
|
72
|
+
assert.match(content, /^\.env\.local$/m);
|
|
73
|
+
assert.match(content, /^\.claude\/skills\/$/m);
|
|
74
|
+
assert.match(content, /^\.claude\/settings\.local\.json$/m);
|
|
42
75
|
});
|
|
43
76
|
|
|
44
|
-
it("
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
77
|
+
it("appends only missing entries when some are already present", () => {
|
|
78
|
+
// .env.local already exists — only the other two should be added
|
|
79
|
+
writeFileSync(
|
|
80
|
+
join(tempDir, ".gitignore"),
|
|
81
|
+
"node_modules/\n.env.local\n",
|
|
82
|
+
);
|
|
48
83
|
const result = mergeGitignore();
|
|
49
84
|
|
|
50
|
-
assert.equal(result.action, "
|
|
51
|
-
|
|
85
|
+
assert.equal(result.action, "updated");
|
|
86
|
+
assert.deepEqual(result.added.sort(), [
|
|
87
|
+
".claude/settings.local.json",
|
|
88
|
+
".claude/skills/",
|
|
89
|
+
]);
|
|
52
90
|
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
const content = readFileSync(join(tempDir, ".gitignore"), "utf-8");
|
|
92
|
+
// .env.local should NOT appear twice
|
|
93
|
+
const envLocalMatches = content.match(/^\.env\.local$/gm) ?? [];
|
|
94
|
+
assert.equal(envLocalMatches.length, 1, ".env.local must not be duplicated");
|
|
95
|
+
});
|
|
55
96
|
|
|
97
|
+
it("appends a newline before the section if the existing file lacks trailing newline", () => {
|
|
56
98
|
writeFileSync(join(tempDir, ".gitignore"), "node_modules/");
|
|
57
99
|
mergeGitignore();
|
|
58
100
|
|
|
59
101
|
const content = readFileSync(join(tempDir, ".gitignore"), "utf-8");
|
|
60
|
-
//
|
|
61
|
-
assert.
|
|
102
|
+
// Separator newline between existing content and the SkillRepo section
|
|
103
|
+
assert.match(content, /node_modules\/\n\n# SkillRepo/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("preserves CRLF line endings on existing files", () => {
|
|
107
|
+
writeFileSync(join(tempDir, ".gitignore"), "node_modules/\r\n.env\r\n");
|
|
108
|
+
mergeGitignore();
|
|
109
|
+
|
|
110
|
+
const content = readFileSync(join(tempDir, ".gitignore"), "utf-8");
|
|
111
|
+
// The SkillRepo section should use CRLF to match
|
|
112
|
+
assert.match(content, /# SkillRepo CLI.*\r\n\.env\.local\r\n/);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("mergeGitignore — idempotency", () => {
|
|
117
|
+
it("returns skipped when every entry is already present", () => {
|
|
118
|
+
writeFileSync(
|
|
119
|
+
join(tempDir, ".gitignore"),
|
|
120
|
+
[
|
|
121
|
+
"node_modules/",
|
|
122
|
+
".env.local",
|
|
123
|
+
".claude/skills/",
|
|
124
|
+
".claude/settings.local.json",
|
|
125
|
+
].join("\n") + "\n",
|
|
126
|
+
);
|
|
127
|
+
const result = mergeGitignore();
|
|
128
|
+
|
|
129
|
+
assert.equal(result.action, "skipped");
|
|
130
|
+
assert.equal(result.added.length, 0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("second run after creation is a no-op skip", () => {
|
|
134
|
+
const first = mergeGitignore();
|
|
135
|
+
assert.equal(first.action, "created");
|
|
136
|
+
|
|
137
|
+
const second = mergeGitignore();
|
|
138
|
+
assert.equal(second.action, "skipped");
|
|
139
|
+
assert.equal(second.added.length, 0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("third run after an append is also a no-op skip", () => {
|
|
143
|
+
writeFileSync(join(tempDir, ".gitignore"), "node_modules/\n");
|
|
144
|
+
const first = mergeGitignore();
|
|
145
|
+
assert.equal(first.action, "updated");
|
|
146
|
+
|
|
147
|
+
const second = mergeGitignore();
|
|
148
|
+
assert.equal(second.action, "skipped");
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("mergeGitignore — line-exact matching", () => {
|
|
153
|
+
it("does NOT count a comment containing .env.local as satisfied", () => {
|
|
154
|
+
writeFileSync(
|
|
155
|
+
join(tempDir, ".gitignore"),
|
|
156
|
+
"# mention of .env.local in a comment\n",
|
|
157
|
+
);
|
|
158
|
+
const result = mergeGitignore();
|
|
159
|
+
|
|
160
|
+
// The comment line is not a literal .env.local line, so the
|
|
161
|
+
// merger should still add it.
|
|
162
|
+
assert.equal(result.action, "updated");
|
|
163
|
+
assert.ok(result.added.includes(".env.local"));
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("does NOT count .env.local.backup as satisfying .env.local", () => {
|
|
167
|
+
writeFileSync(
|
|
168
|
+
join(tempDir, ".gitignore"),
|
|
169
|
+
".env.local.backup\n",
|
|
170
|
+
);
|
|
171
|
+
const result = mergeGitignore();
|
|
172
|
+
|
|
173
|
+
assert.equal(result.action, "updated");
|
|
174
|
+
assert.ok(result.added.includes(".env.local"));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("tolerates leading/trailing whitespace on existing lines", () => {
|
|
178
|
+
// Some editors add trailing whitespace. The merger trims on
|
|
179
|
+
// compare so these lines should still count as matches.
|
|
180
|
+
writeFileSync(
|
|
181
|
+
join(tempDir, ".gitignore"),
|
|
182
|
+
" .env.local \n .claude/skills/\n.claude/settings.local.json\n",
|
|
183
|
+
);
|
|
184
|
+
const result = mergeGitignore();
|
|
185
|
+
|
|
186
|
+
assert.equal(result.action, "skipped");
|
|
62
187
|
});
|
|
63
188
|
});
|