@hasna/shortlinks 0.1.2 → 0.1.4

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 CHANGED
@@ -67,6 +67,17 @@ shortlinks serve --port 8787
67
67
  shortlinks doctor
68
68
  ```
69
69
 
70
+ ## Local Domain Setup
71
+
72
+ Record a local mapping with the `machines` CLI and print the remaining hosts/proxy setup:
73
+
74
+ ```bash
75
+ shortlinks local setup has.na --port 8787
76
+ shortlinks local plan has.na --port 8787
77
+ ```
78
+
79
+ The command emits the `/etc/hosts` line, a Caddy reverse-proxy snippet, and certificate paths. Writing `/etc/hosts` still requires sudo on macOS.
80
+
70
81
  ## Custom Domains
71
82
 
72
83
  Add as many domains as you need:
package/dist/cli/index.js CHANGED
@@ -2560,9 +2560,9 @@ var source_default = chalk;
2560
2560
 
2561
2561
  // src/cli/index.ts
2562
2562
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2563
- import { dirname as dirname3, join as join4 } from "path";
2563
+ import { dirname as dirname3, join as join5 } from "path";
2564
2564
  import { fileURLToPath } from "url";
2565
- import { spawnSync as spawnSync2 } from "child_process";
2565
+ import { spawnSync as spawnSync3 } from "child_process";
2566
2566
 
2567
2567
  // src/store.ts
2568
2568
  import { createHash } from "crypto";
@@ -3324,6 +3324,54 @@ function runDomains(action, domain, options = {}) {
3324
3324
  };
3325
3325
  }
3326
3326
 
3327
+ // src/local.ts
3328
+ import { spawnSync as spawnSync2 } from "child_process";
3329
+ import { homedir as homedir2 } from "os";
3330
+ import { join as join4 } from "path";
3331
+ function createLocalSetupPlan(input) {
3332
+ const domain = normalizeHostname(input.domain);
3333
+ const targetHost = input.targetHost || "127.0.0.1";
3334
+ const port = input.port || 8787;
3335
+ const certDir = input.certDir || join4(homedir2(), ".hasna", "machines", "certs");
3336
+ const certPath = join4(certDir, `${domain}.pem`);
3337
+ const keyPath = join4(certDir, `${domain}-key.pem`);
3338
+ return {
3339
+ domain,
3340
+ targetHost,
3341
+ port,
3342
+ hostsEntry: `${targetHost} ${domain}`,
3343
+ caddySnippet: `${domain} {
3344
+ reverse_proxy ${targetHost}:${port}
3345
+ tls ${certPath} ${keyPath}
3346
+ }`,
3347
+ certPath,
3348
+ keyPath,
3349
+ machinesCommand: `machines dns add --domain ${domain} --target-host ${targetHost} --port ${port} --json`,
3350
+ sudoRequired: true
3351
+ };
3352
+ }
3353
+ function registerMachinesDns(input) {
3354
+ const plan = createLocalSetupPlan(input);
3355
+ const args = [
3356
+ "dns",
3357
+ "add",
3358
+ "--domain",
3359
+ plan.domain,
3360
+ "--target-host",
3361
+ plan.targetHost,
3362
+ "--port",
3363
+ String(plan.port),
3364
+ "--json"
3365
+ ];
3366
+ const result = spawnSync2("machines", args, { encoding: "utf-8" });
3367
+ return {
3368
+ command: `machines ${args.join(" ")}`,
3369
+ status: result.status,
3370
+ stdout: result.stdout || "",
3371
+ stderr: result.stderr || ""
3372
+ };
3373
+ }
3374
+
3327
3375
  // src/pg-migrations.ts
3328
3376
  var PG_MIGRATIONS = [
3329
3377
  `
@@ -3400,7 +3448,7 @@ var PG_MIGRATIONS = [
3400
3448
  // src/cli/index.ts
3401
3449
  function getPackageVersion() {
3402
3450
  try {
3403
- const pkgPath = join4(dirname3(fileURLToPath(import.meta.url)), "..", "..", "package.json");
3451
+ const pkgPath = join5(dirname3(fileURLToPath(import.meta.url)), "..", "..", "package.json");
3404
3452
  return JSON.parse(readFileSync3(pkgPath, "utf-8")).version || "0.0.0";
3405
3453
  } catch {
3406
3454
  return "0.0.0";
@@ -3447,7 +3495,7 @@ function formatLink(link) {
3447
3495
  return `${source_default.green(link.short_url || `${link.hostname}/${link.slug}`)} ${source_default.dim("->")} ${link.destination_url}`;
3448
3496
  }
3449
3497
  function commandExists(command) {
3450
- const result = spawnSync2("which", [command], { encoding: "utf-8" });
3498
+ const result = spawnSync3("which", [command], { encoding: "utf-8" });
3451
3499
  return result.status === 0;
3452
3500
  }
3453
3501
  program2.name("shortlinks").description("CLI-only shortlink manager with custom domains, click tracking, Cloudflare helpers, and cloud sync").version(getPackageVersion()).option("--db <path>", "SQLite database path").option("-j, --json", "Output JSON for agents and scripts");
@@ -3826,6 +3874,42 @@ cloudCmd.command("status").description("Show local and cloud configuration healt
3826
3874
  handleError(error);
3827
3875
  }
3828
3876
  });
3877
+ var localCmd = program2.command("local").description("Local domain setup helpers");
3878
+ localCmd.command("plan <domain>").description("Render hosts and reverse-proxy setup for a local shortlink domain").option("--port <port>", "Local redirect server port", "8787").option("--target-host <host>", "Local target host", "127.0.0.1").option("-j, --json", "Output JSON").action((domain, opts) => {
3879
+ try {
3880
+ const plan = createLocalSetupPlan({
3881
+ domain,
3882
+ port: Number(opts.port),
3883
+ targetHost: opts.targetHost
3884
+ });
3885
+ print(plan, opts, () => console.log(JSON.stringify(plan, null, 2)));
3886
+ } catch (error) {
3887
+ handleError(error);
3888
+ }
3889
+ });
3890
+ localCmd.command("setup <domain>").description("Record local domain mapping with machines and print remaining sudo-only setup").option("--port <port>", "Local redirect server port", "8787").option("--target-host <host>", "Local target host", "127.0.0.1").option("--skip-machines", "Do not call machines dns add").option("-j, --json", "Output JSON").action((domain, opts) => {
3891
+ try {
3892
+ const plan = createLocalSetupPlan({
3893
+ domain,
3894
+ port: Number(opts.port),
3895
+ targetHost: opts.targetHost
3896
+ });
3897
+ const machines = opts.skipMachines ? null : registerMachinesDns({
3898
+ domain,
3899
+ port: Number(opts.port),
3900
+ targetHost: opts.targetHost
3901
+ });
3902
+ const result = { plan, machines };
3903
+ print(result, opts, () => {
3904
+ if (machines && machines.status !== 0) {
3905
+ console.error(source_default.yellow(machines.stderr.trim() || "machines dns add failed"));
3906
+ }
3907
+ console.log(JSON.stringify(result, null, 2));
3908
+ });
3909
+ } catch (error) {
3910
+ handleError(error);
3911
+ }
3912
+ });
3829
3913
  program2.command("doctor").description("Check local shortlinks tooling and integration readiness").option("-j, --json", "Output JSON").action((opts) => {
3830
3914
  try {
3831
3915
  const stats = withStore((store) => store.totalStats());
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { ShortlinksDatabase, SQLITE_MIGRATIONS, makeId, now } from "./database.j
2
2
  export { ShortlinksStore } from "./store.js";
3
3
  export { createShortlinksHandler, serveShortlinks } from "./server.js";
4
4
  export { createCloudflarePlan, generateWorkerScript, writeWorkerFiles, upsertCloudflareDnsRecord } from "./cloudflare.js";
5
+ export { createLocalSetupPlan, registerMachinesDns } from "./local.js";
5
6
  export { PG_MIGRATIONS } from "./pg-migrations.js";
6
7
  export { formatShortUrl, getConfigPath, getDataDir, getDatabasePath, loadConfig, normalizeHostname, saveConfig } from "./config.js";
7
8
  export { normalizeSlug, randomToken } from "./slug.js";
package/dist/index.js CHANGED
@@ -728,6 +728,53 @@ async function upsertCloudflareDnsRecord(options) {
728
728
  });
729
729
  return { id: created.id, action: "created" };
730
730
  }
731
+ // src/local.ts
732
+ import { spawnSync } from "child_process";
733
+ import { homedir as homedir2 } from "os";
734
+ import { join as join4 } from "path";
735
+ function createLocalSetupPlan(input) {
736
+ const domain = normalizeHostname(input.domain);
737
+ const targetHost = input.targetHost || "127.0.0.1";
738
+ const port = input.port || 8787;
739
+ const certDir = input.certDir || join4(homedir2(), ".hasna", "machines", "certs");
740
+ const certPath = join4(certDir, `${domain}.pem`);
741
+ const keyPath = join4(certDir, `${domain}-key.pem`);
742
+ return {
743
+ domain,
744
+ targetHost,
745
+ port,
746
+ hostsEntry: `${targetHost} ${domain}`,
747
+ caddySnippet: `${domain} {
748
+ reverse_proxy ${targetHost}:${port}
749
+ tls ${certPath} ${keyPath}
750
+ }`,
751
+ certPath,
752
+ keyPath,
753
+ machinesCommand: `machines dns add --domain ${domain} --target-host ${targetHost} --port ${port} --json`,
754
+ sudoRequired: true
755
+ };
756
+ }
757
+ function registerMachinesDns(input) {
758
+ const plan = createLocalSetupPlan(input);
759
+ const args = [
760
+ "dns",
761
+ "add",
762
+ "--domain",
763
+ plan.domain,
764
+ "--target-host",
765
+ plan.targetHost,
766
+ "--port",
767
+ String(plan.port),
768
+ "--json"
769
+ ];
770
+ const result = spawnSync("machines", args, { encoding: "utf-8" });
771
+ return {
772
+ command: `machines ${args.join(" ")}`,
773
+ status: result.status,
774
+ stdout: result.stdout || "",
775
+ stderr: result.stderr || ""
776
+ };
777
+ }
731
778
  // src/pg-migrations.ts
732
779
  var PG_MIGRATIONS = [
733
780
  `
@@ -805,6 +852,7 @@ export {
805
852
  upsertCloudflareDnsRecord,
806
853
  serveShortlinks,
807
854
  saveConfig,
855
+ registerMachinesDns,
808
856
  randomToken,
809
857
  now,
810
858
  normalizeSlug,
@@ -817,6 +865,7 @@ export {
817
865
  generateWorkerScript,
818
866
  formatShortUrl,
819
867
  createShortlinksHandler,
868
+ createLocalSetupPlan,
820
869
  createCloudflarePlan,
821
870
  ShortlinksStore,
822
871
  ShortlinksDatabase,
@@ -0,0 +1,27 @@
1
+ export interface LocalSetupPlan {
2
+ domain: string;
3
+ targetHost: string;
4
+ port: number;
5
+ hostsEntry: string;
6
+ caddySnippet: string;
7
+ certPath: string;
8
+ keyPath: string;
9
+ machinesCommand: string;
10
+ sudoRequired: boolean;
11
+ }
12
+ export declare function createLocalSetupPlan(input: {
13
+ domain: string;
14
+ port?: number;
15
+ targetHost?: string;
16
+ certDir?: string;
17
+ }): LocalSetupPlan;
18
+ export declare function registerMachinesDns(input: {
19
+ domain: string;
20
+ port?: number;
21
+ targetHost?: string;
22
+ }): {
23
+ command: string;
24
+ status: number | null;
25
+ stdout: string;
26
+ stderr: string;
27
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/shortlinks",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "CLI-only shortlink manager for custom domains, click tracking, Cloudflare setup, and @hasna cloud sync",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -34,8 +34,7 @@
34
34
  "typecheck": "tsc --noEmit",
35
35
  "test": "bun test",
36
36
  "dev:cli": "bun run src/cli/index.ts",
37
- "prepublishOnly": "bun run test && bun run build",
38
- "postinstall": "mkdir -p $HOME/.hasna/shortlinks 2>/dev/null || true"
37
+ "prepublishOnly": "bun run test && bun run build"
39
38
  },
40
39
  "keywords": [
41
40
  "shortlinks",