@skilltap/core 0.4.4 → 0.5.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/package.json +1 -1
- package/src/self-update.ts +1 -1
- package/src/taps.test.ts +63 -1
- package/src/taps.ts +30 -0
package/package.json
CHANGED
package/src/self-update.ts
CHANGED
|
@@ -60,7 +60,7 @@ async function writeCache(configDir: string, latest: string): Promise<void> {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
async function fetchLatestVersion(): Promise<string | null> {
|
|
63
|
+
export async function fetchLatestVersion(): Promise<string | null> {
|
|
64
64
|
try {
|
|
65
65
|
const response = await fetch(
|
|
66
66
|
`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest`,
|
package/src/taps.test.ts
CHANGED
|
@@ -9,7 +9,14 @@ import {
|
|
|
9
9
|
import { loadConfig } from "./config";
|
|
10
10
|
import { installSkill } from "./install";
|
|
11
11
|
import type { TapEntry } from "./taps";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
addTap,
|
|
14
|
+
loadTaps,
|
|
15
|
+
parseGitHubTapShorthand,
|
|
16
|
+
removeTap,
|
|
17
|
+
searchTaps,
|
|
18
|
+
updateTap,
|
|
19
|
+
} from "./taps";
|
|
13
20
|
|
|
14
21
|
type Env = {
|
|
15
22
|
SKILLTAP_HOME?: string;
|
|
@@ -74,6 +81,61 @@ async function createLocalSkillRepo(
|
|
|
74
81
|
return { path: repoDir, cleanup: () => removeTmpDir(repoDir) };
|
|
75
82
|
}
|
|
76
83
|
|
|
84
|
+
// ─── Unit tests: parseGitHubTapShorthand ───────────────────────────────────
|
|
85
|
+
|
|
86
|
+
describe("parseGitHubTapShorthand", () => {
|
|
87
|
+
test("parses owner/repo", () => {
|
|
88
|
+
expect(parseGitHubTapShorthand("user/my-tap")).toEqual({
|
|
89
|
+
name: "my-tap",
|
|
90
|
+
url: "https://github.com/user/my-tap.git",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("parses github:owner/repo", () => {
|
|
95
|
+
expect(parseGitHubTapShorthand("github:acme/skills")).toEqual({
|
|
96
|
+
name: "skills",
|
|
97
|
+
url: "https://github.com/acme/skills.git",
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("strips @ref suffix", () => {
|
|
102
|
+
expect(parseGitHubTapShorthand("user/tap@main")).toEqual({
|
|
103
|
+
name: "tap",
|
|
104
|
+
url: "https://github.com/user/tap.git",
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("returns null for bare names", () => {
|
|
109
|
+
expect(parseGitHubTapShorthand("my-tap")).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("returns null for full URLs", () => {
|
|
113
|
+
expect(
|
|
114
|
+
parseGitHubTapShorthand("https://github.com/user/repo.git"),
|
|
115
|
+
).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("returns null for npm: prefix", () => {
|
|
119
|
+
expect(parseGitHubTapShorthand("npm:my-package")).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("returns null for local paths", () => {
|
|
123
|
+
expect(parseGitHubTapShorthand("./local")).toBeNull();
|
|
124
|
+
expect(parseGitHubTapShorthand("/abs/path")).toBeNull();
|
|
125
|
+
expect(parseGitHubTapShorthand("~/home")).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("returns null for three-part paths", () => {
|
|
129
|
+
expect(parseGitHubTapShorthand("a/b/c")).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("returns null for git@ URLs", () => {
|
|
133
|
+
expect(
|
|
134
|
+
parseGitHubTapShorthand("git@github.com:user/repo.git"),
|
|
135
|
+
).toBeNull();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
77
139
|
// ─── Unit tests: searchTaps ────────────────────────────────────────────────
|
|
78
140
|
|
|
79
141
|
describe("searchTaps", () => {
|
package/src/taps.ts
CHANGED
|
@@ -63,6 +63,36 @@ function registrySourceToRepo(source: RegistrySource): string {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export type GitHubTapShorthand = { name: string; url: string };
|
|
67
|
+
|
|
68
|
+
const GH_LOCAL_PREFIXES = ["./", "/", "~/"];
|
|
69
|
+
const GH_URL_PROTOCOLS = ["https://", "http://", "git@", "ssh://", "npm:"];
|
|
70
|
+
|
|
71
|
+
/** Parse GitHub shorthand (owner/repo) into a tap name + clone URL. Returns null if not shorthand. */
|
|
72
|
+
export function parseGitHubTapShorthand(
|
|
73
|
+
source: string,
|
|
74
|
+
): GitHubTapShorthand | null {
|
|
75
|
+
let s = source;
|
|
76
|
+
if (s.startsWith("github:")) s = s.slice("github:".length);
|
|
77
|
+
else if (!s.includes("/")) return null;
|
|
78
|
+
|
|
79
|
+
if (GH_URL_PROTOCOLS.some((p) => s.startsWith(p))) return null;
|
|
80
|
+
if (GH_LOCAL_PREFIXES.some((p) => s.startsWith(p))) return null;
|
|
81
|
+
|
|
82
|
+
// Strip @ref suffix (taps always clone HEAD)
|
|
83
|
+
const atIdx = s.lastIndexOf("@");
|
|
84
|
+
if (atIdx !== -1) s = s.slice(0, atIdx);
|
|
85
|
+
|
|
86
|
+
const parts = s.split("/").filter(Boolean);
|
|
87
|
+
if (parts.length !== 2) return null;
|
|
88
|
+
|
|
89
|
+
const [owner, repo] = parts;
|
|
90
|
+
return {
|
|
91
|
+
name: repo!,
|
|
92
|
+
url: `https://github.com/${owner}/${repo}.git`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
66
96
|
export async function addTap(
|
|
67
97
|
name: string,
|
|
68
98
|
url: string,
|