@base44-preview/cli 0.0.1-pr.13.d022278 → 0.0.1-pr.14.723fe70

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.
package/dist/cli/index.js CHANGED
@@ -1,16 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import chalk from "chalk";
4
- import { intro, log, spinner } from "@clack/prompts";
4
+ import { cancel, intro, isCancel, log, spinner, text } from "@clack/prompts";
5
5
  import pWaitFor from "p-wait-for";
6
6
  import { z } from "zod";
7
- import { dirname, join } from "node:path";
7
+ import { dirname, join, resolve } from "node:path";
8
8
  import { homedir } from "node:os";
9
+ import { config } from "dotenv";
9
10
  import ky from "ky";
10
11
  import { access, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
11
12
  import { parse, printParseErrorCode } from "jsonc-parser";
12
13
  import { globby } from "globby";
14
+ import { fileURLToPath } from "node:url";
15
+ import ejs from "ejs";
13
16
 
17
+ //#region rolldown:runtime
18
+ var __defProp = Object.defineProperty;
19
+ var __exportAll = (all, symbols) => {
20
+ let target = {};
21
+ for (var name in all) {
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true
25
+ });
26
+ }
27
+ if (symbols) {
28
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
29
+ }
30
+ return target;
31
+ };
32
+
33
+ //#endregion
14
34
  //#region src/core/auth/schema.ts
15
35
  const AuthDataSchema = z.object({
16
36
  accessToken: z.string().min(1, "Token cannot be empty"),
@@ -73,9 +93,11 @@ var AuthValidationError = class extends Error {
73
93
  };
74
94
 
75
95
  //#endregion
76
- //#region src/core/consts.ts
96
+ //#region src/core/config.ts
77
97
  const PROJECT_SUBDIR = "base44";
78
98
  const FUNCTION_CONFIG_FILE = "function.jsonc";
99
+ const AUTH_CLIENT_ID = "base44_cli";
100
+ const DEFAULT_API_URL = "https://app.base44.com";
79
101
  function getBase44Dir() {
80
102
  return join(homedir(), ".base44");
81
103
  }
@@ -90,15 +112,34 @@ function getProjectConfigPatterns() {
90
112
  "config.json"
91
113
  ];
92
114
  }
93
- const AUTH_CLIENT_ID = "base44_cli";
94
- const DEFAULT_API_URL = "https://app.base44.com";
115
+ /**
116
+ * Load .env.local from the project root if it exists.
117
+ * Values won't override existing process.env variables.
118
+ * Safe to call multiple times - only loads once.
119
+ */
120
+ async function loadProjectEnv(projectRoot) {
121
+ const { findProjectRoot: findProjectRoot$1 } = await Promise.resolve().then(() => config_exports);
122
+ const found = projectRoot ? { root: projectRoot } : await findProjectRoot$1();
123
+ if (!found) return;
124
+ config({
125
+ path: join(found.root, ".env.local"),
126
+ override: false
127
+ });
128
+ }
129
+ /**
130
+ * Get the Base44 API URL.
131
+ * Priority: process.env.BASE44_API_URL > .env.local > default
132
+ */
95
133
  function getBase44ApiUrl() {
96
134
  return process.env.BASE44_API_URL || DEFAULT_API_URL;
97
135
  }
98
- function getAppId() {
99
- const appId = process.env.BASE44_CLIENT_ID;
100
- if (!appId) throw new Error("BASE44_CLIENT_ID environment variable is not set");
101
- return appId;
136
+ /**
137
+ * Get the Base44 Client ID (app ID).
138
+ * Priority: process.env.BASE44_CLIENT_ID > .env.local
139
+ * Returns undefined if not set.
140
+ */
141
+ function getBase44ClientId() {
142
+ return process.env.BASE44_CLIENT_ID;
102
143
  }
103
144
 
104
145
  //#endregion
@@ -181,8 +222,18 @@ async function getUserInfo(accessToken) {
181
222
 
182
223
  //#endregion
183
224
  //#region src/core/utils/fs.ts
184
- function pathExists(path) {
185
- return access(path).then(() => true).catch(() => false);
225
+ async function pathExists(path) {
226
+ try {
227
+ await access(path);
228
+ return true;
229
+ } catch {
230
+ return false;
231
+ }
232
+ }
233
+ async function writeFile$1(filePath, content) {
234
+ const dir = dirname(filePath);
235
+ if (!await pathExists(dir)) await mkdir(dir, { recursive: true });
236
+ await writeFile(filePath, content, "utf-8");
186
237
  }
187
238
  async function readJsonFile(filePath) {
188
239
  if (!await pathExists(filePath)) throw new Error(`File not found: ${filePath}`);
@@ -201,21 +252,13 @@ async function readJsonFile(filePath) {
201
252
  }
202
253
  }
203
254
  async function writeJsonFile(filePath, data) {
204
- try {
205
- const dir = dirname(filePath);
206
- if (!await pathExists(dir)) await mkdir(dir, { recursive: true });
207
- await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
208
- } catch (error) {
209
- throw new Error(`Failed to write file ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
210
- }
255
+ const dir = dirname(filePath);
256
+ if (!await pathExists(dir)) await mkdir(dir, { recursive: true });
257
+ await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
211
258
  }
212
259
  async function deleteFile(filePath) {
213
260
  if (!await pathExists(filePath)) return;
214
- try {
215
- await unlink(filePath);
216
- } catch (error) {
217
- throw new Error(`Failed to delete file ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
218
- }
261
+ await unlink(filePath);
219
262
  }
220
263
 
221
264
  //#endregion
@@ -290,11 +333,13 @@ const base44Color = chalk.bgHex("#E86B3C");
290
333
  /**
291
334
  * Wraps a command function with the Base44 intro banner.
292
335
  * All CLI commands should use this utility to ensure consistent branding.
336
+ * Also loads .env.local from the project root if available.
293
337
  *
294
338
  * @param commandFn - The async function to execute as the command
295
339
  */
296
340
  async function runCommand(commandFn) {
297
341
  intro(base44Color(" Base 44 "));
342
+ await loadProjectEnv();
298
343
  try {
299
344
  await commandFn();
300
345
  } catch (e) {
@@ -328,6 +373,42 @@ async function runTask(startMessage, operation, options) {
328
373
  }
329
374
  }
330
375
 
376
+ //#endregion
377
+ //#region src/cli/utils/prompts.ts
378
+ /**
379
+ * Handles prompt cancellation by exiting gracefully.
380
+ */
381
+ function handleCancel(value) {
382
+ if (isCancel(value)) {
383
+ cancel("Operation cancelled.");
384
+ process.exit(0);
385
+ }
386
+ }
387
+ /**
388
+ * Wrapper around @clack/prompts text() that handles cancellation automatically.
389
+ * Returns the string value directly, exits process if cancelled.
390
+ */
391
+ async function textPrompt(options) {
392
+ const value = await text(options);
393
+ handleCancel(value);
394
+ return value;
395
+ }
396
+
397
+ //#endregion
398
+ //#region src/cli/utils/banner.ts
399
+ const orange = chalk.hex("#E86B3C");
400
+ const BANNER = `
401
+ ${orange("██████╗ █████╗ ███████╗███████╗ ██╗ ██╗██╗ ██╗")}
402
+ ${orange("██╔══██╗██╔══██╗██╔════╝██╔════╝ ██║ ██║██║ ██║")}
403
+ ${orange("██████╔╝███████║███████╗█████╗ ███████║███████║")}
404
+ ${orange("██╔══██╗██╔══██║╚════██║██╔══╝ ╚════██║╚════██║")}
405
+ ${orange("██████╔╝██║ ██║███████║███████╗ ██║ ██║")}
406
+ ${orange("╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝")}
407
+ `;
408
+ function printBanner() {
409
+ console.log(BANNER);
410
+ }
411
+
331
412
  //#endregion
332
413
  //#region src/cli/commands/auth/login.ts
333
414
  async function generateAndDisplayDeviceCode() {
@@ -496,26 +577,18 @@ const base44Client = ky.create({
496
577
  afterResponse: [handleUnauthorized]
497
578
  }
498
579
  });
499
- let _appClient = null;
500
580
  /**
501
581
  * Returns an HTTP client scoped to the current app.
502
- * Lazily initialized on first call (app ID must be set via BASE44_CLIENT_ID env var).
503
582
  */
504
583
  function getAppClient() {
505
- if (!_appClient) _appClient = base44Client.extend({ prefixUrl: new URL(`/api/apps/${getAppId()}/`, getBase44ApiUrl()).href });
506
- return _appClient;
584
+ return base44Client.extend({ prefixUrl: new URL(`/api/apps/${getBase44ClientId()}/`, getBase44ApiUrl()).href });
507
585
  }
508
586
 
509
587
  //#endregion
510
588
  //#region src/core/resources/entity/api.ts
511
- const appClient = getAppClient();
512
589
  async function pushEntities(entities) {
513
- const schemaSyncPayload = entities.reduce((acc, current) => {
514
- return {
515
- ...acc,
516
- [current.name]: current
517
- };
518
- }, {});
590
+ const appClient = getAppClient();
591
+ const schemaSyncPayload = Object.fromEntries(entities.map((entity) => [entity.name, entity]));
519
592
  const response = await appClient.put("entities-schemas/sync-all", {
520
593
  json: { entityNameToSchema: schemaSyncPayload },
521
594
  throwHttpErrors: false
@@ -591,13 +664,15 @@ async function readAllFunctions(functionsDir) {
591
664
 
592
665
  //#endregion
593
666
  //#region src/core/resources/function/resource.ts
594
- const functionResource = {
595
- readAll: readAllFunctions,
596
- push: async () => {}
597
- };
667
+ const functionResource = { readAll: readAllFunctions };
598
668
 
599
669
  //#endregion
600
- //#region src/core/config/project.ts
670
+ //#region src/core/project/config.ts
671
+ var config_exports = /* @__PURE__ */ __exportAll({
672
+ ProjectConfigSchema: () => ProjectConfigSchema,
673
+ findProjectRoot: () => findProjectRoot,
674
+ readProjectConfig: () => readProjectConfig
675
+ });
601
676
  const ProjectConfigSchema = z.looseObject({
602
677
  name: z.string().min(1, "Project name cannot be empty"),
603
678
  entitiesDir: z.string().default("./entities"),
@@ -669,7 +744,7 @@ const showProjectCommand = new Command("show-project").description("Display proj
669
744
  });
670
745
 
671
746
  //#endregion
672
- //#region src/core/config/app.ts
747
+ //#region src/core/project/schema.ts
673
748
  const SiteConfigSchema = z.object({
674
749
  buildCommand: z.string().optional(),
675
750
  serveCommand: z.string().optional(),
@@ -682,6 +757,60 @@ const AppConfigSchema = z.object({
682
757
  site: SiteConfigSchema.optional(),
683
758
  domains: z.array(z.string()).optional()
684
759
  });
760
+ const CreateProjectResponseSchema = z.looseObject({ id: z.string() });
761
+
762
+ //#endregion
763
+ //#region src/core/project/api.ts
764
+ async function createProject(projectName, description) {
765
+ const response = await base44Client.post("api/apps", { json: {
766
+ name: projectName,
767
+ user_description: description ?? `Backend for '${projectName}'`,
768
+ app_type: "baas"
769
+ } });
770
+ return { projectId: CreateProjectResponseSchema.parse(await response.json()).id };
771
+ }
772
+
773
+ //#endregion
774
+ //#region src/core/project/templates/index.ts
775
+ const TEMPLATES_DIR = join(dirname(fileURLToPath(import.meta.url)), "templates");
776
+ const CONFIG_TEMPLATE_PATH = join(TEMPLATES_DIR, "config.jsonc.ejs");
777
+ const ENV_TEMPLATE_PATH = join(TEMPLATES_DIR, "env.local.ejs");
778
+ async function renderConfigTemplate(data) {
779
+ return ejs.renderFile(CONFIG_TEMPLATE_PATH, data);
780
+ }
781
+ async function renderEnvTemplate(data) {
782
+ return ejs.renderFile(ENV_TEMPLATE_PATH, data);
783
+ }
784
+
785
+ //#endregion
786
+ //#region src/core/project/init.ts
787
+ /**
788
+ * Initialize a new Base44 project.
789
+ * Creates the base44 directory, config.jsonc, and .env.local files.
790
+ */
791
+ async function initProject(options) {
792
+ const { name, description, path: basePath } = options;
793
+ const projectDir = join(basePath, PROJECT_SUBDIR);
794
+ const configPath = join(projectDir, "config.jsonc");
795
+ const envPath = join(projectDir, ".env.local");
796
+ const existingConfigs = await globby(getProjectConfigPatterns(), {
797
+ cwd: basePath,
798
+ absolute: true
799
+ });
800
+ if (existingConfigs.length > 0) throw new Error(`A Base44 project already exists at ${existingConfigs[0]}. Please choose a different location.`);
801
+ const { projectId } = await createProject(name, description);
802
+ await writeFile$1(configPath, await renderConfigTemplate({
803
+ name,
804
+ description
805
+ }));
806
+ await writeFile$1(envPath, await renderEnvTemplate({ projectId }));
807
+ return {
808
+ projectId,
809
+ projectDir,
810
+ configPath,
811
+ envPath
812
+ };
813
+ }
685
814
 
686
815
  //#endregion
687
816
  //#region src/cli/commands/entities/push.ts
@@ -707,6 +836,50 @@ const entitiesPushCommand = new Command("entities").description("Manage project
707
836
  await runCommand(pushEntitiesAction);
708
837
  }));
709
838
 
839
+ //#endregion
840
+ //#region src/cli/commands/project/init.ts
841
+ async function init() {
842
+ printBanner();
843
+ await loadProjectEnv();
844
+ const name = await textPrompt({
845
+ message: "What is the name of your project?",
846
+ placeholder: "my-app-backend",
847
+ validate: (value) => {
848
+ if (!value || value.trim().length === 0) return "Project name is required";
849
+ }
850
+ });
851
+ const description = await textPrompt({
852
+ message: "Project description (optional)",
853
+ placeholder: "A brief description of your project"
854
+ });
855
+ const defaultPath = "./";
856
+ const resolvedPath = resolve(await textPrompt({
857
+ message: "Where should we create the base44 folder?",
858
+ placeholder: defaultPath,
859
+ initialValue: defaultPath
860
+ }) || defaultPath);
861
+ await runTask("Creating project...", async () => {
862
+ return await initProject({
863
+ name: name.trim(),
864
+ description: description ? description.trim() : void 0,
865
+ path: resolvedPath
866
+ });
867
+ }, {
868
+ successMessage: "Project created successfully",
869
+ errorMessage: "Failed to create project"
870
+ });
871
+ log.success(`Project ${chalk.bold(name)} has been initialized!`);
872
+ }
873
+ const initCommand = new Command("init").alias("create").description("Initialize a new Base44 project").action(async () => {
874
+ try {
875
+ await init();
876
+ } catch (e) {
877
+ if (e instanceof Error) log.error(e.stack ?? e.message);
878
+ else log.error(String(e));
879
+ process.exit(1);
880
+ }
881
+ });
882
+
710
883
  //#endregion
711
884
  //#region package.json
712
885
  var version = "0.0.1";
@@ -718,9 +891,10 @@ program.name("base44").description("Base44 CLI - Unified interface for managing
718
891
  program.addCommand(loginCommand);
719
892
  program.addCommand(whoamiCommand);
720
893
  program.addCommand(logoutCommand);
894
+ program.addCommand(initCommand);
721
895
  program.addCommand(showProjectCommand);
722
896
  program.addCommand(entitiesPushCommand);
723
897
  program.parse();
724
898
 
725
899
  //#endregion
726
- export { };
900
+ export { findProjectRoot as n, readProjectConfig as r, ProjectConfigSchema as t };
@@ -0,0 +1,17 @@
1
+ // Base44 Project Configuration
2
+ // JSONC enables inline documentation and discoverability directly in config files.
3
+ // Commented-out properties show available options you can enable.
4
+
5
+ {
6
+ "name": "<%= name %>"<% if (description) { %>,
7
+ "description": "<%= description %>"<% } %>
8
+
9
+ // Site/hosting configuration
10
+ // Docs: https://docs.base44.com/configuration/hosting
11
+ // "site": {
12
+ // "buildCommand": "npm run build",
13
+ // "serveCommand": "npm run dev",
14
+ // "outputDirectory": "./dist",
15
+ // "installCommand": "npm ci"
16
+ // }
17
+ }
@@ -0,0 +1,6 @@
1
+ # Base44 Project Environment Variables
2
+ # This file contains environment-specific configuration for your Base44 project.
3
+ # Do not commit this file to version control if it contains sensitive data.
4
+
5
+ # Your Base44 Application ID
6
+ BASE44_CLIENT_ID=<%= projectId %>
@@ -0,0 +1,30 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import ejs from "ejs";
4
+
5
+ // After bundling, import.meta.url points to dist/cli/index.js
6
+ // Templates are copied to dist/cli/templates/
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const TEMPLATES_DIR = join(__dirname, "templates");
9
+
10
+ const CONFIG_TEMPLATE_PATH = join(TEMPLATES_DIR, "config.jsonc.ejs");
11
+ const ENV_TEMPLATE_PATH = join(TEMPLATES_DIR, "env.local.ejs");
12
+
13
+ interface ConfigTemplateData {
14
+ name: string;
15
+ description?: string;
16
+ }
17
+
18
+ interface EnvTemplateData {
19
+ projectId: string;
20
+ }
21
+
22
+ export async function renderConfigTemplate(
23
+ data: ConfigTemplateData
24
+ ): Promise<string> {
25
+ return ejs.renderFile(CONFIG_TEMPLATE_PATH, data);
26
+ }
27
+
28
+ export async function renderEnvTemplate(data: EnvTemplateData): Promise<string> {
29
+ return ejs.renderFile(ENV_TEMPLATE_PATH, data);
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/cli",
3
- "version": "0.0.1-pr.13.d022278",
3
+ "version": "0.0.1-pr.14.723fe70",
4
4
  "description": "Base44 CLI - Unified interface for managing Base44 applications",
5
5
  "type": "module",
6
6
  "main": "./dist/cli/index.js",
@@ -36,6 +36,8 @@
36
36
  "@clack/prompts": "^0.11.0",
37
37
  "chalk": "^5.6.2",
38
38
  "commander": "^12.1.0",
39
+ "dotenv": "^17.2.3",
40
+ "ejs": "^3.1.10",
39
41
  "globby": "^16.1.0",
40
42
  "jsonc-parser": "^3.3.1",
41
43
  "ky": "^1.14.2",
@@ -44,6 +46,7 @@
44
46
  },
45
47
  "devDependencies": {
46
48
  "@stylistic/eslint-plugin": "^5.6.1",
49
+ "@types/ejs": "^3.1.5",
47
50
  "@types/node": "^22.10.5",
48
51
  "@typescript-eslint/eslint-plugin": "^8.51.0",
49
52
  "@typescript-eslint/parser": "^8.51.0",