uplink-cli 0.2.2 → 0.2.3
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/CHANGELOG.md +6 -0
- package/cli/src/index.ts +3 -0
- package/cli/src/subcommands/domains.ts +13 -6
- package/cli/src/subcommands/menu.ts +2 -26
- package/cli/src/subcommands/upgrade.ts +62 -0
- package/cli/src/tui/domain-search.mts +10 -0
- package/cli/src/utils/run-esm.ts +38 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.3 — 2026-08-30
|
|
4
|
+
|
|
5
|
+
New **`uplink upgrade`** (Uplink Pro subscription — $9/mo or `--yearly` $90/yr via Stripe Checkout) and **`uplink billing`** (Stripe billing portal for cancel/card changes). Pro unlocks unlimited hosted apps within 1 GB of storage, always-on for your 5 most-active apps, custom domains, and permanent aliases.
|
|
6
|
+
|
|
7
|
+
Fix global CLI crash on every command (`host list`, `login`, …): `domains.ts` was statically importing Ink, so tsx tried to load `yoga-layout` as CommonJS (`Top-level await is currently not supported with the "cjs" output format`). Interactive Find a domain now runs as a child ESM process, same as the menu.
|
|
8
|
+
|
|
3
9
|
## 0.2.2 — 2026-08-29
|
|
4
10
|
|
|
5
11
|
Built-in **Find a domain** in the CLI (no Domainking install). Type a label to check common TLDs via DNS/RDAP. `uplink domains` opens it; agents use `uplink domains search acme --json`. Domainking remains a separate app.
|
package/cli/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { signupCommand } from "./subcommands/signup";
|
|
|
10
10
|
import { systemCommand } from "./subcommands/system";
|
|
11
11
|
import { hostCommand } from "./subcommands/host";
|
|
12
12
|
import { domainsCommand } from "./subcommands/domains";
|
|
13
|
+
import { upgradeCommand, billingCommand } from "./subcommands/upgrade";
|
|
13
14
|
import { readFileSync } from "fs";
|
|
14
15
|
import { join } from "path";
|
|
15
16
|
import { ensureApiBase, parseTokenEnv } from "./utils/api-base";
|
|
@@ -39,6 +40,8 @@ program.addCommand(systemCommand);
|
|
|
39
40
|
program.addCommand(menuCommand);
|
|
40
41
|
program.addCommand(hostCommand);
|
|
41
42
|
program.addCommand(domainsCommand);
|
|
43
|
+
program.addCommand(upgradeCommand);
|
|
44
|
+
program.addCommand(billingCommand);
|
|
42
45
|
|
|
43
46
|
// Global pre-action hook to apply shared options
|
|
44
47
|
let cachedTokenStdin: string | null = null;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { join } from "path";
|
|
2
3
|
import { handleError, printJson } from "../utils/machine";
|
|
4
|
+
import { runEsmEntry } from "../utils/run-esm";
|
|
3
5
|
import {
|
|
4
6
|
adapters,
|
|
5
7
|
getAdapter,
|
|
@@ -18,7 +20,14 @@ import {
|
|
|
18
20
|
formatPublicAvailability,
|
|
19
21
|
} from "../utils/domain-availability";
|
|
20
22
|
import { searchDomains } from "../utils/domain-search";
|
|
21
|
-
|
|
23
|
+
|
|
24
|
+
function runDomainSearchTui(): void {
|
|
25
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
26
|
+
console.log("Domain search needs a terminal. Agents: uplink domains search myapp --json");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
runEsmEntry(join(__dirname, "../tui/domain-search.mts"));
|
|
30
|
+
}
|
|
22
31
|
|
|
23
32
|
// DreamHost last: it can only confirm ownership, not quote availability.
|
|
24
33
|
const CHECK_ORDER: ProviderId[] = ["godaddy", "cloudflare", "hostinger", "namecheap", "dreamhost"];
|
|
@@ -137,9 +146,8 @@ domainsCommand.addHelpText(
|
|
|
137
146
|
"\nWith no subcommand, opens Find a domain (type a name; common TLDs are checked via DNS/RDAP).\n"
|
|
138
147
|
);
|
|
139
148
|
|
|
140
|
-
domainsCommand.action(
|
|
141
|
-
|
|
142
|
-
if (message) console.log(message);
|
|
149
|
+
domainsCommand.action(() => {
|
|
150
|
+
runDomainSearchTui();
|
|
143
151
|
});
|
|
144
152
|
|
|
145
153
|
domainsCommand
|
|
@@ -223,8 +231,7 @@ domainsCommand
|
|
|
223
231
|
try {
|
|
224
232
|
if (!name) {
|
|
225
233
|
if (opts.json) throw new Error("Pass a name: uplink domains search acme --json");
|
|
226
|
-
|
|
227
|
-
if (message) console.log(message);
|
|
234
|
+
runDomainSearchTui();
|
|
228
235
|
return;
|
|
229
236
|
}
|
|
230
237
|
const results = await searchDomains(name);
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { spawnSync } from "child_process";
|
|
3
2
|
import { join } from "path";
|
|
4
|
-
|
|
5
|
-
function projectRoot(): string {
|
|
6
|
-
return join(__dirname, "../../..");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function resolveTsx(): string {
|
|
10
|
-
const root = projectRoot();
|
|
11
|
-
try {
|
|
12
|
-
return require.resolve("tsx/dist/cli.cjs", { paths: [root] });
|
|
13
|
-
} catch {
|
|
14
|
-
try {
|
|
15
|
-
return require.resolve("tsx/cli", { paths: [root] });
|
|
16
|
-
} catch {
|
|
17
|
-
return "tsx";
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
3
|
+
import { runEsmEntry } from "../utils/run-esm";
|
|
21
4
|
|
|
22
5
|
/**
|
|
23
6
|
* Ink 6 is ESM-only (yoga-layout uses top-level await). The CLI package is
|
|
@@ -30,12 +13,5 @@ export const menuCommand = new Command("menu")
|
|
|
30
13
|
console.error("Uplink menu needs an interactive terminal. Use `uplink --help` for commands.");
|
|
31
14
|
process.exit(1);
|
|
32
15
|
}
|
|
33
|
-
|
|
34
|
-
const result = spawnSync(resolveTsx(), [entry], {
|
|
35
|
-
stdio: "inherit",
|
|
36
|
-
cwd: projectRoot(),
|
|
37
|
-
env: process.env,
|
|
38
|
-
});
|
|
39
|
-
if (result.error) throw result.error;
|
|
40
|
-
process.exit(result.status ?? 0);
|
|
16
|
+
runEsmEntry(join(__dirname, "../tui/index.mts"));
|
|
41
17
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { apiRequest } from "../http";
|
|
4
|
+
import { handleError, printJson } from "../utils/machine";
|
|
5
|
+
|
|
6
|
+
function openInBrowser(url: string): void {
|
|
7
|
+
const [cmd, args] =
|
|
8
|
+
process.platform === "darwin"
|
|
9
|
+
? ["open", [url]]
|
|
10
|
+
: process.platform === "win32"
|
|
11
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
12
|
+
: ["xdg-open", [url]];
|
|
13
|
+
try {
|
|
14
|
+
spawn(cmd, args, { detached: true, stdio: "ignore" }).unref();
|
|
15
|
+
} catch {
|
|
16
|
+
// Non-fatal: the URL is printed either way.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const upgradeCommand = new Command("upgrade")
|
|
21
|
+
.description("Upgrade to Uplink Pro — unlimited apps in 1 GB, 5 always-on, custom domains, aliases")
|
|
22
|
+
.option("--yearly", "Yearly billing (2 months free)", false)
|
|
23
|
+
.option("--json", "Output JSON (prints the checkout URL, does not open a browser)", false)
|
|
24
|
+
.action(async (opts) => {
|
|
25
|
+
try {
|
|
26
|
+
const interval = opts.yearly ? "year" : "month";
|
|
27
|
+
const result = await apiRequest("POST", "/v1/billing/checkout", { interval });
|
|
28
|
+
if (opts.json) {
|
|
29
|
+
printJson({ url: result.url, interval: result.interval });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
console.log("");
|
|
33
|
+
console.log("Uplink Pro — complete your upgrade in the browser:");
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log(` ${result.url}`);
|
|
36
|
+
console.log("");
|
|
37
|
+
openInBrowser(result.url);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
handleError(error, { json: opts.json });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const billingCommand = new Command("billing")
|
|
44
|
+
.description("Manage your subscription (opens the Stripe billing portal)")
|
|
45
|
+
.option("--json", "Output JSON (prints the portal URL, does not open a browser)", false)
|
|
46
|
+
.action(async (opts) => {
|
|
47
|
+
try {
|
|
48
|
+
const result = await apiRequest("POST", "/v1/billing/portal");
|
|
49
|
+
if (opts.json) {
|
|
50
|
+
printJson({ url: result.url });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
console.log("");
|
|
54
|
+
console.log("Manage your subscription here:");
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log(` ${result.url}`);
|
|
57
|
+
console.log("");
|
|
58
|
+
openInBrowser(result.url);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
handleError(error, { json: opts.json });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
export function cliPackageRoot(): string {
|
|
6
|
+
return join(__dirname, "../../..");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function resolveTsx(): string {
|
|
10
|
+
const root = cliPackageRoot();
|
|
11
|
+
try {
|
|
12
|
+
return require.resolve("tsx/dist/cli.cjs", { paths: [root] });
|
|
13
|
+
} catch {
|
|
14
|
+
try {
|
|
15
|
+
return require.resolve("tsx/cli", { paths: [root] });
|
|
16
|
+
} catch {
|
|
17
|
+
return "tsx";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Ink 6 is ESM-only (yoga-layout uses top-level await). The CLI package is
|
|
24
|
+
* CommonJS, so Ink screens must run as a child ESM process rather than being
|
|
25
|
+
* imported from commander commands.
|
|
26
|
+
*/
|
|
27
|
+
export function runEsmEntry(entry: string): never {
|
|
28
|
+
const root = cliPackageRoot();
|
|
29
|
+
const tsconfigPath = join(root, "tsconfig.json");
|
|
30
|
+
const tsxArgs = existsSync(tsconfigPath) ? ["--tsconfig", tsconfigPath, entry] : [entry];
|
|
31
|
+
const result = spawnSync(resolveTsx(), tsxArgs, {
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
cwd: root,
|
|
34
|
+
env: process.env,
|
|
35
|
+
});
|
|
36
|
+
if (result.error) throw result.error;
|
|
37
|
+
process.exit(result.status ?? 0);
|
|
38
|
+
}
|
package/package.json
CHANGED