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

A repository name's rules and its disk-init failure live in one module, read by migrate, apps and new repository #5600

Merged opened this

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

Merged

mason/forge:mason/5584-a-repository-name-s-rules-and-its-disk-i → main 1 commit fork 1 thread · 1 unresolved

  • Merged. mason/5584-a-repository-name-s-rules-and-its-disk-i was merged into main as 900217e.

Review

Sign in to review this change.
5 files changed +49 −6
src/actions/migrate.ts +3 −2
@@ -64,6 +64,7 @@ import {
6464 } from "../origins/jobs.ts";
6565 import type { Provider } from "../origins/types.ts";
6666 import { requireUser } from "./access.ts";
67 import { REPO_INIT_FAILED, REPO_NAME_RULES } from "./repo_words.ts";
6768 import { enableApp } from "./apps.ts";
6869 import { addToCrew } from "./crew.ts";
6970 import {
@@ -271,9 +272,9 @@ export function migrateMessage(error: MigrateError, limit = "at the repositories
271272 case "name_taken":
272273 return "That owner already has a repository by that name. Pick another name.";
273274 case "invalid_name":
274 return "Names start with a letter or digit; no spaces, no .git.";
275 return REPO_NAME_RULES;
275276 case "git_init_failed":
276 return "The repository could not be initialised on disk.";
277 return REPO_INIT_FAILED;
277278 case "forbidden_owner":
278279 return "You cannot create repositories for that owner.";
279280 case "unauthorized":
src/actions/repo_words.ts added +11
@@ -0,0 +1,11 @@
1 /**
2 * The words a person reads when a new repository is refused: its name
3 * breaks the rules, or git could not make it on disk. Migrate, a new app
4 * and a new repository all say them, so they are said from here.
5 */
6
7 /** A name `createRepo` refused as `invalid_name`. */
8 export const REPO_NAME_RULES = "Names start with a letter or digit; no spaces, no .git.";
9
10 /** `createRepo` failed as `git_init_failed`. */
11 export const REPO_INIT_FAILED = "The repository could not be initialised on disk.";
src/web/pages/apps.ts +3 −2
@@ -14,6 +14,7 @@
1414
1515 import { storageLimitMessage } from "../../core/storage.ts";
1616 import { repositoryLimitMessage } from "../../actions/repos.ts";
17 import { REPO_INIT_FAILED, REPO_NAME_RULES } from "../../actions/repo_words.ts";
1718 import { appLimitWords } from "../../actions/app_limit.ts";
1819 import { getUserByName } from "../../core/users.ts";
1920 import {
@@ -433,8 +434,8 @@ const MAX_NAME_ATTEMPTS = 30;
433434
434435 const NEW_APP_ERRORS: Record<string, string> = {
435436 name_taken: "That owner already has a repository by that name.",
436 invalid_name: "Names start with a letter or digit; no spaces, no .git.",
437 git_init_failed: "The repository could not be initialised on disk.",
437 invalid_name: REPO_NAME_RULES,
438 git_init_failed: REPO_INIT_FAILED,
438439 invalid_slug:
439440 "That name makes no usable hostname — start it with a letter or digit.",
440441 slug_taken:
src/web/pages/site.ts +3 −2
@@ -17,6 +17,7 @@ import { clientAddress } from "../../http/peer.ts";
1717 import { verifyEmail } from "../../actions/verify.ts";
1818 import { STRANGER_MAX } from "../../http/limits.ts";
1919 import { createRepo, repositoryLimitMessage, resolveOwner } from "../../actions/repos.ts";
20 import { REPO_INIT_FAILED, REPO_NAME_RULES } from "../../actions/repo_words.ts";
2021 import { HUMAN_CHECK_MESSAGE, INVITE_SPENT_MESSAGE, PASSKEY_REQUIRED_MESSAGE, SIGNUP_CLOSED_MESSAGE, signUp, signupClosed } from "../../actions/users.ts";
2122 import { accessAllows, listOrgsForUser, orgAccess } from "../../core/orgs.ts";
2223 import { listPins } from "../../core/pins.ts";
@@ -842,8 +843,8 @@ export async function newRepoSubmit(ctx: Ctx): Promise<Response> {
842843 repository_limit: `Not made: ${repositoryLimitMessage(ctx.app, ctx.auth.user)}.`,
843844 storage_limit: `Not made: ${storageLimitMessage(ctx.app, resolveOwner(ctx.app, ctx.auth.user, values.owner) ?? ctx.auth.user.id)}.`,
844845 name_taken: "That owner already has a repository by that name.",
845 invalid_name: "Names start with a letter or digit; no spaces, no .git.",
846 git_init_failed: "The repository could not be initialised on disk.",
846 invalid_name: REPO_NAME_RULES,
847 git_init_failed: REPO_INIT_FAILED,
847848 };
848849 return page(
849850 {
test/repo-words.test.ts added +29
@@ -0,0 +1,29 @@
1 /**
2 * A refused repository name and a failed disk init read the same from
3 * migrate, a new app and a new repository, because each says them from
4 * src/actions/repo_words.ts. A copy typed back into one of them fails here.
5 */
6
7 import { describe, expect, test } from "bun:test";
8 import { readFileSync } from "node:fs";
9 import { join } from "node:path";
10 import { REPO_INIT_FAILED, REPO_NAME_RULES } from "../src/actions/repo_words.ts";
11
12 const WORDS = { REPO_NAME_RULES, REPO_INIT_FAILED };
13 const READERS = ["src/actions/migrate.ts", "src/web/pages/apps.ts", "src/web/pages/site.ts"];
14
15 describe("a repository's refusal words", () => {
16 for (const [name, words] of Object.entries(WORDS)) {
17 test(`${name} is a sentence`, () => {
18 expect(words.trim().length).toBeGreaterThan(0);
19 });
20 }
21
22 for (const file of READERS) {
23 test(`${file} imports the words and types no copy`, () => {
24 const source = readFileSync(join(import.meta.dir, "..", file), "utf8");

This asserts on source text, so it fails on a harmless import reshuffle and passes if the constant is imported but unused. Call migrateMessage({invalid_name}) and compare it to REPO_NAME_RULES instead.

25 expect(source).toContain("repo_words.ts");
26 for (const words of Object.values(WORDS)) expect(source).not.toContain(words);
27 });
28 }
29 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs