clipr-cli 0.0.5
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/dist/api-KFVDRB2Q.js +90 -0
- package/dist/api-KFVDRB2Q.js.map +1 -0
- package/dist/api-LBSFNXNQ.js +88 -0
- package/dist/api-LBSFNXNQ.js.map +1 -0
- package/dist/api.d.ts +86 -0
- package/dist/api.js +165 -0
- package/dist/api.js.map +1 -0
- package/dist/chunk-I7CHG5Z3.js +92 -0
- package/dist/chunk-I7CHG5Z3.js.map +1 -0
- package/dist/github-OYIAPRKM.js +155 -0
- package/dist/github-OYIAPRKM.js.map +1 -0
- package/dist/github-PSGU4YYI.js +157 -0
- package/dist/github-PSGU4YYI.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +698 -0
- package/dist/index.js.map +1 -0
- package/dist/json-adapter-5YGDHNVJ.js +8 -0
- package/dist/json-adapter-5YGDHNVJ.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/backends/github.ts
|
|
2
|
+
import {
|
|
3
|
+
appendUtm,
|
|
4
|
+
DB_VERSION,
|
|
5
|
+
generateRandomSlug,
|
|
6
|
+
hasUtm,
|
|
7
|
+
normalizeSlug,
|
|
8
|
+
SlugConflictError,
|
|
9
|
+
SlugNotFoundError
|
|
10
|
+
} from "@clipr/core";
|
|
11
|
+
var GitHubBackend = class {
|
|
12
|
+
constructor(config, shortBaseUrl) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.shortBaseUrl = shortBaseUrl;
|
|
15
|
+
this.apiBase = `https://api.github.com/repos/${config.owner}/${config.repo}/contents/${config.path}`;
|
|
16
|
+
this.headers = {
|
|
17
|
+
Authorization: `token ${config.token}`,
|
|
18
|
+
Accept: "application/vnd.github.v3+json",
|
|
19
|
+
"Content-Type": "application/json"
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
apiBase;
|
|
23
|
+
headers;
|
|
24
|
+
/** Fetch the current urls.json from GitHub, returning content + SHA. */
|
|
25
|
+
async fetchDatabase() {
|
|
26
|
+
const url = `${this.apiBase}?ref=${this.config.branch}`;
|
|
27
|
+
const res = await fetch(url, { headers: this.headers });
|
|
28
|
+
if (res.status === 404) {
|
|
29
|
+
const db2 = {
|
|
30
|
+
version: DB_VERSION,
|
|
31
|
+
counter: 0,
|
|
32
|
+
baseUrl: this.shortBaseUrl,
|
|
33
|
+
urls: {}
|
|
34
|
+
};
|
|
35
|
+
return { db: db2, sha: "" };
|
|
36
|
+
}
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);
|
|
39
|
+
}
|
|
40
|
+
const data = await res.json();
|
|
41
|
+
const decoded = Buffer.from(data.content, "base64").toString("utf-8");
|
|
42
|
+
const db = JSON.parse(decoded);
|
|
43
|
+
return { db, sha: data.sha };
|
|
44
|
+
}
|
|
45
|
+
/** Commit updated urls.json back to GitHub. */
|
|
46
|
+
async commitDatabase(db, sha, message) {
|
|
47
|
+
const content = Buffer.from(`${JSON.stringify(db, null, 2)}
|
|
48
|
+
`).toString("base64");
|
|
49
|
+
const body = {
|
|
50
|
+
message,
|
|
51
|
+
content,
|
|
52
|
+
branch: this.config.branch
|
|
53
|
+
};
|
|
54
|
+
if (sha) {
|
|
55
|
+
body.sha = sha;
|
|
56
|
+
}
|
|
57
|
+
const res = await fetch(this.apiBase, {
|
|
58
|
+
method: "PUT",
|
|
59
|
+
headers: this.headers,
|
|
60
|
+
body: JSON.stringify(body)
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
const errBody = await res.text();
|
|
64
|
+
throw new Error(`GitHub API commit failed: ${res.status} ${res.statusText} \u2014 ${errBody}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async create(slug, targetUrl, options) {
|
|
68
|
+
const { db, sha } = await this.fetchDatabase();
|
|
69
|
+
const finalSlug = normalizeSlug(slug || generateRandomSlug());
|
|
70
|
+
if (db.urls[finalSlug]) {
|
|
71
|
+
throw new SlugConflictError(finalSlug);
|
|
72
|
+
}
|
|
73
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
74
|
+
const entry = {
|
|
75
|
+
slug: finalSlug,
|
|
76
|
+
url: targetUrl,
|
|
77
|
+
createdAt: now,
|
|
78
|
+
...options?.description && { description: options.description },
|
|
79
|
+
...options?.expiresAt && { expiresAt: options.expiresAt },
|
|
80
|
+
...options?.utm && hasUtm(options.utm) && { utm: options.utm },
|
|
81
|
+
...options?.tags && { tags: options.tags },
|
|
82
|
+
...options?.ogTitle && { ogTitle: options.ogTitle },
|
|
83
|
+
...options?.ogDescription && { ogDescription: options.ogDescription },
|
|
84
|
+
...options?.ogImage && { ogImage: options.ogImage }
|
|
85
|
+
};
|
|
86
|
+
db.urls[finalSlug] = entry;
|
|
87
|
+
db.counter = Object.keys(db.urls).length;
|
|
88
|
+
await this.commitDatabase(db, sha, `clipr: add ${finalSlug} \u2192 ${targetUrl}`);
|
|
89
|
+
return entry;
|
|
90
|
+
}
|
|
91
|
+
async resolve(slug) {
|
|
92
|
+
const { db } = await this.fetchDatabase();
|
|
93
|
+
const entry = db.urls[slug];
|
|
94
|
+
if (!entry) return null;
|
|
95
|
+
const expired = entry.expiresAt ? new Date(entry.expiresAt) < /* @__PURE__ */ new Date() : false;
|
|
96
|
+
const url = hasUtm(entry.utm) ? appendUtm(entry.url, entry.utm) : entry.url;
|
|
97
|
+
return { url, passwordProtected: false, expired };
|
|
98
|
+
}
|
|
99
|
+
async list(options) {
|
|
100
|
+
const { db } = await this.fetchDatabase();
|
|
101
|
+
let entries = Object.values(db.urls);
|
|
102
|
+
if (options?.search) {
|
|
103
|
+
const q = options.search.toLowerCase();
|
|
104
|
+
entries = entries.filter(
|
|
105
|
+
(e) => e.slug.includes(q) || e.url.toLowerCase().includes(q) || e.description?.toLowerCase().includes(q)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (options?.tag) {
|
|
109
|
+
entries = entries.filter((e) => e.tags?.includes(options.tag));
|
|
110
|
+
}
|
|
111
|
+
if (options?.limit) {
|
|
112
|
+
entries = entries.slice(0, options.limit);
|
|
113
|
+
}
|
|
114
|
+
return entries;
|
|
115
|
+
}
|
|
116
|
+
async delete(slug) {
|
|
117
|
+
const { db, sha } = await this.fetchDatabase();
|
|
118
|
+
if (!db.urls[slug]) {
|
|
119
|
+
throw new SlugNotFoundError(slug);
|
|
120
|
+
}
|
|
121
|
+
delete db.urls[slug];
|
|
122
|
+
db.counter = Object.keys(db.urls).length;
|
|
123
|
+
await this.commitDatabase(db, sha, `clipr: delete ${slug}`);
|
|
124
|
+
}
|
|
125
|
+
async update(slug, updates) {
|
|
126
|
+
const { db, sha } = await this.fetchDatabase();
|
|
127
|
+
const existing = db.urls[slug];
|
|
128
|
+
if (!existing) {
|
|
129
|
+
throw new SlugNotFoundError(slug);
|
|
130
|
+
}
|
|
131
|
+
const newSlug = updates.slug ?? slug;
|
|
132
|
+
if (newSlug !== slug) {
|
|
133
|
+
if (db.urls[newSlug]) {
|
|
134
|
+
throw new SlugConflictError(newSlug);
|
|
135
|
+
}
|
|
136
|
+
delete db.urls[slug];
|
|
137
|
+
}
|
|
138
|
+
const updated = { ...existing, ...updates, slug: newSlug };
|
|
139
|
+
db.urls[newSlug] = updated;
|
|
140
|
+
db.counter = Object.keys(db.urls).length;
|
|
141
|
+
await this.commitDatabase(
|
|
142
|
+
db,
|
|
143
|
+
sha,
|
|
144
|
+
`clipr: update ${slug}${newSlug !== slug ? ` \u2192 ${newSlug}` : ""}`
|
|
145
|
+
);
|
|
146
|
+
return updated;
|
|
147
|
+
}
|
|
148
|
+
async getStats(_slug) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
export {
|
|
153
|
+
GitHubBackend
|
|
154
|
+
};
|
|
155
|
+
//# sourceMappingURL=github-OYIAPRKM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/backends/github.ts"],"sourcesContent":["import type {\n CreateOptions,\n LinkStats,\n ListOptions,\n ResolveResult,\n ShortUrl,\n UrlBackend,\n UrlDatabase,\n} from '@clipr/core';\nimport {\n appendUtm,\n DB_VERSION,\n generateRandomSlug,\n hasUtm,\n normalizeSlug,\n SlugConflictError,\n SlugNotFoundError,\n} from '@clipr/core';\n\ninterface GitHubConfig {\n owner: string;\n repo: string;\n branch: string;\n path: string;\n token: string;\n}\n\ninterface GitHubContentsResponse {\n sha: string;\n content: string;\n encoding: string;\n}\n\n/**\n * UrlBackend implementation using the GitHub Contents API.\n * Stores all URL data in a urls.json file in a GitHub repo.\n */\nexport class GitHubBackend implements UrlBackend {\n private readonly apiBase: string;\n private readonly headers: Record<string, string>;\n\n constructor(\n private readonly config: GitHubConfig,\n private readonly shortBaseUrl: string,\n ) {\n this.apiBase = `https://api.github.com/repos/${config.owner}/${config.repo}/contents/${config.path}`;\n this.headers = {\n Authorization: `token ${config.token}`,\n Accept: 'application/vnd.github.v3+json',\n 'Content-Type': 'application/json',\n };\n }\n\n /** Fetch the current urls.json from GitHub, returning content + SHA. */\n private async fetchDatabase(): Promise<{ db: UrlDatabase; sha: string }> {\n const url = `${this.apiBase}?ref=${this.config.branch}`;\n const res = await fetch(url, { headers: this.headers });\n\n if (res.status === 404) {\n // File doesn't exist yet, return empty database\n const db: UrlDatabase = {\n version: DB_VERSION,\n counter: 0,\n baseUrl: this.shortBaseUrl,\n urls: {},\n };\n return { db, sha: '' };\n }\n\n if (!res.ok) {\n throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);\n }\n\n const data = (await res.json()) as GitHubContentsResponse;\n const decoded = Buffer.from(data.content, 'base64').toString('utf-8');\n const db = JSON.parse(decoded) as UrlDatabase;\n\n return { db, sha: data.sha };\n }\n\n /** Commit updated urls.json back to GitHub. */\n private async commitDatabase(db: UrlDatabase, sha: string, message: string): Promise<void> {\n const content = Buffer.from(`${JSON.stringify(db, null, 2)}\\n`).toString('base64');\n\n const body: Record<string, string> = {\n message,\n content,\n branch: this.config.branch,\n };\n\n // Include SHA for updates (not for creation)\n if (sha) {\n body.sha = sha;\n }\n\n const res = await fetch(this.apiBase, {\n method: 'PUT',\n headers: this.headers,\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n throw new Error(`GitHub API commit failed: ${res.status} ${res.statusText} — ${errBody}`);\n }\n }\n\n async create(slug: string, targetUrl: string, options?: CreateOptions): Promise<ShortUrl> {\n const { db, sha } = await this.fetchDatabase();\n const finalSlug = normalizeSlug(slug || generateRandomSlug());\n\n if (db.urls[finalSlug]) {\n throw new SlugConflictError(finalSlug);\n }\n\n const now = new Date().toISOString();\n const entry: ShortUrl = {\n slug: finalSlug,\n url: targetUrl,\n createdAt: now,\n ...(options?.description && { description: options.description }),\n ...(options?.expiresAt && { expiresAt: options.expiresAt }),\n ...(options?.utm && hasUtm(options.utm) && { utm: options.utm }),\n ...(options?.tags && { tags: options.tags }),\n ...(options?.ogTitle && { ogTitle: options.ogTitle }),\n ...(options?.ogDescription && { ogDescription: options.ogDescription }),\n ...(options?.ogImage && { ogImage: options.ogImage }),\n };\n\n db.urls[finalSlug] = entry;\n db.counter = Object.keys(db.urls).length;\n\n await this.commitDatabase(db, sha, `clipr: add ${finalSlug} → ${targetUrl}`);\n return entry;\n }\n\n async resolve(slug: string): Promise<ResolveResult | null> {\n const { db } = await this.fetchDatabase();\n const entry = db.urls[slug];\n if (!entry) return null;\n\n const expired = entry.expiresAt ? new Date(entry.expiresAt) < new Date() : false;\n const url = hasUtm(entry.utm) ? appendUtm(entry.url, entry.utm!) : entry.url;\n\n return { url, passwordProtected: false, expired };\n }\n\n async list(options?: ListOptions): Promise<ShortUrl[]> {\n const { db } = await this.fetchDatabase();\n let entries = Object.values(db.urls) as ShortUrl[];\n\n if (options?.search) {\n const q = options.search.toLowerCase();\n entries = entries.filter(\n (e) =>\n e.slug.includes(q) ||\n e.url.toLowerCase().includes(q) ||\n e.description?.toLowerCase().includes(q),\n );\n }\n\n if (options?.tag) {\n entries = entries.filter((e) => e.tags?.includes(options.tag!));\n }\n\n if (options?.limit) {\n entries = entries.slice(0, options.limit);\n }\n\n return entries;\n }\n\n async delete(slug: string): Promise<void> {\n const { db, sha } = await this.fetchDatabase();\n\n if (!db.urls[slug]) {\n throw new SlugNotFoundError(slug);\n }\n\n delete db.urls[slug];\n db.counter = Object.keys(db.urls).length;\n\n await this.commitDatabase(db, sha, `clipr: delete ${slug}`);\n }\n\n async update(slug: string, updates: Partial<ShortUrl>): Promise<ShortUrl> {\n const { db, sha } = await this.fetchDatabase();\n const existing = db.urls[slug];\n\n if (!existing) {\n throw new SlugNotFoundError(slug);\n }\n\n const newSlug = updates.slug ?? slug;\n\n // If renaming, check for conflict and remove old entry\n if (newSlug !== slug) {\n if (db.urls[newSlug]) {\n throw new SlugConflictError(newSlug);\n }\n delete db.urls[slug];\n }\n\n const updated: ShortUrl = { ...existing, ...updates, slug: newSlug };\n db.urls[newSlug] = updated;\n db.counter = Object.keys(db.urls).length;\n\n await this.commitDatabase(\n db,\n sha,\n `clipr: update ${slug}${newSlug !== slug ? ` → ${newSlug}` : ''}`,\n );\n return updated;\n }\n\n async getStats(_slug: string): Promise<LinkStats | null> {\n // GitHub mode has no click tracking\n return null;\n }\n}\n"],"mappings":"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","names":["db"]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/backends/github.ts
|
|
4
|
+
import {
|
|
5
|
+
appendUtm,
|
|
6
|
+
DB_VERSION,
|
|
7
|
+
generateRandomSlug,
|
|
8
|
+
hasUtm,
|
|
9
|
+
normalizeSlug,
|
|
10
|
+
SlugConflictError,
|
|
11
|
+
SlugNotFoundError
|
|
12
|
+
} from "@clipr/core";
|
|
13
|
+
var GitHubBackend = class {
|
|
14
|
+
constructor(config, shortBaseUrl) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.shortBaseUrl = shortBaseUrl;
|
|
17
|
+
this.apiBase = `https://api.github.com/repos/${config.owner}/${config.repo}/contents/${config.path}`;
|
|
18
|
+
this.headers = {
|
|
19
|
+
Authorization: `token ${config.token}`,
|
|
20
|
+
Accept: "application/vnd.github.v3+json",
|
|
21
|
+
"Content-Type": "application/json"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
apiBase;
|
|
25
|
+
headers;
|
|
26
|
+
/** Fetch the current urls.json from GitHub, returning content + SHA. */
|
|
27
|
+
async fetchDatabase() {
|
|
28
|
+
const url = `${this.apiBase}?ref=${this.config.branch}`;
|
|
29
|
+
const res = await fetch(url, { headers: this.headers });
|
|
30
|
+
if (res.status === 404) {
|
|
31
|
+
const db2 = {
|
|
32
|
+
version: DB_VERSION,
|
|
33
|
+
counter: 0,
|
|
34
|
+
baseUrl: this.shortBaseUrl,
|
|
35
|
+
urls: {}
|
|
36
|
+
};
|
|
37
|
+
return { db: db2, sha: "" };
|
|
38
|
+
}
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
const data = await res.json();
|
|
43
|
+
const decoded = Buffer.from(data.content, "base64").toString("utf-8");
|
|
44
|
+
const db = JSON.parse(decoded);
|
|
45
|
+
return { db, sha: data.sha };
|
|
46
|
+
}
|
|
47
|
+
/** Commit updated urls.json back to GitHub. */
|
|
48
|
+
async commitDatabase(db, sha, message) {
|
|
49
|
+
const content = Buffer.from(`${JSON.stringify(db, null, 2)}
|
|
50
|
+
`).toString("base64");
|
|
51
|
+
const body = {
|
|
52
|
+
message,
|
|
53
|
+
content,
|
|
54
|
+
branch: this.config.branch
|
|
55
|
+
};
|
|
56
|
+
if (sha) {
|
|
57
|
+
body.sha = sha;
|
|
58
|
+
}
|
|
59
|
+
const res = await fetch(this.apiBase, {
|
|
60
|
+
method: "PUT",
|
|
61
|
+
headers: this.headers,
|
|
62
|
+
body: JSON.stringify(body)
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
const errBody = await res.text();
|
|
66
|
+
throw new Error(`GitHub API commit failed: ${res.status} ${res.statusText} \u2014 ${errBody}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async create(slug, targetUrl, options) {
|
|
70
|
+
const { db, sha } = await this.fetchDatabase();
|
|
71
|
+
const finalSlug = normalizeSlug(slug || generateRandomSlug());
|
|
72
|
+
if (db.urls[finalSlug]) {
|
|
73
|
+
throw new SlugConflictError(finalSlug);
|
|
74
|
+
}
|
|
75
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
76
|
+
const entry = {
|
|
77
|
+
slug: finalSlug,
|
|
78
|
+
url: targetUrl,
|
|
79
|
+
createdAt: now,
|
|
80
|
+
...options?.description && { description: options.description },
|
|
81
|
+
...options?.expiresAt && { expiresAt: options.expiresAt },
|
|
82
|
+
...options?.utm && hasUtm(options.utm) && { utm: options.utm },
|
|
83
|
+
...options?.tags && { tags: options.tags },
|
|
84
|
+
...options?.ogTitle && { ogTitle: options.ogTitle },
|
|
85
|
+
...options?.ogDescription && { ogDescription: options.ogDescription },
|
|
86
|
+
...options?.ogImage && { ogImage: options.ogImage }
|
|
87
|
+
};
|
|
88
|
+
db.urls[finalSlug] = entry;
|
|
89
|
+
db.counter = Object.keys(db.urls).length;
|
|
90
|
+
await this.commitDatabase(db, sha, `clipr: add ${finalSlug} \u2192 ${targetUrl}`);
|
|
91
|
+
return entry;
|
|
92
|
+
}
|
|
93
|
+
async resolve(slug) {
|
|
94
|
+
const { db } = await this.fetchDatabase();
|
|
95
|
+
const entry = db.urls[slug];
|
|
96
|
+
if (!entry) return null;
|
|
97
|
+
const expired = entry.expiresAt ? new Date(entry.expiresAt) < /* @__PURE__ */ new Date() : false;
|
|
98
|
+
const url = hasUtm(entry.utm) ? appendUtm(entry.url, entry.utm) : entry.url;
|
|
99
|
+
return { url, passwordProtected: false, expired };
|
|
100
|
+
}
|
|
101
|
+
async list(options) {
|
|
102
|
+
const { db } = await this.fetchDatabase();
|
|
103
|
+
let entries = Object.values(db.urls);
|
|
104
|
+
if (options?.search) {
|
|
105
|
+
const q = options.search.toLowerCase();
|
|
106
|
+
entries = entries.filter(
|
|
107
|
+
(e) => e.slug.includes(q) || e.url.toLowerCase().includes(q) || e.description?.toLowerCase().includes(q)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (options?.tag) {
|
|
111
|
+
entries = entries.filter((e) => e.tags?.includes(options.tag));
|
|
112
|
+
}
|
|
113
|
+
if (options?.limit) {
|
|
114
|
+
entries = entries.slice(0, options.limit);
|
|
115
|
+
}
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
async delete(slug) {
|
|
119
|
+
const { db, sha } = await this.fetchDatabase();
|
|
120
|
+
if (!db.urls[slug]) {
|
|
121
|
+
throw new SlugNotFoundError(slug);
|
|
122
|
+
}
|
|
123
|
+
delete db.urls[slug];
|
|
124
|
+
db.counter = Object.keys(db.urls).length;
|
|
125
|
+
await this.commitDatabase(db, sha, `clipr: delete ${slug}`);
|
|
126
|
+
}
|
|
127
|
+
async update(slug, updates) {
|
|
128
|
+
const { db, sha } = await this.fetchDatabase();
|
|
129
|
+
const existing = db.urls[slug];
|
|
130
|
+
if (!existing) {
|
|
131
|
+
throw new SlugNotFoundError(slug);
|
|
132
|
+
}
|
|
133
|
+
const newSlug = updates.slug ?? slug;
|
|
134
|
+
if (newSlug !== slug) {
|
|
135
|
+
if (db.urls[newSlug]) {
|
|
136
|
+
throw new SlugConflictError(newSlug);
|
|
137
|
+
}
|
|
138
|
+
delete db.urls[slug];
|
|
139
|
+
}
|
|
140
|
+
const updated = { ...existing, ...updates, slug: newSlug };
|
|
141
|
+
db.urls[newSlug] = updated;
|
|
142
|
+
db.counter = Object.keys(db.urls).length;
|
|
143
|
+
await this.commitDatabase(
|
|
144
|
+
db,
|
|
145
|
+
sha,
|
|
146
|
+
`clipr: update ${slug}${newSlug !== slug ? ` \u2192 ${newSlug}` : ""}`
|
|
147
|
+
);
|
|
148
|
+
return updated;
|
|
149
|
+
}
|
|
150
|
+
async getStats(_slug) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
export {
|
|
155
|
+
GitHubBackend
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=github-PSGU4YYI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/backends/github.ts"],"sourcesContent":["import type {\n CreateOptions,\n LinkStats,\n ListOptions,\n ResolveResult,\n ShortUrl,\n UrlBackend,\n UrlDatabase,\n} from '@clipr/core';\nimport {\n appendUtm,\n DB_VERSION,\n generateRandomSlug,\n hasUtm,\n normalizeSlug,\n SlugConflictError,\n SlugNotFoundError,\n} from '@clipr/core';\n\ninterface GitHubConfig {\n owner: string;\n repo: string;\n branch: string;\n path: string;\n token: string;\n}\n\ninterface GitHubContentsResponse {\n sha: string;\n content: string;\n encoding: string;\n}\n\n/**\n * UrlBackend implementation using the GitHub Contents API.\n * Stores all URL data in a urls.json file in a GitHub repo.\n */\nexport class GitHubBackend implements UrlBackend {\n private readonly apiBase: string;\n private readonly headers: Record<string, string>;\n\n constructor(\n private readonly config: GitHubConfig,\n private readonly shortBaseUrl: string,\n ) {\n this.apiBase = `https://api.github.com/repos/${config.owner}/${config.repo}/contents/${config.path}`;\n this.headers = {\n Authorization: `token ${config.token}`,\n Accept: 'application/vnd.github.v3+json',\n 'Content-Type': 'application/json',\n };\n }\n\n /** Fetch the current urls.json from GitHub, returning content + SHA. */\n private async fetchDatabase(): Promise<{ db: UrlDatabase; sha: string }> {\n const url = `${this.apiBase}?ref=${this.config.branch}`;\n const res = await fetch(url, { headers: this.headers });\n\n if (res.status === 404) {\n // File doesn't exist yet, return empty database\n const db: UrlDatabase = {\n version: DB_VERSION,\n counter: 0,\n baseUrl: this.shortBaseUrl,\n urls: {},\n };\n return { db, sha: '' };\n }\n\n if (!res.ok) {\n throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);\n }\n\n const data = (await res.json()) as GitHubContentsResponse;\n const decoded = Buffer.from(data.content, 'base64').toString('utf-8');\n const db = JSON.parse(decoded) as UrlDatabase;\n\n return { db, sha: data.sha };\n }\n\n /** Commit updated urls.json back to GitHub. */\n private async commitDatabase(db: UrlDatabase, sha: string, message: string): Promise<void> {\n const content = Buffer.from(`${JSON.stringify(db, null, 2)}\\n`).toString('base64');\n\n const body: Record<string, string> = {\n message,\n content,\n branch: this.config.branch,\n };\n\n // Include SHA for updates (not for creation)\n if (sha) {\n body.sha = sha;\n }\n\n const res = await fetch(this.apiBase, {\n method: 'PUT',\n headers: this.headers,\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n throw new Error(`GitHub API commit failed: ${res.status} ${res.statusText} — ${errBody}`);\n }\n }\n\n async create(slug: string, targetUrl: string, options?: CreateOptions): Promise<ShortUrl> {\n const { db, sha } = await this.fetchDatabase();\n const finalSlug = normalizeSlug(slug || generateRandomSlug());\n\n if (db.urls[finalSlug]) {\n throw new SlugConflictError(finalSlug);\n }\n\n const now = new Date().toISOString();\n const entry: ShortUrl = {\n slug: finalSlug,\n url: targetUrl,\n createdAt: now,\n ...(options?.description && { description: options.description }),\n ...(options?.expiresAt && { expiresAt: options.expiresAt }),\n ...(options?.utm && hasUtm(options.utm) && { utm: options.utm }),\n ...(options?.tags && { tags: options.tags }),\n ...(options?.ogTitle && { ogTitle: options.ogTitle }),\n ...(options?.ogDescription && { ogDescription: options.ogDescription }),\n ...(options?.ogImage && { ogImage: options.ogImage }),\n };\n\n db.urls[finalSlug] = entry;\n db.counter = Object.keys(db.urls).length;\n\n await this.commitDatabase(db, sha, `clipr: add ${finalSlug} → ${targetUrl}`);\n return entry;\n }\n\n async resolve(slug: string): Promise<ResolveResult | null> {\n const { db } = await this.fetchDatabase();\n const entry = db.urls[slug];\n if (!entry) return null;\n\n const expired = entry.expiresAt ? new Date(entry.expiresAt) < new Date() : false;\n const url = hasUtm(entry.utm) ? appendUtm(entry.url, entry.utm!) : entry.url;\n\n return { url, passwordProtected: false, expired };\n }\n\n async list(options?: ListOptions): Promise<ShortUrl[]> {\n const { db } = await this.fetchDatabase();\n let entries = Object.values(db.urls) as ShortUrl[];\n\n if (options?.search) {\n const q = options.search.toLowerCase();\n entries = entries.filter(\n (e) =>\n e.slug.includes(q) ||\n e.url.toLowerCase().includes(q) ||\n e.description?.toLowerCase().includes(q),\n );\n }\n\n if (options?.tag) {\n entries = entries.filter((e) => e.tags?.includes(options.tag!));\n }\n\n if (options?.limit) {\n entries = entries.slice(0, options.limit);\n }\n\n return entries;\n }\n\n async delete(slug: string): Promise<void> {\n const { db, sha } = await this.fetchDatabase();\n\n if (!db.urls[slug]) {\n throw new SlugNotFoundError(slug);\n }\n\n delete db.urls[slug];\n db.counter = Object.keys(db.urls).length;\n\n await this.commitDatabase(db, sha, `clipr: delete ${slug}`);\n }\n\n async update(slug: string, updates: Partial<ShortUrl>): Promise<ShortUrl> {\n const { db, sha } = await this.fetchDatabase();\n const existing = db.urls[slug];\n\n if (!existing) {\n throw new SlugNotFoundError(slug);\n }\n\n const newSlug = updates.slug ?? slug;\n\n // If renaming, check for conflict and remove old entry\n if (newSlug !== slug) {\n if (db.urls[newSlug]) {\n throw new SlugConflictError(newSlug);\n }\n delete db.urls[slug];\n }\n\n const updated: ShortUrl = { ...existing, ...updates, slug: newSlug };\n db.urls[newSlug] = updated;\n db.counter = Object.keys(db.urls).length;\n\n await this.commitDatabase(\n db,\n sha,\n `clipr: update ${slug}${newSlug !== slug ? ` → ${newSlug}` : ''}`,\n );\n return updated;\n }\n\n async getStats(_slug: string): Promise<LinkStats | null> {\n // GitHub mode has no click tracking\n return null;\n }\n}\n"],"mappings":"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","names":["db"]}
|
package/dist/index.d.ts
ADDED