@base44-preview/cli 0.0.1-pr.13.e830fa0 → 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() {
@@ -500,19 +581,14 @@ const base44Client = ky.create({
500
581
  * Returns an HTTP client scoped to the current app.
501
582
  */
502
583
  function getAppClient() {
503
- return base44Client.extend({ prefixUrl: new URL(`/api/apps/${getAppId()}/`, getBase44ApiUrl()).href });
584
+ return base44Client.extend({ prefixUrl: new URL(`/api/apps/${getBase44ClientId()}/`, getBase44ApiUrl()).href });
504
585
  }
505
586
 
506
587
  //#endregion
507
588
  //#region src/core/resources/entity/api.ts
508
589
  async function pushEntities(entities) {
509
590
  const appClient = getAppClient();
510
- const schemaSyncPayload = entities.reduce((acc, current) => {
511
- return {
512
- ...acc,
513
- [current.name]: current
514
- };
515
- }, {});
591
+ const schemaSyncPayload = Object.fromEntries(entities.map((entity) => [entity.name, entity]));
516
592
  const response = await appClient.put("entities-schemas/sync-all", {
517
593
  json: { entityNameToSchema: schemaSyncPayload },
518
594
  throwHttpErrors: false
@@ -588,13 +664,15 @@ async function readAllFunctions(functionsDir) {
588
664
 
589
665
  //#endregion
590
666
  //#region src/core/resources/function/resource.ts
591
- const functionResource = {
592
- readAll: readAllFunctions,
593
- push: async () => {}
594
- };
667
+ const functionResource = { readAll: readAllFunctions };
595
668
 
596
669
  //#endregion
597
- //#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
+ });
598
676
  const ProjectConfigSchema = z.looseObject({
599
677
  name: z.string().min(1, "Project name cannot be empty"),
600
678
  entitiesDir: z.string().default("./entities"),
@@ -666,7 +744,7 @@ const showProjectCommand = new Command("show-project").description("Display proj
666
744
  });
667
745
 
668
746
  //#endregion
669
- //#region src/core/config/app.ts
747
+ //#region src/core/project/schema.ts
670
748
  const SiteConfigSchema = z.object({
671
749
  buildCommand: z.string().optional(),
672
750
  serveCommand: z.string().optional(),
@@ -679,6 +757,60 @@ const AppConfigSchema = z.object({
679
757
  site: SiteConfigSchema.optional(),
680
758
  domains: z.array(z.string()).optional()
681
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
+ }
682
814
 
683
815
  //#endregion
684
816
  //#region src/cli/commands/entities/push.ts
@@ -704,6 +836,50 @@ const entitiesPushCommand = new Command("entities").description("Manage project
704
836
  await runCommand(pushEntitiesAction);
705
837
  }));
706
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
+
707
883
  //#endregion
708
884
  //#region package.json
709
885
  var version = "0.0.1";
@@ -715,9 +891,10 @@ program.name("base44").description("Base44 CLI - Unified interface for managing
715
891
  program.addCommand(loginCommand);
716
892
  program.addCommand(whoamiCommand);
717
893
  program.addCommand(logoutCommand);
894
+ program.addCommand(initCommand);
718
895
  program.addCommand(showProjectCommand);
719
896
  program.addCommand(entitiesPushCommand);
720
897
  program.parse();
721
898
 
722
899
  //#endregion
723
- 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.e830fa0",
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",