diff --git a/CHANGELOG.md b/CHANGELOG.md index 31fb8eec..f523079c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ ### Fixed +- `base44 link --app-id` links directly instead of first listing your apps to confirm the ID. The listing only covered the 50 most recently updated apps, and a workspace API key (`BASE44_API_KEY`) is refused on it, so `link --app-id` failed in CI. A wrong ID now fails on the first request that addresses the app. - `base44 deploy --git-hash` (and `site deploy --publish`) no longer fails after a successful publish when the app's URL can't be looked up, as happened with a workspace API key (`BASE44_API_KEY`). It reports the deploy and leaves out the URL. - An app id passed with `--app-id` or `BASE44_APP_ID` no longer discards the project the command runs in. Commands that need a project — `site dev`, `dev`, `connectors pull`/`push` — refused as if there were none. A checkout with `base44/config.jsonc` but no `.app.jsonc`, such as a hosted sandbox, can now run them. - The `backend-and-client` template names its entity file `Task.jsonc`, matching the entity's `name`; the Base44 platform derives the entity from the filename, so `task.jsonc` read as a second entity. diff --git a/packages/cli/src/cli/commands/project/link.ts b/packages/cli/src/cli/commands/project/link.ts index cb31e920..3837be36 100644 --- a/packages/cli/src/cli/commands/project/link.ts +++ b/packages/cli/src/cli/commands/project/link.ts @@ -179,38 +179,24 @@ async function link( : await promptForLinkAction(); if (action === "choose") { - const projects = await runTask( - "Fetching projects...", - async () => listProjects(), - { - successMessage: "Projects fetched", - errorMessage: "Failed to fetch projects", - }, - ); - - if (!projects.length) { - return { outroMessage: "No projects available for linking" }; - } - - let linkedAppId: string; - - if (appId) { - const project = projects.find((p) => p.id === appId); - if (!project) { - throw new InvalidInputError(`App with ID "${appId}" not found.`, { - hints: [ - { message: "Check the app ID is correct" }, - { - message: - "Use 'base44 link' without --app-id to see available projects", - }, - ], - }); + // An explicit id needs no listing: the first request that addresses the app + // checks it, and a workspace API key can't list apps at all. + let linkedAppId = appId; + if (!linkedAppId) { + const projects = await runTask( + "Fetching projects...", + async () => listProjects(), + { + successMessage: "Projects fetched", + errorMessage: "Failed to fetch projects", + }, + ); + + if (!projects.length) { + return { outroMessage: "No projects available for linking" }; } - linkedAppId = appId; - } else { - const selectedProject = await promptForExistingProject(projects); - linkedAppId = selectedProject.id; + + linkedAppId = (await promptForExistingProject(projects)).id; } await runTask( diff --git a/packages/cli/tests/cli/link.spec.ts b/packages/cli/tests/cli/link.spec.ts index ca6be2e2..a3825f69 100644 --- a/packages/cli/tests/cli/link.spec.ts +++ b/packages/cli/tests/cli/link.spec.ts @@ -44,13 +44,6 @@ describe("link command", () => { it("links an existing app with --app-id", async () => { await t.givenLoggedInWithProject(fixture("no-app-config")); - t.api.mockListProjects([ - { - id: "existing-app-id", - name: "Existing App", - is_managed_source_code: false, - }, - ]); const result = await t.run("link", "--app-id", "existing-app-id"); @@ -65,15 +58,29 @@ describe("link command", () => { expect(appConfig).toContain("existing-app-id"); }); + it("links --app-id without listing apps, which a workspace API key can't do", async () => { + await t.givenProject(fixture("no-app-config")); + t.givenEnv({ + BASE44_API_KEY: + "b44k_dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + }); + t.api.mockError("get", "/api/apps", { + status: 401, + body: { detail: "Unauthorized. ['api_key'] -> Object not found" }, + }); + + const result = await t.run("link", "--app-id", "existing-app-id"); + + t.expectResult(result).toSucceed(); + const appConfig = await readFile( + join(t.getTempDir(), "project", "base44", ".app.jsonc"), + "utf-8", + ); + expect(appConfig).toContain("existing-app-id"); + }); + it("links an editor-created app (managed source code)", async () => { await t.givenLoggedInWithProject(fixture("no-app-config")); - t.api.mockListProjects([ - { - id: "editor-app-id", - name: "Editor App", - is_managed_source_code: true, - }, - ]); const result = await t.run("link", "--app-id", "editor-app-id"); @@ -84,13 +91,6 @@ describe("link command", () => { it("links an existing app with legacy --project-id", async () => { await t.givenLoggedInWithProject(fixture("no-app-config")); - t.api.mockListProjects([ - { - id: "legacy-app-id", - name: "Legacy App", - is_managed_source_code: false, - }, - ]); const result = await t.run("link", "--project-id", "legacy-app-id"); @@ -101,13 +101,6 @@ describe("link command", () => { it("links an existing app with legacy --projectId", async () => { await t.givenLoggedInWithProject(fixture("no-app-config")); - t.api.mockListProjects([ - { - id: "camel-legacy-app-id", - name: "Camel Legacy App", - is_managed_source_code: false, - }, - ]); const result = await t.run("link", "--projectId", "camel-legacy-app-id");