Public: anyone can read this; the crew can change it.

The terminal reads its refusals from the modules that own them, not its own copies #5599

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/5594-the-terminal-reads-its-refusals-from-the → main 2 commits fork

  • Merged. mason/5594-the-terminal-reads-its-refusals-from-the was merged into main as 5e57f18.

Review

Sign in to review this change.
7 files changed +85 −8
scripts/shards.ts +3 −2
@@ -445,7 +445,8 @@ export function sliceFiles(
445445
446446 /**
447447 * Parses `--shard N/M` (or the same shape from env). Empty / undefined means
448 * no matrix slice — run every named file.
448 * no matrix slice — run every named file. Four digits a side: the gate
449 * passed 999 files, and a slice per file must still be nameable.
449450 */
450451 export function parseShardSpec(
451452 spec: string | undefined,
@@ -454,7 +455,7 @@ export function parseShardSpec(
454455 | { ok: true; none: true }
455456 | { ok: false; error: string } {
456457 if (spec === undefined || spec.trim() === "") return { ok: true, none: true };
457 const m = /^([1-9][0-9]{0,2})\/([1-9][0-9]{0,2})$/.exec(spec.trim());
458 const m = /^([1-9][0-9]{0,3})\/([1-9][0-9]{0,3})$/.exec(spec.trim());
458459 if (!m)
459460 return {
460461 ok: false,
src/actions/origins.ts +4 −1
@@ -103,6 +103,9 @@ import {
103103 type AccessError,
104104 } from "./access.ts";
105105
106 /** The words a connect carries when the account is already connected. */
107 export const ORIGIN_ALREADY_CONNECTED = "that account is already connected";
108
106109 // ---- abuse control ----
107110
108111 /**
@@ -646,7 +649,7 @@ async function completeConnect(
646649 expiresAt: tokens.expiresAt,
647650 });
648651 if (!stored.ok)
649 return { status: "failed", detail: "that account is already connected" };
652 return { status: "failed", detail: ORIGIN_ALREADY_CONNECTED };
650653 await afterConnect(app, user, stored.value);
651654 return { status: "done", detail: "", originId: stored.value.id };
652655 }
src/http/api.ts +4 −2
@@ -74,6 +74,7 @@ import { CAPPED_WORDS } from "../actions/mail.ts";
7474 import { emailVerified } from "../core/verify.ts";
7575 import { readHealth } from "../actions/health.ts";
7676 import { isProbe } from "../kernel/probe.ts";
77 import { PRIVATE_ADDRESS_REFUSED } from "../kernel/address_words.ts";
7778 import { listChildren } from "../actions/spawn.ts";
7879 import { forgeState, PARENT_HEADER } from "../spawn/clients.ts";
7980 import { familyView } from "../spawn/family.ts";
@@ -120,6 +121,7 @@ import {
120121 syncOriginNow,
121122 syncRepoNow,
122123 unpinRepo,
124 ORIGIN_ALREADY_CONNECTED,
123125 } from "../actions/origins.ts";
124126 import {
125127 createBranch,
@@ -6586,11 +6588,11 @@ export function forgeBaseUrl(
65866588 export function connectMessage(code: string): string {
65876589 if (code === "unknown_host") return "that does not look like a forge host";
65886590 if (code === "private_host")
6589 return "that address is inside a network, not on the internet";
6591 return PRIVATE_ADDRESS_REFUSED;
65906592 if (code === "unreachable") return "that host could not be reached";
65916593 if (code === "bad_token") return "the origin rejected that token";
65926594 if (code === "already_connected")
6593 return "that account is already connected here";
6595 return `${ORIGIN_ALREADY_CONNECTED} here`;
65946596 if (code === "rate_limited")
65956597 return "too many connect attempts; wait a minute";
65966598 return "that origin could not be connected";
src/kernel/address_words.ts added +7
@@ -0,0 +1,7 @@
1 /**
2 * The sentence a refused address carries when it points inside a network
3 * rather than at the internet. The terminal and the API both say it, so it
4 * is said once here and they import it.
5 */
6 export const PRIVATE_ADDRESS_REFUSED =
7 "that address is inside a network, not on the internet";
src/tui/screens/common.ts +6 −3
@@ -15,6 +15,9 @@ import { binding, type Binding } from "../app/keys.ts";
1515 import type { KeyEvent } from "../term/input.ts";
1616 import { ARCHIVED_WHY, type RepoCounts } from "../../core/repos.ts";
1717 import { SIGNUP_CLOSED_MESSAGE } from "../../actions/users.ts";
18 import { ORIGIN_ALREADY_CONNECTED } from "../../actions/origins.ts";
19 import { NEW_ACCOUNT_GUARD_MESSAGE } from "../../core/capacity.ts";
20 import { PRIVATE_ADDRESS_REFUSED } from "../../kernel/address_words.ts";
1821 import type { Session } from "../session.ts";
1922 import { G, Screen as Surface, type Rect } from "../term/screen.ts";
2023 import { style } from "../term/style.ts";
@@ -826,7 +829,7 @@ const MESSAGES: Record<string, string> = {
826829 unauthorized: "sign in first",
827830 forbidden: "you do not have permission",
828831 forbidden_owner: "you cannot create repos for that owner",
829 refused: "new accounts cannot create repositories yet",
832 refused: NEW_ACCOUNT_GUARD_MESSAGE,
830833 empty_title: "a title is required",
831834 empty_body: "write something first",
832835 title_too_long: "that title is too long (256 characters at most)",
@@ -881,10 +884,10 @@ const MESSAGES: Record<string, string> = {
881884 origin_owner_only: "only whoever connected this origin can write through it",
882885 origin_refused: "the origin refused that; nothing changed here",
883886 origin_unreachable: "the origin could not be reached; nothing changed",
884 already_connected: "that account is already connected",
887 already_connected: ORIGIN_ALREADY_CONNECTED,
885888 already_mirrored: "that repository is already mirrored here",
886889 unknown_host: "that does not look like a forge",
887 private_host: "that address is inside a network, not on the internet",
890 private_host: PRIVATE_ADDRESS_REFUSED,
888891 unreachable: "that instance could not be reached",
889892 bad_token: "the origin refused that token",
890893 rate_limited: "too many connect attempts; wait a few minutes",
test/shards.test.ts +1
@@ -513,6 +513,7 @@ describe("parseShardSpec", () => {
513513 expect(parseShardSpec("1/3")).toEqual({ ok: true, index: 1, total: 3 });
514514 expect(parseShardSpec("3/3")).toEqual({ ok: true, index: 3, total: 3 });
515515 expect(parseShardSpec(" 2/4 ")).toEqual({ ok: true, index: 2, total: 4 });
516 expect(parseShardSpec("1000/1000")).toEqual({ ok: true, index: 1000, total: 1000 });
516517 for (const bad of ["0/3", "1/0", "4/3", "1", "1-3", "a/b", "1/3/5"])
517518 expect(parseShardSpec(bad).ok).toBe(false);
518519 });
test/tui-words.test.ts added +60
@@ -0,0 +1,60 @@
1 // The terminal's refusals for a private address, a new account's repository
2 // limit and an origin already connected are said by the modules that own
3 // them. These tests fail if a copy of one is typed back into a file that
4 // should import it.
5 import { describe, expect, test } from "bun:test";
6 import { readFileSync } from "node:fs";
7 import { join } from "node:path";
8 import { ORIGIN_ALREADY_CONNECTED } from "../src/actions/origins.ts";
9 import { NEW_ACCOUNT_GUARD_MESSAGE } from "../src/core/capacity.ts";
10 import { PRIVATE_ADDRESS_REFUSED } from "../src/kernel/address_words.ts";
11
12 const root = join(import.meta.dir, "..");
13 const source = (path: string) => readFileSync(join(root, path), "utf8");
14
15 const words: [string, string, string, string[]][] = [
16 [
17 "PRIVATE_ADDRESS_REFUSED",
18 PRIVATE_ADDRESS_REFUSED,
19 "that address is inside a network, not on the internet",
20 ["src/tui/screens/common.ts", "src/http/api.ts"],
21 ],
22 [
23 "NEW_ACCOUNT_GUARD_MESSAGE",
24 NEW_ACCOUNT_GUARD_MESSAGE,
25 "new accounts cannot create repositories yet",
26 ["src/tui/screens/common.ts"],
27 ],
28 [
29 "ORIGIN_ALREADY_CONNECTED",
30 ORIGIN_ALREADY_CONNECTED,
31 "that account is already connected",
32 ["src/tui/screens/common.ts", "src/http/api.ts"],
33 ],
34 ];
35
36 describe("the terminal's refusals are said from one place", () => {
37 for (const [name, value, sentence, files] of words) {
38 test(`${name} is the sentence, and non-empty`, () => {
39 expect(value.length).toBeGreaterThan(0);
40 expect(value).toBe(sentence);
41 });
42 for (const file of files) {
43 test(`${file} imports ${name} rather than typing it`, () => {
44 const text = source(file);
45 expect(text).toContain(name);
46 expect(text).not.toContain(sentence);
47 });
48 }
49 }
50
51 test("actions/origins.ts says its sentence once, in the export", () => {
52 const text = source("src/actions/origins.ts");
53 expect(text.split(ORIGIN_ALREADY_CONNECTED).length - 1).toBe(1);
54 });
55
56 test("core/capacity.ts says its sentence once, in the export", () => {
57 const text = source("src/core/capacity.ts");
58 expect(text.split(NEW_ACCOUNT_GUARD_MESSAGE).length - 1).toBe(1);
59 });
60 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs