@hasna/shortlinks 0.1.13 → 0.1.14

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.
@@ -6,25 +6,32 @@ export default {
6
6
  }
7
7
 
8
8
  const incoming = new URL(request.url);
9
+ const proxyTo = (targetOrigin, marker) => {
10
+ const upstream = new URL(incoming.pathname + incoming.search, targetOrigin);
11
+ const headers = new Headers(request.headers);
12
+ headers.set("x-forwarded-host", incoming.host);
13
+ headers.set("x-shortlinks-worker", marker);
14
+
15
+ return fetch(upstream.toString(), {
16
+ method: request.method,
17
+ headers,
18
+ body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
19
+ redirect: "manual"
20
+ });
21
+ };
22
+
9
23
  const reserved = (env.SHORTLINKS_RESERVED_PATH_PREFIXES || "a")
10
24
  .split(",")
11
25
  .map((value) => value.trim().toLowerCase())
12
26
  .filter(Boolean);
13
27
  const firstSegment = decodeURIComponent(incoming.pathname.replace(/^\/+/, "").split("/")[0] || "").toLowerCase();
14
28
  if (firstSegment && reserved.includes(firstSegment)) {
29
+ if (env.ATTACHMENTS_ORIGIN) {
30
+ return proxyTo(env.ATTACHMENTS_ORIGIN, "attachments");
31
+ }
15
32
  return new Response("Reserved path prefix", { status: 404 });
16
33
  }
17
34
 
18
- const upstream = new URL(incoming.pathname + incoming.search, origin);
19
- const headers = new Headers(request.headers);
20
- headers.set("x-forwarded-host", incoming.host);
21
- headers.set("x-shortlinks-worker", "cloudflare");
22
-
23
- return fetch(upstream.toString(), {
24
- method: request.method,
25
- headers,
26
- body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
27
- redirect: "manual"
28
- });
35
+ return proxyTo(origin, "cloudflare");
29
36
  }
30
37
  };
@@ -4,4 +4,5 @@ compatibility_date = "2026-05-01"
4
4
 
5
5
  [vars]
6
6
  SHORTLINKS_ORIGIN = "https://shortlinks.example.com"
7
+ ATTACHMENTS_ORIGIN = ""
7
8
  SHORTLINKS_RESERVED_PATH_PREFIXES = "a"
package/dist/cli/index.js CHANGED
@@ -9585,26 +9585,33 @@ function generateWorkerScript() {
9585
9585
  }
9586
9586
 
9587
9587
  const incoming = new URL(request.url);
9588
+ const proxyTo = (targetOrigin, marker) => {
9589
+ const upstream = new URL(incoming.pathname + incoming.search, targetOrigin);
9590
+ const headers = new Headers(request.headers);
9591
+ headers.set("x-forwarded-host", incoming.host);
9592
+ headers.set("x-shortlinks-worker", marker);
9593
+
9594
+ return fetch(upstream.toString(), {
9595
+ method: request.method,
9596
+ headers,
9597
+ body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
9598
+ redirect: "manual"
9599
+ });
9600
+ };
9601
+
9588
9602
  const reserved = (env.SHORTLINKS_RESERVED_PATH_PREFIXES || "a")
9589
9603
  .split(",")
9590
9604
  .map((value) => value.trim().toLowerCase())
9591
9605
  .filter(Boolean);
9592
9606
  const firstSegment = decodeURIComponent(incoming.pathname.replace(/^\\/+/, "").split("/")[0] || "").toLowerCase();
9593
9607
  if (firstSegment && reserved.includes(firstSegment)) {
9608
+ if (env.ATTACHMENTS_ORIGIN) {
9609
+ return proxyTo(env.ATTACHMENTS_ORIGIN, "attachments");
9610
+ }
9594
9611
  return new Response("Reserved path prefix", { status: 404 });
9595
9612
  }
9596
9613
 
9597
- const upstream = new URL(incoming.pathname + incoming.search, origin);
9598
- const headers = new Headers(request.headers);
9599
- headers.set("x-forwarded-host", incoming.host);
9600
- headers.set("x-shortlinks-worker", "cloudflare");
9601
-
9602
- return fetch(upstream.toString(), {
9603
- method: request.method,
9604
- headers,
9605
- body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
9606
- redirect: "manual"
9607
- });
9614
+ return proxyTo(origin, "cloudflare");
9608
9615
  }
9609
9616
  };
9610
9617
  `;
@@ -9622,6 +9629,7 @@ compatibility_date = "2026-05-01"
9622
9629
 
9623
9630
  [vars]
9624
9631
  SHORTLINKS_ORIGIN = "${options.origin || "https://shortlinks.example.com"}"
9632
+ ATTACHMENTS_ORIGIN = "${options.attachmentsOrigin || ""}"
9625
9633
  SHORTLINKS_RESERVED_PATH_PREFIXES = "a"
9626
9634
  `);
9627
9635
  return { workerPath, wranglerPath };
@@ -10134,9 +10142,14 @@ cfCmd.command("plan <hostname>").description("Print the Cloudflare setup plan").
10134
10142
  handleError(error);
10135
10143
  }
10136
10144
  });
10137
- cfCmd.command("worker").description("Write Cloudflare Worker files").option("--out-dir <dir>", "Output directory", "cloudflare").option("--worker <name>", "Worker name", "shortlinks").option("--origin <url>", "Origin redirect server URL", process.env.SHORTLINKS_ORIGIN || "https://shortlinks.example.com").option("-j, --json", "Output JSON").action((opts) => {
10145
+ cfCmd.command("worker").description("Write Cloudflare Worker files").option("--out-dir <dir>", "Output directory", "cloudflare").option("--worker <name>", "Worker name", "shortlinks").option("--origin <url>", "Origin redirect server URL", process.env.SHORTLINKS_ORIGIN || "https://shortlinks.example.com").option("--attachments-origin <url>", "Origin URL for reserved attachment paths", process.env.ATTACHMENTS_ORIGIN).option("-j, --json", "Output JSON").action((opts) => {
10138
10146
  try {
10139
- const result = writeWorkerFiles({ outDir: opts.outDir, workerName: opts.worker, origin: opts.origin });
10147
+ const result = writeWorkerFiles({
10148
+ outDir: opts.outDir,
10149
+ workerName: opts.worker,
10150
+ origin: opts.origin,
10151
+ attachmentsOrigin: opts.attachmentsOrigin
10152
+ });
10140
10153
  print2(result, opts, () => {
10141
10154
  console.log(source_default.green(`Wrote ${result.workerPath}`));
10142
10155
  console.log(source_default.green(`Wrote ${result.wranglerPath}`));
@@ -32,6 +32,7 @@ export declare function writeWorkerFiles(options?: {
32
32
  outDir?: string;
33
33
  workerName?: string;
34
34
  origin?: string;
35
+ attachmentsOrigin?: string;
35
36
  }): {
36
37
  workerPath: string;
37
38
  wranglerPath: string;
@@ -56,26 +56,33 @@ function generateWorkerScript() {
56
56
  }
57
57
 
58
58
  const incoming = new URL(request.url);
59
+ const proxyTo = (targetOrigin, marker) => {
60
+ const upstream = new URL(incoming.pathname + incoming.search, targetOrigin);
61
+ const headers = new Headers(request.headers);
62
+ headers.set("x-forwarded-host", incoming.host);
63
+ headers.set("x-shortlinks-worker", marker);
64
+
65
+ return fetch(upstream.toString(), {
66
+ method: request.method,
67
+ headers,
68
+ body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
69
+ redirect: "manual"
70
+ });
71
+ };
72
+
59
73
  const reserved = (env.SHORTLINKS_RESERVED_PATH_PREFIXES || "a")
60
74
  .split(",")
61
75
  .map((value) => value.trim().toLowerCase())
62
76
  .filter(Boolean);
63
77
  const firstSegment = decodeURIComponent(incoming.pathname.replace(/^\\/+/, "").split("/")[0] || "").toLowerCase();
64
78
  if (firstSegment && reserved.includes(firstSegment)) {
79
+ if (env.ATTACHMENTS_ORIGIN) {
80
+ return proxyTo(env.ATTACHMENTS_ORIGIN, "attachments");
81
+ }
65
82
  return new Response("Reserved path prefix", { status: 404 });
66
83
  }
67
84
 
68
- const upstream = new URL(incoming.pathname + incoming.search, origin);
69
- const headers = new Headers(request.headers);
70
- headers.set("x-forwarded-host", incoming.host);
71
- headers.set("x-shortlinks-worker", "cloudflare");
72
-
73
- return fetch(upstream.toString(), {
74
- method: request.method,
75
- headers,
76
- body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
77
- redirect: "manual"
78
- });
85
+ return proxyTo(origin, "cloudflare");
79
86
  }
80
87
  };
81
88
  `;
@@ -93,6 +100,7 @@ compatibility_date = "2026-05-01"
93
100
 
94
101
  [vars]
95
102
  SHORTLINKS_ORIGIN = "${options.origin || "https://shortlinks.example.com"}"
103
+ ATTACHMENTS_ORIGIN = "${options.attachmentsOrigin || ""}"
96
104
  SHORTLINKS_RESERVED_PATH_PREFIXES = "a"
97
105
  `);
98
106
  return { workerPath, wranglerPath };
package/dist/index.js CHANGED
@@ -6315,26 +6315,33 @@ function generateWorkerScript() {
6315
6315
  }
6316
6316
 
6317
6317
  const incoming = new URL(request.url);
6318
+ const proxyTo = (targetOrigin, marker) => {
6319
+ const upstream = new URL(incoming.pathname + incoming.search, targetOrigin);
6320
+ const headers = new Headers(request.headers);
6321
+ headers.set("x-forwarded-host", incoming.host);
6322
+ headers.set("x-shortlinks-worker", marker);
6323
+
6324
+ return fetch(upstream.toString(), {
6325
+ method: request.method,
6326
+ headers,
6327
+ body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
6328
+ redirect: "manual"
6329
+ });
6330
+ };
6331
+
6318
6332
  const reserved = (env.SHORTLINKS_RESERVED_PATH_PREFIXES || "a")
6319
6333
  .split(",")
6320
6334
  .map((value) => value.trim().toLowerCase())
6321
6335
  .filter(Boolean);
6322
6336
  const firstSegment = decodeURIComponent(incoming.pathname.replace(/^\\/+/, "").split("/")[0] || "").toLowerCase();
6323
6337
  if (firstSegment && reserved.includes(firstSegment)) {
6338
+ if (env.ATTACHMENTS_ORIGIN) {
6339
+ return proxyTo(env.ATTACHMENTS_ORIGIN, "attachments");
6340
+ }
6324
6341
  return new Response("Reserved path prefix", { status: 404 });
6325
6342
  }
6326
6343
 
6327
- const upstream = new URL(incoming.pathname + incoming.search, origin);
6328
- const headers = new Headers(request.headers);
6329
- headers.set("x-forwarded-host", incoming.host);
6330
- headers.set("x-shortlinks-worker", "cloudflare");
6331
-
6332
- return fetch(upstream.toString(), {
6333
- method: request.method,
6334
- headers,
6335
- body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
6336
- redirect: "manual"
6337
- });
6344
+ return proxyTo(origin, "cloudflare");
6338
6345
  }
6339
6346
  };
6340
6347
  `;
@@ -6352,6 +6359,7 @@ compatibility_date = "2026-05-01"
6352
6359
 
6353
6360
  [vars]
6354
6361
  SHORTLINKS_ORIGIN = "${options.origin || "https://shortlinks.example.com"}"
6362
+ ATTACHMENTS_ORIGIN = "${options.attachmentsOrigin || ""}"
6355
6363
  SHORTLINKS_RESERVED_PATH_PREFIXES = "a"
6356
6364
  `);
6357
6365
  return { workerPath, wranglerPath };
@@ -8,6 +8,7 @@ export RDS_SECRET_ID="${RDS_SECRET_ID:-}"
8
8
  export RDS_HOST="${RDS_HOST:-}"
9
9
  export RDS_USERNAME="${RDS_USERNAME:-}"
10
10
  export SHORTLINKS_DOMAIN="${SHORTLINKS_DOMAIN:-}"
11
+ export ATTACHMENTS_ORIGIN="${ATTACHMENTS_ORIGIN:-}"
11
12
 
12
13
  : "${RDS_SECRET_ID:?Set RDS_SECRET_ID to the AWS Secrets Manager secret for the PostgreSQL database_url}"
13
14
  : "${SHORTLINKS_DOMAIN:?Set SHORTLINKS_DOMAIN to the public host served by Caddy}"
@@ -145,12 +146,32 @@ WantedBy=multi-user.target
145
146
  SERVICE
146
147
 
147
148
  install -d /etc/caddy
149
+ if [ -n "${ATTACHMENTS_ORIGIN}" ]; then
150
+ cat > /etc/caddy/Caddyfile <<CADDY
151
+ ${SHORTLINKS_DOMAIN} {
152
+ encode zstd gzip
153
+
154
+ handle /a/* {
155
+ reverse_proxy ${ATTACHMENTS_ORIGIN}
156
+ }
157
+
158
+ handle /api/* {
159
+ reverse_proxy ${ATTACHMENTS_ORIGIN}
160
+ }
161
+
162
+ handle {
163
+ reverse_proxy 127.0.0.1:8787
164
+ }
165
+ }
166
+ CADDY
167
+ else
148
168
  cat > /etc/caddy/Caddyfile <<CADDY
149
169
  ${SHORTLINKS_DOMAIN} {
150
170
  encode zstd gzip
151
171
  reverse_proxy 127.0.0.1:8787
152
172
  }
153
173
  CADDY
174
+ fi
154
175
 
155
176
  systemctl daemon-reload
156
177
  systemctl enable shortlinks.service caddy.service
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/shortlinks",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "CLI-only shortlink manager for custom domains, click tracking, Cloudflare setup, and repo-native storage sync",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",