Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion docs/product/cli-style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,20 @@ Recommended symbols:
- Banners are reserved for `init` and similar first-run experiences.
- Outside those flows, focus on status, context, result, and next steps.

Human-oriented command output in TTY mode should usually start with a compact header:
Human-oriented command output in TTY mode should usually start with a compact header.

Bound state:

```text
project show → This directory is linked to the following platform project.

│ local repo ~/code/apple
│ platform Edith / orange
│ → https://prisma.build/edith/orange
```

Recovery state:

```text
project show → This directory is not linked to a Prisma Project.
Expand All @@ -84,6 +97,8 @@ Rules:
- mask sensitive values rather than omitting their presence entirely when the value matters to the flow
- include only rows that are actually known for the current command
- use human labels such as `Not linked` instead of internal resolution terms such as `unbound`
- hide internal resolution terms such as `local pin` from default human output when the visible binding is clearer
- document distinct success and recovery states when a command's terminal output materially differs
- include a `Read more` row that points to the source-of-truth repo doc or anchor until a stable public docs URL exists
- leave one blank line between the header block and the body

Expand Down
1 change: 1 addition & 0 deletions docs/product/command-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ Behavior:
- does not mutate local state
- `--project <id-or-name>` resolves only the explicit project
- when bound, returns Workspace, Project, and `resolution.projectSource`
- when bound, human output shows the local repo path, the `<workspace> / <project>` platform label, and the Project URL; it does not show the internal resolution source
- when unbound, human output says `project: Not linked` and shows link/create next steps
- when unbound, JSON exits successfully with `project: null`, `localBinding.status: "not-linked"`, `resolution.projectSource: "unbound"`, a suggested Project name, matching Project candidates, recovery commands, and `user-choice` `nextActions`
- package names and directory names only power unbound suggestions
Expand Down
15 changes: 14 additions & 1 deletion docs/product/output-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,18 @@ Human output should:
- keep header metadata compact and aligned
- avoid placeholder rows for unknown values

Recommended header shape:
Recommended header shape for a bound Project:

```text
project show → This directory is linked to the following platform project.

│ local repo ~/code/apple
│ platform Edith / orange
│ → https://prisma.build/edith/orange
```

Recommended recovery shape for an unbound Project:

```text
project show → This directory is not linked to a Prisma Project.
Expand All @@ -238,6 +249,8 @@ Rules:
- include `Read more` when a stable repo doc reference exists
- prefer display labels in default human output and keep opaque ids in JSON unless a later verbose mode explicitly asks for them
- do not expose agent-only reasoning in human output when a clear status and next step is enough
- for bound `project show`, show the local repo, platform project label, and Project URL instead of the internal resolution source
- keep explicit recovery examples when a command has a distinct not-linked or setup-required state

Recommended summary lines:

Expand Down
3 changes: 3 additions & 0 deletions packages/cli/fixtures/mock-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,21 @@
"id": "proj_123",
"name": "Acme Dashboard",
"slug": "acme-dashboard",
"url": "https://prisma.build/acme/acme-dashboard",
"workspaceId": "ws_123"
},
{
"id": "proj_456",
"name": "Billing API",
"slug": "billing-api",
"url": "https://prisma.build/acme/billing-api",
"workspaceId": "ws_123"
},
{
"id": "proj_789",
"name": "Website",
"slug": "website",
"url": "https://prisma.build/prisma/website",
"workspaceId": "ws_456"
}
],
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/adapters/mock-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface ProjectRecord {
id: string;
name: string;
slug: string;
url?: string;
workspaceId: string;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/controllers/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ export async function listRealWorkspaceProjects(
.map((project) => ({
id: project.id,
name: project.name,
...("url" in project && typeof project.url === "string" ? { url: project.url } : {}),
slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
workspace: {
id: project.workspace.id,
Expand All @@ -608,6 +609,7 @@ export function listFixtureWorkspaceProjects(
context.api.listProjectsForWorkspace(workspace.id).map((project) => ({
id: project.id,
name: project.name,
...(project.url ? { url: project.url } : {}),
slug: project.slug,
workspace,
})),
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/lib/project/resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,10 @@ function buildProjectRecoveryCommands(commandName: string | undefined): string[]
return commands;
}

function toProjectSummary(project: Pick<ProjectCandidate, "id" | "name">): ProjectSummary {
function toProjectSummary(project: Pick<ProjectCandidate, "id" | "name" | "url">): ProjectSummary {
return {
id: project.id,
name: project.name,
...(project.url ? { url: project.url } : {}),
};
}
3 changes: 2 additions & 1 deletion packages/cli/src/lib/project/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ export async function bindProjectToDirectory(
};
}

export function toProjectSummary(project: Pick<ProjectCandidate, "id" | "name">): ProjectSummary {
export function toProjectSummary(project: Pick<ProjectCandidate, "id" | "name" | "url">): ProjectSummary {
return {
id: project.id,
name: project.name,
...(project.url ? { url: project.url } : {}),
};
}

Expand Down
67 changes: 38 additions & 29 deletions packages/cli/src/presenters/project.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import path from "node:path";

import type { CommandDescriptor } from "../shell/command-meta";
import { formatDescriptorLabel } from "../shell/command-meta";
import { formatCommandArgument } from "../shell/command-arguments";
import type { CommandContext } from "../shell/runtime";
import type {
Expand All @@ -9,7 +12,7 @@ import type {
ProjectShowResult,
} from "../types/project";
import { renderList, renderMutate, renderShow, serializeList } from "../output/patterns";
import { renderNextSteps, renderSummaryLine } from "../shell/ui";
import { padDisplay, renderNextSteps, renderSummaryLine } from "../shell/ui";

export function renderProjectList(
context: CommandContext,
Expand Down Expand Up @@ -88,18 +91,7 @@ export function renderProjectShow(
return lines;
}

return renderShow(
{
title: "Showing this directory's Project binding.",
descriptor,
fields: [
{ key: "workspace", value: result.workspace.name },
{ key: "project", value: result.project.name },
{ key: "resolution", value: formatProjectSource(result.resolution.projectSource) },
],
},
context.ui,
);
return renderBoundProjectShow(context, descriptor, result);
}

export function serializeProjectShow(result: ProjectShowResult) {
Expand Down Expand Up @@ -173,23 +165,40 @@ export function renderGitDisconnect(
);
}

function formatProjectSource(source: ProjectShowResult["resolution"]["projectSource"]): string {
switch (source) {
case "explicit":
return "explicit";
case "env":
return "environment";
case "local-pin":
return "local pin";
case "platform-mapping":
return "platform mapping";
case "created":
return "created";
case "prompt":
return "prompt";
case "unbound":
return "unbound";
function renderBoundProjectShow(
context: CommandContext,
descriptor: CommandDescriptor,
result: Exclude<ProjectShowResult, { project: null }>,
): string[] {
const { ui } = context;
const rail = ui.dim("│");
const keyWidth = "local repo".length;
const platform = `${result.workspace.name} / ${result.project.name}`;
const lines = [
`${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("This directory is linked to the following platform project.")}`,
"",
`${rail} ${ui.accent(padDisplay("local repo", keyWidth))} ${formatLocalRepoPath(context.runtime.cwd, context.runtime.env)}`,
`${rail} ${ui.accent(padDisplay("platform", keyWidth))} ${ui.strong(platform)}`,
];

if (result.project.url) {
lines.push(rail);
lines.push(`${rail} ${ui.dim("→")} ${ui.link(result.project.url)}`);
}

return lines;
}

function formatLocalRepoPath(cwd: string, env: NodeJS.ProcessEnv): string {
const resolved = path.resolve(cwd);
const home = env.HOME ? path.resolve(env.HOME) : null;

if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
const relative = path.relative(home, resolved);
return relative ? `~/${relative}` : "~";
}

return resolved;
}

function formatGitConnectionDetail(status: GitRepositoryConnection["status"]): string {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/types/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AuthWorkspace } from "./auth";
export interface ProjectSummary {
id: string;
name: string;
url?: string;
}

export type ProjectSource =
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/use-cases/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ function listSortedWorkspaceProjects(projectGateway: ProjectGateway, workspaceId
.sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
}

function toProjectSummary(project: { id: string; name: string }): ProjectSummary {
function toProjectSummary(project: { id: string; name: string; url?: string }): ProjectSummary {
return {
id: project.id,
name: project.name,
...(project.url ? { url: project.url } : {}),
};
}

Expand Down
9 changes: 5 additions & 4 deletions packages/cli/tests/project-real-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ function mockClient(extra: Partial<{
return {
data: {
data: [
{ id: "proj_456", name: "Billing API", slug: "billing-api", workspace: { id: "ws_123", name: "Acme Inc" } },
{ id: "proj_456", name: "Billing API", slug: "billing-api", url: "https://prisma.build/acme/billing-api", workspace: { id: "ws_123", name: "Acme Inc" } },
{ id: "proj_999", name: "Alpha", slug: "alpha", workspace: { id: "ws_other", name: "Other" } },
{ id: "proj_123", name: "Acme Dashboard", slug: "acme-dashboard", workspace: { id: "ws_123", name: "Acme Inc" } },
{ id: "proj_123", name: "Acme Dashboard", slug: "acme-dashboard", url: "https://prisma.build/acme/acme-dashboard", workspace: { id: "ws_123", name: "Acme Inc" } },
],
},
};
Expand Down Expand Up @@ -144,8 +144,8 @@ describe("real project mode", () => {
name: "Acme Inc",
},
projects: [
{ id: "proj_123", name: "Acme Dashboard" },
{ id: "proj_456", name: "Billing API" },
{ id: "proj_123", name: "Acme Dashboard", url: "https://prisma.build/acme/acme-dashboard" },
{ id: "proj_456", name: "Billing API", url: "https://prisma.build/acme/billing-api" },
],
localBinding: {
status: "not-linked",
Expand Down Expand Up @@ -191,6 +191,7 @@ describe("real project mode", () => {
project: {
id: "proj_123",
name: "Acme Dashboard",
url: "https://prisma.build/acme/acme-dashboard",
},
resolution: {
projectSource: "explicit",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/tests/project-usecases.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ describe("project use cases", () => {
{
id: "proj_123",
name: "Acme Dashboard",
url: "https://prisma.build/acme/acme-dashboard",
},
{
id: "proj_456",
name: "Billing API",
url: "https://prisma.build/acme/billing-api",
},
],
});
Expand Down
Loading
Loading