mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 14:57:28 +00:00
feat(cli): address team vaults as <project> <env> (#109)
`teams push|pull|grant` took a single `<vault>` name, so a team holding more than one project had to encode both halves by hand and hope everyone spelled it the same way. They now take `<project> <env>` and join them into the `project/env` vault name. The split lives entirely in the CLI — vaultName()/splitVaultName() are the only things that know about it, and the server still stores one opaque vault name — so there's no migration. Both halves reject a "/" so the join stays unambiguous and the split is a true inverse. `teams vaults` now breaks the name back into project/env columns, falling back to the raw name for vaults created before the convention. Those legacy vaults are no longer addressable (their names don't contain a slash), so resolveVaultId() lists what the team actually has instead of just saying "not found" — better than silently retargeting a push, which in a secrets tool would write to the wrong vault. Note push/pull carry two different "env"s: the `<env>` positional is the environment half of the address, `--env` is the local .env path. Verified commander keeps them separate. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7c9796ae51
commit
ca182bc057
4 changed files with 130 additions and 23 deletions
50
packages/cli/src/teams.test.ts
Normal file
50
packages/cli/src/teams.test.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { splitVaultName, vaultName } from "./teams.js";
|
||||
|
||||
// A vault is addressed as <project> <env> on the command line and stored as a
|
||||
// single `project/env` name server-side. The join is the only thing keeping
|
||||
// those two halves apart, so it has to reject anything that would make the
|
||||
// name ambiguous — a wrong split would point a push at the wrong vault.
|
||||
|
||||
describe("vaultName", () => {
|
||||
it("joins project and env with a slash", () => {
|
||||
expect(vaultName("web", "prod")).toBe("web/prod");
|
||||
});
|
||||
|
||||
it("keeps distinct envs of one project apart", () => {
|
||||
expect(vaultName("web", "staging")).not.toBe(vaultName("web", "prod"));
|
||||
});
|
||||
|
||||
it("keeps distinct projects in one env apart", () => {
|
||||
expect(vaultName("api", "prod")).not.toBe(vaultName("web", "prod"));
|
||||
});
|
||||
|
||||
it("rejects a slash in either half", () => {
|
||||
expect(() => vaultName("web/api", "prod")).toThrow(/cannot contain/);
|
||||
expect(() => vaultName("web", "prod/eu")).toThrow(/cannot contain/);
|
||||
});
|
||||
|
||||
it("rejects empty or blank halves", () => {
|
||||
expect(() => vaultName("", "prod")).toThrow(/Missing project/);
|
||||
expect(() => vaultName("web", "")).toThrow(/Missing env/);
|
||||
expect(() => vaultName(" ", "prod")).toThrow(/Missing project/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("splitVaultName", () => {
|
||||
it("round-trips a name built by vaultName", () => {
|
||||
expect(splitVaultName(vaultName("web", "prod"))).toEqual({ project: "web", env: "prod" });
|
||||
});
|
||||
|
||||
it("returns null for legacy single-word names", () => {
|
||||
// Vaults created before the split are still listable; they just don't
|
||||
// decompose, so `teams vaults` shows the raw name instead of guessing.
|
||||
expect(splitVaultName("prod")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null rather than guessing at an ambiguous name", () => {
|
||||
expect(splitVaultName("a/b/c")).toBeNull();
|
||||
expect(splitVaultName("/prod")).toBeNull();
|
||||
expect(splitVaultName("web/")).toBeNull();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue