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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
48 changes: 17 additions & 31 deletions packages/cli/src/cli/commands/project/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
49 changes: 21 additions & 28 deletions packages/cli/tests/cli/link.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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");

Expand All @@ -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");

Expand All @@ -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");

Expand Down
Loading