Skip to content
Open
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
Binary file added docs/assets/auth-logout-update-docs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/auth-logout-update-npm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 9 additions & 4 deletions docs/product/output-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ when all of these are true:
- `--quiet` is not active
- CI is not detected
- `NO_UPDATE_NOTIFIER` is not set
- cached update-check state already shows a newer official `@prisma/cli`
- cached update-check state already shows a newer official `prisma`
version

The notification is human-only stderr output. It must never be written to
Expand All @@ -87,12 +87,13 @@ Update available: prisma <current> -> <latest>
Run <package-manager command> to update.
```

When the CLI cannot confidently infer the install context, link to installation
docs instead of guessing a package-manager command:
The update command must name the published `prisma` package. When the CLI
cannot confidently infer the install context, link directly to the Prisma CLI
installation instructions instead of guessing a package-manager command:

```text
Update available: prisma <current> -> <latest>
See https://www.prisma.io/docs/orm/tools/prisma-cli for update instructions.
See https://www.prisma.io/docs/orm/release-status#what-you-get-when-you-install-today for installation commands.
```

## Out-Of-Date Agent Skills
Expand Down Expand Up @@ -195,6 +196,10 @@ Current MVP commands map to patterns like this:

No current MVP command uses `verify` or `inspect`, but new commands must still choose one existing pattern rather than inventing a new one casually.

`auth logout` reports the number of ended workspace sessions in its success
summary, not as a second `ended` field. The structured result retains
`endedCount` and `workspaceIds` for automation.

### Workspace session identity

`auth login --ui-context prisma-plugin` changes only the browser completion
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/cli-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
*/
export const CLI_NAME = "prisma";

/** The unified CLI's docs section (also the update-check fallback
* instruction URL). */
/** The unified CLI's docs section. */
export const CLI_DOCS_URL = "https://www.prisma.io/docs/cli";

/**
Expand Down
9 changes: 1 addition & 8 deletions packages/cli/src/commands/auth/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,9 @@ function presentationsFor(
result.endedCount === 0
? "No workspace sessions to end."
: `Ended ${result.endedCount} workspace ${result.endedCount === 1 ? "session" : "sessions"}.`;
const rows = [{ label: "ended", value: String(result.endedCount) }];
return {
json: () => result,
human: () => [
{
kind: "summary",
status: "info",
text: "Clearing your stored workspace sessions.",
},
{ kind: "fields", rows },
{ kind: "summary", status: "ok", text: summary },
...(environmentInForce
? [
Expand All @@ -44,7 +37,7 @@ function presentationsFor(
]
: []),
],
stdout: () => rows.map((row) => `${row.label}: ${row.value}`),
stdout: () => [],
next: () => [SIGN_IN],
};
}
Expand Down
49 changes: 26 additions & 23 deletions packages/cli/src/update-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";

import { CLI_DOCS_URL } from "./cli-name";
import { compareVersionStrings } from "./lib/semver-order";
import { getCliName, getCliVersion } from "./lib/version";

Expand All @@ -18,9 +17,11 @@ export interface UpdateCheckRuntime {
}

const UPDATE_CHECK_FILE_NAME = "update-check.json";
const FALLBACK_INSTALL_DOCS_URL = CLI_DOCS_URL;
const PACKAGE_NAME = "prisma";
const FALLBACK_INSTALL_DOCS_URL =
"https://www.prisma.io/docs/orm/release-status#what-you-get-when-you-install-today";
const NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
const REGISTRY_URL = "https://registry.npmjs.org/@prisma%2fcli";
const REGISTRY_URL = "https://registry.npmjs.org/prisma";
const REGISTRY_TIMEOUT_MS = 3_000;

export interface UpdateCheckState {
Expand Down Expand Up @@ -79,7 +80,8 @@ export async function maybeWriteCachedUpdateNotification(
try {
const cacheDir = resolveUpdateCheckCacheDir(runtime);
const store = new UpdateCheckStore(cacheDir);
const state = await store.read();
const cached = await store.read();
const state = cached?.packageName === PACKAGE_NAME ? cached : null;
const latestVersion = state?.latestVersion;

if (
Expand All @@ -95,7 +97,7 @@ export async function maybeWriteCachedUpdateNotification(
);
await store.write({
...state,
packageName: "@prisma/cli",
packageName: PACKAGE_NAME,
installedVersion: getCliVersion(),
notifiedAt: new Date().toISOString(),
});
Expand Down Expand Up @@ -124,10 +126,11 @@ export async function runUpdateDiscovery(options: {
}

const store = new UpdateCheckStore(options.cacheDir);
const previousState = await store.read();
const cached = await store.read();
const previousState = cached?.packageName === PACKAGE_NAME ? cached : null;
await store.write({
...previousState,
packageName: "@prisma/cli",
packageName: PACKAGE_NAME,
installedVersion: options.installedVersion,
latestVersion,
checkedAt: (options.now ?? new Date()).toISOString(),
Expand Down Expand Up @@ -216,7 +219,7 @@ async function scheduleRemoteDiscovery(
const checkedAt = new Date().toISOString();
await store.write({
...state,
packageName: "@prisma/cli",
packageName: PACKAGE_NAME,
installedVersion: getCliVersion(),
checkedAt,
});
Expand Down Expand Up @@ -257,27 +260,27 @@ export function selectUpdateInstruction(
return docsInstruction();
}

if (entrypoint.includes("/node_modules/.bin/")) {
if (
env.npm_config_global === "true" ||
isLikelyGlobalNpmEntrypoint(entrypoint)
) {
return commandInstruction("npm install --global prisma@latest");
}

if (entrypoint.includes("/node_modules/")) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (userAgent.startsWith("pnpm")) {
return commandInstruction("pnpm add -D @prisma/cli@latest");
return commandInstruction("pnpm add -D prisma@latest");
}

if (userAgent.startsWith("bun")) {
return commandInstruction("bun add -d @prisma/cli@latest");
return commandInstruction("bun add -d prisma@latest");
}

if (userAgent.startsWith("npm")) {
return commandInstruction("npm install --save-dev @prisma/cli@latest");
return commandInstruction("npm install --save-dev prisma@latest");
}
}

if (
env.npm_config_global === "true" ||
isLikelyGlobalNpmEntrypoint(entrypoint)
) {
return commandInstruction("npm install --global @prisma/cli@latest");
}

return docsInstruction();
}

Expand All @@ -297,22 +300,22 @@ function renderUpdateInstruction(instruction: UpdateInstruction): string {
return `Run ${instruction.value} to update.`;
}

return `See ${instruction.value} for update instructions.`;
return `See ${instruction.value} for installation commands.`;
}

function isEphemeralInvocation(entrypoint: string, lifecycle: string): boolean {
return (
lifecycle === "npx" ||
lifecycle === "pnpx" ||
entrypoint.includes("/_npx/") ||
entrypoint.includes("/.bun/")
entrypoint.includes("/.bun/install/cache/")
);
}

function isLikelyGlobalNpmEntrypoint(entrypoint: string): boolean {
return (
/\/npm\/prisma-cli(\.cmd|\.exe)?$/.test(entrypoint) ||
/\/npm-global\/bin\/prisma-cli$/.test(entrypoint)
/\/npm\/prisma(\.cmd|\.exe)?$/.test(entrypoint) ||
/\/npm-global\/bin\/prisma$/.test(entrypoint)
);
}

Expand Down
9 changes: 2 additions & 7 deletions packages/cli/tests/golden-rendering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,9 @@ describe("golden rendering", () => {

expect(result.exitCode).toBe(0);
expect(result.stderr).toBe(
"ℹ Clearing your stored workspace sessions.\n" +
"\n" +
"ended: 1\n" +
"\n" +
"✔ Ended 1 workspace session.\n" +
"→ Sign in: prisma auth login\n",
"✔ Ended 1 workspace session.\n" + "→ Sign in: prisma auth login\n",
);
expect(result.stdout).toBe("ended: 1\n");
expect(result.stdout).toBe("");
});

it("table (representative: auth workspace list)", async () => {
Expand Down
27 changes: 25 additions & 2 deletions packages/cli/tests/update-check-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function nextMajorVersion(): string {

async function seedStaleUpdate(updateCheckDir: string): Promise<void> {
await new UpdateCheckStore(updateCheckDir).write({
packageName: "@prisma/cli",
packageName: "prisma",
installedVersion: getCliVersion(),
latestVersion: nextMajorVersion(),
checkedAt: new Date().toISOString(),
Expand All @@ -101,6 +101,9 @@ describe("main update-check wiring", () => {
expect(proc.stderrText).toContain(
`Update available: prisma ${getCliVersion()} -> ${nextMajorVersion()}`,
);
expect(proc.stderrText).toContain(
"See https://www.prisma.io/docs/orm/release-status#what-you-get-when-you-install-today for installation commands.",
);
expect(proc.stdoutText).toBe("");
});

Expand Down Expand Up @@ -153,6 +156,26 @@ describe("main update-check wiring", () => {
expect(second.stderrText).not.toContain("Update available");
});

it("discards cached versions from the old @prisma/cli package", async () => {
const updateCheckDir = await makeUpdateCheckDir();
await new UpdateCheckStore(updateCheckDir).write({
packageName: "@prisma/cli",
installedVersion: getCliVersion(),
latestVersion: nextMajorVersion(),
checkedAt: new Date().toISOString(),
});
const proc = makeProcess({ env: updateCheckEnv(updateCheckDir) });

await main(proc, stubCli);

expect(proc.stderrText).not.toContain("Update available");
expect(vi.mocked(spawn)).toHaveBeenCalledTimes(1);
const state = JSON.parse(
await readFile(path.join(updateCheckDir, "update-check.json"), "utf8"),
) as Record<string, unknown>;
expect(state.packageName).toBe("prisma");
});

it("stays silent in json mode (legacy behavior, copied)", async () => {
const updateCheckDir = await makeUpdateCheckDir();
await seedStaleUpdate(updateCheckDir);
Expand Down Expand Up @@ -219,7 +242,7 @@ describe("main update-check wiring", () => {
it("skips the refresh spawn inside the 24-hour discovery interval", async () => {
const updateCheckDir = await makeUpdateCheckDir();
await new UpdateCheckStore(updateCheckDir).write({
packageName: "@prisma/cli",
packageName: "prisma",
installedVersion: getCliVersion(),
checkedAt: new Date().toISOString(),
});
Expand Down
Loading
Loading