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

A refused New app leaves a repository behind #331

Merged opened this in answer to

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

Merged

scout/forge:scout/329-a-refused-new-app-leaves-a-repository-be → main 1 commit fork

  • Merged. scout/329-a-refused-new-app-leaves-a-repository-be was merged into main as 0d9a671.

Review

Sign in to review this change.
2 files changed +28 −4
src/actions/apps.ts +18 −3
@@ -20,6 +20,7 @@ import {
2020 createApp as insertApp,
2121 deleteApp as removeApp,
2222 getAppByRepo,
23 getAppBySlug,
2324 slugFor,
2425 updateApp as patchApp,
2526 type AppAccess,
@@ -41,7 +42,7 @@ import { listAllAgents } from "./crew.ts";
4142 import { plan, planIssueBody, planningAvailable } from "../crew/plan.ts";
4243 import type { Issue } from "../core/issues.ts";
4344 import { getPull } from "../core/pulls.ts";
44 import { canWrite, type Repo } from "../core/repos.ts";
45 import { canWrite, getRepo, type Repo } from "../core/repos.ts";
4546 import type { Run } from "../core/runs.ts";
4647 import type { User } from "../core/users.ts";
4748 import { resolveOid } from "../git/merge.ts";
@@ -59,6 +60,7 @@ import { assignIssue, openIssue } from "./issues.ts";
5960 import {
6061 commitToBranch,
6162 createRepo,
63 deleteRepo,
6264 type CreateRepoActionError,
6365 } from "./repos.ts";
6466
@@ -284,6 +286,14 @@ export async function newApp(
284286 : getAgentByName(app.db, input.agent);
285287 if (input.agent && !agent) return err("no_such_agent");
286288
289 // Refuse a taken slug before anything exists to take back.
290 // A name the owner already has stays `name_taken`, which createRepo says.
291 if (
292 getAppBySlug(app.db, slugFor(input.name)) &&
293 !getRepo(app.db, input.owner || user.value.username, input.name)
294 )
295 return err("slug_taken");
296
287297 const repo = await createRepo(app, user.value, {
288298 name: input.name,
289299 visibility: input.visibility ?? "private",
@@ -298,12 +308,17 @@ export async function newApp(
298308 description: input.description ?? "",
299309 }),
300310 });
301 if (!committed.ok) return err("scaffold_failed");
311 if (!committed.ok) {
312 await deleteRepo(app, user.value, repo.value);
313 return err("scaffold_failed");
314 }
302315 const record = enableApp(app, user.value, repo.value, {});
303316 // The creator administers the repository they just made, so `not_found`
304317 // here can only mean the row vanished under us — a failed scaffold.
305 if (!record.ok)
318 if (!record.ok) {
319 await deleteRepo(app, user.value, repo.value);
306320 return err(record.error === "not_found" ? "scaffold_failed" : record.error);
321 }
307322
308323 const description = (input.description ?? "").trim();
309324 let chosen = agent;
test/apps.test.ts +10 −1
@@ -39,7 +39,7 @@ import {
3939 import type { AppRecord } from "../src/core/apps.ts";
4040 import { listEvents } from "../src/core/events.ts";
4141 import { getPull, type Pull } from "../src/core/pulls.ts";
42 import { getRepoById, type Repo } from "../src/core/repos.ts";
42 import { getRepo, getRepoById, type Repo } from "../src/core/repos.ts";
4343 import { resolveOid } from "../src/git/merge.ts";
4444 import { repoDir } from "../src/git/repos.ts";
4545 import {
@@ -1373,6 +1373,15 @@ printf '%s\\n' '{"is_error":false,"result":"{\\"summary\\":\\"Build a notes app\
13731373 expect(made.value.issue?.body).toContain("- [ ] Add a form");
13741374 });
13751375
1376 test("a slug already in use is refused before any repository exists", async () => {
1377 const first = await newApp(app, alice, { name: "held-x" });
1378 expect(first.ok).toBe(true);
1379 const second = await newApp(app, alice, { name: "held_x" });
1380 expect(second.ok).toBe(false);
1381 if (!second.ok) expect(second.error).toBe("slug_taken");
1382 expect(getRepo(app.db, alice.username, "held_x")).toBeUndefined();
1383 });
1384
13761385 test("a plan naming an agent not on the forge is refused; the sentence still opens unassigned", async () => {
13771386 const made = await newApp(app, alice, {
13781387 name: "notes-b",

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs