@lizard-build/cli 4.0.37 → 4.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -186,20 +186,17 @@ describe("account state", () => {
186
186
 
187
187
  test("the closing note leads with sandboxes, aligned in two columns", () => {
188
188
  const plain = (t: string) => t.replace(/\x1b\[[0-9;]*m/g, "");
189
- expect(plain(nextStepsNote(false)).split("\n")).toEqual([
190
- "lizard init Create a project and link this folder",
191
- "lizard sandbox create Start a sandbox in it",
192
- "lizard up Or deploy this folder",
193
- ]);
194
- expect(plain(nextStepsNote(true)).split("\n")).toEqual([
195
- "lizard sandbox create Start a sandbox in this project",
189
+ expect(plain(nextStepsNote()).split("\n")).toEqual([
190
+ "lizard sandbox create Start a sandbox",
196
191
  "lizard up Or deploy this folder",
197
192
  ]);
198
193
  });
199
194
 
200
- test("an unlinked folder gets lizard init before lizard up", () => {
201
- expect(nextSteps(false).map((s) => s.command)).toEqual(["lizard skills get core", "lizard init", "lizard sandbox create", "lizard up"]);
195
+ test("no lizard init needed: an unlinked folder uses the default project", () => {
196
+ expect(nextSteps(false).map((s) => s.command)).toEqual(["lizard skills get core", "lizard sandbox create", "lizard up"]);
197
+ expect(nextSteps(false)[1].why).toContain("default project");
202
198
  expect(nextSteps(true).map((s) => s.command)).toEqual(["lizard skills get core", "lizard sandbox create", "lizard up"]);
199
+ expect(nextSteps(true)[1].why).toContain("linked project");
203
200
  });
204
201
  });
205
202
 
@@ -239,7 +236,7 @@ describe("lizard login --json", () => {
239
236
  expect(out).toMatchObject({ status: "complete", username: "ada", email: "ada@example.com", source: "browser" });
240
237
  expect(out.account.state).toBe("trial_available");
241
238
  expect(out.account.billingUrl).toBe("https://lizard.build/profile/account-billing");
242
- expect(out.next.map((s: { command: string }) => s.command)).toContain("lizard init");
239
+ expect(out.next.map((s: { command: string }) => s.command)).toContain("lizard sandbox create");
243
240
  expect(loadConfig().pendingAuth).toBeUndefined();
244
241
  expect(loadConfig().credentials?.username).toBe("ada");
245
242
  });
@@ -3,6 +3,7 @@ import { Command } from "commander";
3
3
  import { registerSandbox } from "../../src/commands/sandbox.js";
4
4
  import { api, APIError } from "../../src/lib/api.js";
5
5
  import { printJSON } from "../../src/lib/format.js";
6
+ import { getProjectLink, resolveProjectId } from "../../src/lib/config.js";
6
7
 
7
8
  vi.mock("../../src/lib/api.js", () => {
8
9
  class APIError extends Error {
@@ -13,8 +14,8 @@ vi.mock("../../src/lib/api.js", () => {
13
14
  vi.mock("open", () => ({ default: vi.fn().mockResolvedValue(undefined) }));
14
15
  vi.mock("../../src/lib/config.js", () => ({
15
16
  resolveProjectId: vi.fn().mockResolvedValue("project-test"),
17
+ getProjectLink: vi.fn(() => ({ projectId: "project-test" })),
16
18
  }));
17
- vi.mock("../../src/lib/config.js", () => ({ resolveProjectId: vi.fn().mockResolvedValue("project-test") }));
18
19
  vi.mock("../../src/lib/format.js", () => ({ isJSONMode: () => true, printJSON: vi.fn() }));
19
20
 
20
21
  function create(args: string[]) {
@@ -46,6 +47,41 @@ describe("sandbox create timeout", () => {
46
47
  });
47
48
 
48
49
 
50
+ describe("sandbox create without a project", () => {
51
+ beforeEach(() => {
52
+ vi.clearAllMocks();
53
+ vi.mocked(api.post).mockResolvedValue({ id: "sandbox-test" });
54
+ });
55
+
56
+ it("an unlinked folder sends no project and leaves the choice to the platform", async () => {
57
+ vi.mocked(getProjectLink).mockReturnValueOnce(null);
58
+ await create([]);
59
+ expect(resolveProjectId).not.toHaveBeenCalled();
60
+ expect(api.post).toHaveBeenCalledWith("/api/sandboxes", expect.objectContaining({ projectId: undefined }));
61
+ });
62
+
63
+ it("a linked folder or --project still sends its project", async () => {
64
+ await create([]);
65
+ expect(api.post).toHaveBeenCalledWith("/api/sandboxes", expect.objectContaining({ projectId: "project-test" }));
66
+ vi.mocked(getProjectLink).mockReturnValueOnce(null);
67
+ await create(["--project", "web"]);
68
+ expect(resolveProjectId).toHaveBeenLastCalledWith("web");
69
+ });
70
+
71
+ it("an unlinked --volume still resolves a project, since a volume name lives in one", async () => {
72
+ vi.mocked(getProjectLink).mockReturnValueOnce(null);
73
+ await create(["--volume", "data"]);
74
+ expect(resolveProjectId).toHaveBeenCalledWith(undefined);
75
+ expect(api.post).toHaveBeenCalledWith("/api/sandboxes", expect.objectContaining({ projectId: "project-test", volumeName: "data" }));
76
+ });
77
+
78
+ it("a platform that still wants a project gets the old message", async () => {
79
+ vi.mocked(getProjectLink).mockReturnValueOnce(null);
80
+ vi.mocked(api.post).mockRejectedValueOnce(new APIError(400, "A project is required", "PROJECT_REQUIRED"));
81
+ await expect(create([])).rejects.toThrow("No project linked. Run `lizard init` or pass --project <id>.");
82
+ });
83
+ });
84
+
49
85
  describe("unsupported sandbox commands", () => {
50
86
  it.each(["fork", "snapshot-fork", "logs"])(
51
87
  "rejects %s without making an API request", async (name) => {