@hasna/shortlinks 0.1.7 → 0.1.9
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 +10 -3
- package/infra/aws-ec2-user-data.sh +4 -36
- package/package.json +2 -2
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -3944
- package/dist/cloudflare.d.ts +0 -43
- package/dist/cloudflare.js +0 -168
- package/dist/config.d.ts +0 -20
- package/dist/database.d.ts +0 -11
- package/dist/domains-cli.d.ts +0 -10
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -877
- package/dist/local.d.ts +0 -27
- package/dist/machine.d.ts +0 -1
- package/dist/pg-migrations.d.ts +0 -1
- package/dist/server.d.ts +0 -12
- package/dist/server.js +0 -611
- package/dist/slug.d.ts +0 -4
- package/dist/store.d.ts +0 -30
- package/dist/types.d.ts +0 -90
package/dist/cloudflare.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export interface CloudflareSetupPlan {
|
|
2
|
-
hostname: string;
|
|
3
|
-
target: string;
|
|
4
|
-
proxied: boolean;
|
|
5
|
-
workerName: string;
|
|
6
|
-
origin: string;
|
|
7
|
-
dnsRecord: {
|
|
8
|
-
type: "CNAME";
|
|
9
|
-
name: string;
|
|
10
|
-
content: string;
|
|
11
|
-
proxied: boolean;
|
|
12
|
-
};
|
|
13
|
-
wranglerCommand: string;
|
|
14
|
-
}
|
|
15
|
-
export interface CloudflareDnsOptions {
|
|
16
|
-
hostname: string;
|
|
17
|
-
target: string;
|
|
18
|
-
token?: string;
|
|
19
|
-
zoneId?: string;
|
|
20
|
-
proxied?: boolean;
|
|
21
|
-
dryRun?: boolean;
|
|
22
|
-
}
|
|
23
|
-
export declare function createCloudflarePlan(input: {
|
|
24
|
-
hostname: string;
|
|
25
|
-
target: string;
|
|
26
|
-
origin: string;
|
|
27
|
-
workerName?: string;
|
|
28
|
-
proxied?: boolean;
|
|
29
|
-
}): CloudflareSetupPlan;
|
|
30
|
-
export declare function generateWorkerScript(): string;
|
|
31
|
-
export declare function writeWorkerFiles(options?: {
|
|
32
|
-
outDir?: string;
|
|
33
|
-
workerName?: string;
|
|
34
|
-
origin?: string;
|
|
35
|
-
}): {
|
|
36
|
-
workerPath: string;
|
|
37
|
-
wranglerPath: string;
|
|
38
|
-
};
|
|
39
|
-
export declare function findCloudflareZoneId(hostname: string, token?: string): Promise<string>;
|
|
40
|
-
export declare function upsertCloudflareDnsRecord(options: CloudflareDnsOptions): Promise<CloudflareSetupPlan | {
|
|
41
|
-
id: string;
|
|
42
|
-
action: "created" | "updated";
|
|
43
|
-
}>;
|
package/dist/cloudflare.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/cloudflare.ts
|
|
3
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
4
|
-
import { join as join2 } from "path";
|
|
5
|
-
|
|
6
|
-
// src/config.ts
|
|
7
|
-
import { homedir } from "os";
|
|
8
|
-
import { dirname, join, resolve } from "path";
|
|
9
|
-
var SERVICE_NAME = "shortlinks";
|
|
10
|
-
var DEFAULT_DATA_DIR = join(homedir(), ".hasna", SERVICE_NAME);
|
|
11
|
-
function normalizeHostname(input) {
|
|
12
|
-
const raw = input.trim().toLowerCase();
|
|
13
|
-
if (!raw)
|
|
14
|
-
throw new Error("Domain is required.");
|
|
15
|
-
const withProtocol = raw.includes("://") ? raw : `https://${raw}`;
|
|
16
|
-
let hostname;
|
|
17
|
-
try {
|
|
18
|
-
hostname = new URL(withProtocol).hostname;
|
|
19
|
-
} catch {
|
|
20
|
-
throw new Error(`Invalid domain: ${input}`);
|
|
21
|
-
}
|
|
22
|
-
hostname = hostname.replace(/\.$/, "");
|
|
23
|
-
if (!/^[a-z0-9.-]+$/.test(hostname) || hostname.includes("..")) {
|
|
24
|
-
throw new Error(`Invalid domain: ${input}`);
|
|
25
|
-
}
|
|
26
|
-
return hostname;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// src/cloudflare.ts
|
|
30
|
-
function createCloudflarePlan(input) {
|
|
31
|
-
const hostname = normalizeHostname(input.hostname);
|
|
32
|
-
const target = normalizeHostname(input.target);
|
|
33
|
-
const workerName = input.workerName || "shortlinks";
|
|
34
|
-
const proxied = input.proxied ?? true;
|
|
35
|
-
return {
|
|
36
|
-
hostname,
|
|
37
|
-
target,
|
|
38
|
-
proxied,
|
|
39
|
-
workerName,
|
|
40
|
-
origin: input.origin,
|
|
41
|
-
dnsRecord: {
|
|
42
|
-
type: "CNAME",
|
|
43
|
-
name: hostname,
|
|
44
|
-
content: target,
|
|
45
|
-
proxied
|
|
46
|
-
},
|
|
47
|
-
wranglerCommand: `wrangler deploy cloudflare/${workerName}.js --name ${workerName}`
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function generateWorkerScript() {
|
|
51
|
-
return `export default {
|
|
52
|
-
async fetch(request, env) {
|
|
53
|
-
const origin = env.SHORTLINKS_ORIGIN;
|
|
54
|
-
if (!origin) {
|
|
55
|
-
return new Response("SHORTLINKS_ORIGIN is not configured", { status: 500 });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const incoming = new URL(request.url);
|
|
59
|
-
const upstream = new URL(incoming.pathname + incoming.search, origin);
|
|
60
|
-
const headers = new Headers(request.headers);
|
|
61
|
-
headers.set("x-forwarded-host", incoming.host);
|
|
62
|
-
headers.set("x-shortlinks-worker", "cloudflare");
|
|
63
|
-
|
|
64
|
-
return fetch(upstream.toString(), {
|
|
65
|
-
method: request.method,
|
|
66
|
-
headers,
|
|
67
|
-
body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
|
|
68
|
-
redirect: "manual"
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
`;
|
|
73
|
-
}
|
|
74
|
-
function writeWorkerFiles(options = {}) {
|
|
75
|
-
const outDir = options.outDir || "cloudflare";
|
|
76
|
-
const workerName = options.workerName || "shortlinks";
|
|
77
|
-
mkdirSync(outDir, { recursive: true });
|
|
78
|
-
const workerPath = join2(outDir, `${workerName}.js`);
|
|
79
|
-
const wranglerPath = join2(outDir, "wrangler.example.toml");
|
|
80
|
-
writeFileSync(workerPath, generateWorkerScript());
|
|
81
|
-
writeFileSync(wranglerPath, `name = "${workerName}"
|
|
82
|
-
main = "${workerName}.js"
|
|
83
|
-
compatibility_date = "2026-05-01"
|
|
84
|
-
|
|
85
|
-
[vars]
|
|
86
|
-
SHORTLINKS_ORIGIN = "${options.origin || "https://shortlinks.example.com"}"
|
|
87
|
-
`);
|
|
88
|
-
return { workerPath, wranglerPath };
|
|
89
|
-
}
|
|
90
|
-
function cloudflareAuthHeaders(token) {
|
|
91
|
-
const apiToken = token || process.env.CLOUDFLARE_API_TOKEN;
|
|
92
|
-
if (apiToken)
|
|
93
|
-
return { authorization: `Bearer ${apiToken}` };
|
|
94
|
-
const apiKey = process.env.CLOUDFLARE_API_KEY;
|
|
95
|
-
const email = process.env.CLOUDFLARE_EMAIL;
|
|
96
|
-
if (apiKey && email) {
|
|
97
|
-
return {
|
|
98
|
-
"x-auth-key": apiKey,
|
|
99
|
-
"x-auth-email": email
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
throw new Error("Cloudflare auth is required: set CLOUDFLARE_API_TOKEN, or CLOUDFLARE_API_KEY plus CLOUDFLARE_EMAIL.");
|
|
103
|
-
}
|
|
104
|
-
async function cloudflareRequest(token, path, init = {}) {
|
|
105
|
-
const response = await fetch(`https://api.cloudflare.com/client/v4${path}`, {
|
|
106
|
-
...init,
|
|
107
|
-
headers: {
|
|
108
|
-
...cloudflareAuthHeaders(token),
|
|
109
|
-
"content-type": "application/json",
|
|
110
|
-
...init.headers || {}
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
const body = await response.json();
|
|
114
|
-
if (!response.ok || body.success === false) {
|
|
115
|
-
const message = body.errors?.map((e) => e.message).join("; ") || response.statusText;
|
|
116
|
-
throw new Error(`Cloudflare API failed: ${message}`);
|
|
117
|
-
}
|
|
118
|
-
return body.result;
|
|
119
|
-
}
|
|
120
|
-
function candidateZones(hostname) {
|
|
121
|
-
const parts = normalizeHostname(hostname).split(".");
|
|
122
|
-
const candidates = [];
|
|
123
|
-
for (let i = 0;i < parts.length - 1; i += 1) {
|
|
124
|
-
candidates.push(parts.slice(i).join("."));
|
|
125
|
-
}
|
|
126
|
-
return candidates;
|
|
127
|
-
}
|
|
128
|
-
async function findCloudflareZoneId(hostname, token) {
|
|
129
|
-
for (const zone of candidateZones(hostname)) {
|
|
130
|
-
const result = await cloudflareRequest(token, `/zones?name=${encodeURIComponent(zone)}`);
|
|
131
|
-
if (result[0]?.id)
|
|
132
|
-
return result[0].id;
|
|
133
|
-
}
|
|
134
|
-
throw new Error(`Could not find a Cloudflare zone for ${hostname}. Pass --zone-id explicitly.`);
|
|
135
|
-
}
|
|
136
|
-
async function upsertCloudflareDnsRecord(options) {
|
|
137
|
-
const token = options.token || process.env.CLOUDFLARE_API_TOKEN;
|
|
138
|
-
const plan = createCloudflarePlan({
|
|
139
|
-
hostname: options.hostname,
|
|
140
|
-
target: options.target,
|
|
141
|
-
origin: process.env.SHORTLINKS_ORIGIN || "https://shortlinks.example.com",
|
|
142
|
-
proxied: options.proxied
|
|
143
|
-
});
|
|
144
|
-
if (options.dryRun)
|
|
145
|
-
return plan;
|
|
146
|
-
const zoneId = options.zoneId || await findCloudflareZoneId(plan.hostname, token);
|
|
147
|
-
const existing = await cloudflareRequest(token, `/zones/${zoneId}/dns_records?type=CNAME&name=${encodeURIComponent(plan.hostname)}`);
|
|
148
|
-
const payload = JSON.stringify(plan.dnsRecord);
|
|
149
|
-
if (existing[0]?.id) {
|
|
150
|
-
const updated = await cloudflareRequest(token, `/zones/${zoneId}/dns_records/${existing[0].id}`, {
|
|
151
|
-
method: "PUT",
|
|
152
|
-
body: payload
|
|
153
|
-
});
|
|
154
|
-
return { id: updated.id, action: "updated" };
|
|
155
|
-
}
|
|
156
|
-
const created = await cloudflareRequest(token, `/zones/${zoneId}/dns_records`, {
|
|
157
|
-
method: "POST",
|
|
158
|
-
body: payload
|
|
159
|
-
});
|
|
160
|
-
return { id: created.id, action: "created" };
|
|
161
|
-
}
|
|
162
|
-
export {
|
|
163
|
-
writeWorkerFiles,
|
|
164
|
-
upsertCloudflareDnsRecord,
|
|
165
|
-
generateWorkerScript,
|
|
166
|
-
findCloudflareZoneId,
|
|
167
|
-
createCloudflarePlan
|
|
168
|
-
};
|
package/dist/config.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export declare const SERVICE_NAME = "shortlinks";
|
|
2
|
-
export declare const DEFAULT_DATA_DIR: string;
|
|
3
|
-
export interface ShortlinksConfig {
|
|
4
|
-
defaultDomain?: string;
|
|
5
|
-
publicBaseUrl?: string;
|
|
6
|
-
cloudflare?: {
|
|
7
|
-
accountId?: string;
|
|
8
|
-
workerName?: string;
|
|
9
|
-
origin?: string;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
export declare function getDataDir(): string;
|
|
13
|
-
export declare function ensureDataDir(): string;
|
|
14
|
-
export declare function getConfigPath(): string;
|
|
15
|
-
export declare function getDatabasePath(explicitPath?: string): string;
|
|
16
|
-
export declare function loadConfig(): ShortlinksConfig;
|
|
17
|
-
export declare function saveConfig(config: ShortlinksConfig): void;
|
|
18
|
-
export declare function updateConfig(patch: ShortlinksConfig): ShortlinksConfig;
|
|
19
|
-
export declare function normalizeHostname(input: string): string;
|
|
20
|
-
export declare function formatShortUrl(hostname: string, slug: string, publicBaseUrl?: string): string;
|
package/dist/database.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Database } from "bun:sqlite";
|
|
2
|
-
export declare function now(): string;
|
|
3
|
-
export declare function makeId(prefix: string): string;
|
|
4
|
-
export declare const SQLITE_MIGRATIONS: string[];
|
|
5
|
-
export declare class ShortlinksDatabase {
|
|
6
|
-
readonly db: Database;
|
|
7
|
-
readonly path: string;
|
|
8
|
-
constructor(path?: string);
|
|
9
|
-
close(): void;
|
|
10
|
-
private applyMigrations;
|
|
11
|
-
}
|
package/dist/domains-cli.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export type DomainsAction = "check" | "buy" | "setup";
|
|
2
|
-
export declare function buildDomainsArgs(action: DomainsAction, domain: string): string[];
|
|
3
|
-
export declare function runDomains(action: DomainsAction, domain: string, options?: {
|
|
4
|
-
dryRun?: boolean;
|
|
5
|
-
}): {
|
|
6
|
-
command: string;
|
|
7
|
-
status: number | null;
|
|
8
|
-
stdout: string;
|
|
9
|
-
stderr: string;
|
|
10
|
-
};
|
package/dist/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { ShortlinksDatabase, SQLITE_MIGRATIONS, makeId, now } from "./database.js";
|
|
2
|
-
export { ShortlinksStore } from "./store.js";
|
|
3
|
-
export { createShortlinksHandler, serveShortlinks } from "./server.js";
|
|
4
|
-
export { createCloudflarePlan, generateWorkerScript, writeWorkerFiles, upsertCloudflareDnsRecord } from "./cloudflare.js";
|
|
5
|
-
export { createLocalSetupPlan, registerMachinesDns } from "./local.js";
|
|
6
|
-
export { PG_MIGRATIONS } from "./pg-migrations.js";
|
|
7
|
-
export { formatShortUrl, getConfigPath, getDataDir, getDatabasePath, loadConfig, normalizeHostname, saveConfig } from "./config.js";
|
|
8
|
-
export { normalizeSlug, randomToken } from "./slug.js";
|
|
9
|
-
export type { AddDomainInput, Click, ClickInput, CreateLinkInput, Domain, Link, LinkStats } from "./types.js";
|