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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

- Verify new profiles from the profile directory instead of the current
directory. Vercel CLI 59 resolves the team from a linked project in the
working directory, so adding an account outside that team failed with
`Not authorized`.
- `install.sh` installs into `~/.vcx` with a fresh manifest instead of
`bun install --global`. Bun 1.4.0 fails with `refusing to install dependency
with unsafe name` on some machines when the global manifest carries entries
Expand Down
4 changes: 4 additions & 0 deletions dist/cli.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/cli.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions dist/vercel.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export interface VercelRunOptions {
globalConfig?: string;
capture?: boolean;
env?: NodeJS.ProcessEnv;
/** Working directory for Vercel. Defaults to the current directory. */
cwd?: string;
}
export interface VercelRunResult {
code: number;
Expand Down
2 changes: 1 addition & 1 deletion dist/vercel.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dist/vercel.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/vercel.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,13 @@ async function forwardToVercel(
}

async function verifyProfile(profileDir: string): Promise<string> {
// Vercel resolves the team from a linked project in the working directory,
// and an account outside that team gets "Not authorized". Verify from the
// profile directory instead, which never holds a project link.
const result = await runVercel(["whoami"], {
globalConfig: profileDir,
capture: true,
cwd: profileDir,
});
if (result.missingBinary) throw missingVercelError();
if (result.code !== 0) {
Expand Down
3 changes: 3 additions & 0 deletions src/vercel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export interface VercelRunOptions {
globalConfig?: string;
capture?: boolean;
env?: NodeJS.ProcessEnv;
/** Working directory for Vercel. Defaults to the current directory. */
cwd?: string;
}

export interface VercelRunResult {
Expand Down Expand Up @@ -33,6 +35,7 @@ export async function runVercel(
const capture = options.capture === true;
const child = spawn(binary, vercelArgs, {
env: childEnv,
...(options.cwd ? { cwd: options.cwd } : {}),
stdio: capture ? ["inherit", "pipe", "pipe"] : "inherit",
windowsHide: false,
});
Expand Down
36 changes: 36 additions & 0 deletions test/cli.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ if (logPath) {
commandArgs,
globalConfig: globalConfig ?? null,
envToken: process.env.VERCEL_TOKEN ?? null,
cwd: process.cwd(),
}) + "\\n");
}

Expand Down Expand Up @@ -522,3 +523,38 @@ test("links Vercel's global config directory to the active profile", async (t) =
assert.equal(back.status, 0, back.stderr);
assert.equal(await linkTarget(), await realpath(profileDir("work")));
});

test("verifies a new profile from its own directory, not the project directory", async (t) => {
const sandbox = await createSandbox(t);
const projectDir = join(sandbox.root, "project");
await mkdir(join(projectDir, ".vercel"), { recursive: true });
await writeFile(
join(projectDir, ".vercel", "project.json"),
JSON.stringify({ orgId: "team_other", projectId: "prj_1" }),
);

const added = spawnSync(
process.execPath,
[CLI_PATH, "profile", "add", "work", "--token", "tok-work"],
{ cwd: projectDir, encoding: "utf8", env: childEnv(sandbox) },
);
assert.equal(added.status, 0, added.stderr);

const whoami = (await readLog(sandbox)).find(
(entry) => entry.commandArgs[0] === "whoami",
);
assert.ok(whoami, "verification ran whoami");
// The staged directory is renamed after verification, so compare prefixes
// instead of resolving it. Either form of the path is fine (macOS reports
// /private/var for /var).
const profilesDir = join(sandbox.configDir, "profiles");
const inside = (base) => whoami.cwd.startsWith(base);
assert.ok(
!inside(projectDir) && !inside(await realpath(projectDir)),
`verify ran in the project directory: ${whoami.cwd}`,
);
assert.ok(
inside(profilesDir) || inside(await realpath(profilesDir)),
`verify cwd ${whoami.cwd} should be inside the profiles directory`,
);
});