Skip to content
Open
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 @@ -6,6 +6,10 @@

- App visibility: `base44 visibility <public|private|workspace>` sets it on the server directly (accepts `--app-id` to target any app). Also configurable via `"visibility"` in `config.jsonc`, which `base44 deploy` applies. New projects scaffold `"visibility": "public"`.

### Changed

- The `backend-and-client` template now scaffolds the same client convention editor-created apps use: `@base44/vite-plugin` + `src/lib/app-params.js`, with the SDK client on same-origin `/api` (`serverUrl: ''`). Under `base44 dev` the plugin proxies `/api` to the local dev backend, so scaffolded apps get local entities and functions; the app id resolves from env / `base44/.app.jsonc` instead of being baked into source.

### Fixed

- `base44 link` now lists editor-created apps; previously only apps created by the CLI could be linked.
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/templates/backend-and-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"preview": "vite preview"
},
"dependencies": {
"@base44/sdk": "^0.8.3",
"@base44/sdk": "^0.8.40",
"@base44/vite-plugin": "^1.0.30",
"lucide-react": "^0.475.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/templates/backend-and-client/src/api/base44Client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createClient } from '@base44/sdk';
import { appParams } from '@/lib/app-params';

const { appId, token, functionsVersion, appBaseUrl } = appParams;

//Create a client with authentication required
export const base44 = createClient({
appId,
token,
functionsVersion,
serverUrl: '',
requiresAuth: false,
appBaseUrl
});

This file was deleted.

54 changes: 54 additions & 0 deletions packages/cli/templates/backend-and-client/src/lib/app-params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const isNode = typeof window === 'undefined';
const windowObj = isNode ? { localStorage: new Map() } : window;
const storage = windowObj.localStorage;

const toSnakeCase = (str) => {
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
}

const getAppParamValue = (paramName, { defaultValue = undefined, removeFromUrl = false } = {}) => {
if (isNode) {
return defaultValue;
}
const storageKey = `base44_${toSnakeCase(paramName)}`;
const urlParams = new URLSearchParams(window.location.search);
const searchParam = urlParams.get(paramName);
if (removeFromUrl) {
urlParams.delete(paramName);
const newUrl = `${window.location.pathname}${urlParams.toString() ? `?${urlParams.toString()}` : ""
}${window.location.hash}`;
window.history.replaceState({}, document.title, newUrl);
}
if (searchParam) {
storage.setItem(storageKey, searchParam);
return searchParam;
}
if (defaultValue) {
storage.setItem(storageKey, defaultValue);
return defaultValue;
}
const storedValue = storage.getItem(storageKey);
if (storedValue) {
return storedValue;
}
return null;
}

const getAppParams = () => {
if (getAppParamValue("clear_access_token") === 'true') {
storage.removeItem('base44_access_token');
storage.removeItem('token');
}
return {
appId: getAppParamValue("app_id", { defaultValue: import.meta.env.VITE_BASE44_APP_ID }),
token: getAppParamValue("access_token", { removeFromUrl: true }),
fromUrl: getAppParamValue("from_url", { defaultValue: window.location.href }),
functionsVersion: getAppParamValue("functions_version", { defaultValue: import.meta.env.VITE_BASE44_FUNCTIONS_VERSION }),
appBaseUrl: getAppParamValue("app_base_url", { defaultValue: import.meta.env.VITE_BASE44_APP_BASE_URL }),
}
}


export const appParams = {
...getAppParams()
}
11 changes: 3 additions & 8 deletions packages/cli/templates/backend-and-client/vite.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { defineConfig } from 'vite';
import base44 from '@base44/vite-plugin';
import react from '@vitejs/plugin-react';
import path from 'path';
import { defineConfig } from 'vite';

export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
plugins: [base44(), react()],
});
46 changes: 46 additions & 0 deletions packages/cli/tests/cli/create.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,52 @@ describe("create command", () => {
expect(config).toContain('"visibility": "public"');
});

it("scaffolds backend-and-client with the editor-app client convention", async () => {
await t.givenLoggedIn({ email: "test@example.com", name: "Test User" });
t.api.mockCreateApp({ id: "convention-app-id", name: "Convention App" });

const projectPath = join(t.getTempDir(), "convention-app");

const result = await t.run(
"create",
"Convention App",
"--path",
projectPath,
"--template",
"backend-and-client",
"--no-skills",
);

t.expectResult(result).toSucceed();

const client = await readFile(
join(projectPath, "src", "api", "base44Client.js"),
"utf-8",
);
expect(client).toContain("serverUrl: ''");
expect(client).toContain("@/lib/app-params");
expect(client).not.toContain("convention-app-id");

const appParams = await readFile(
join(projectPath, "src", "lib", "app-params.js"),
"utf-8",
);
expect(appParams).toContain("VITE_BASE44_APP_ID");
expect(appParams).toContain("VITE_BASE44_APP_BASE_URL");

const viteConfig = await readFile(
join(projectPath, "vite.config.js"),
"utf-8",
);
expect(viteConfig).toContain("@base44/vite-plugin");

const appConfig = await readFile(
join(projectPath, "base44", ".app.jsonc"),
"utf-8",
);
expect(appConfig).toContain("convention-app-id");
});

it("infers path from name when --path is not provided", async () => {
await t.givenLoggedIn({ email: "test@example.com", name: "Test User" });
t.api.mockCreateApp({ id: "inferred-path-id", name: "My App" });
Expand Down
Loading