clefbase 1.4.1 → 1.5.1

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.
@@ -60,7 +60,7 @@ async function runInit(cwd = process.cwd()) {
60
60
  projectId: projectId.trim(),
61
61
  apiKey: apiKey.trim(),
62
62
  adminSecret: adminSecret.trim(),
63
- services: { database: false, auth: false, storage: false, hosting: false },
63
+ services: { database: false, auth: false, storage: false, hosting: false, functions: false },
64
64
  };
65
65
  // ── Step 2: Connection test ───────────────────────────────────────────────
66
66
  const connSpinner = (0, ora_1.default)("Testing connection…").start();
@@ -78,10 +78,11 @@ async function runInit(cwd = process.cwd()) {
78
78
  name: "services",
79
79
  message: "Which services will you use?",
80
80
  choices: [
81
- { name: "Database — store and query documents", value: "database", checked: true },
82
- { name: "Auth — user sign-up / sign-in", value: "auth", checked: true },
83
- { name: "Storage — file uploads", value: "storage", checked: false },
84
- { name: "Hosting — deploy static sites", value: "hosting", checked: false },
81
+ { name: "Database — store and query documents", value: "database", checked: true },
82
+ { name: "Auth — user sign-up / sign-in", value: "auth", checked: true },
83
+ { name: "Storage — file uploads", value: "storage", checked: false },
84
+ { name: "Hosting — deploy static sites", value: "hosting", checked: false },
85
+ { name: "Functions — serverless JS/TS & Python functions", value: "functions", checked: false },
85
86
  ],
86
87
  }]);
87
88
  cfg.services = {
@@ -89,6 +90,7 @@ async function runInit(cwd = process.cwd()) {
89
90
  auth: services.includes("auth"),
90
91
  storage: services.includes("storage"),
91
92
  hosting: services.includes("hosting"),
93
+ functions: services.includes("functions"),
92
94
  };
93
95
  // ── Step 4: Hosting setup ─────────────────────────────────────────────────
94
96
  if (cfg.services.hosting) {
@@ -116,21 +118,19 @@ async function runInit(cwd = process.cwd()) {
116
118
  }
117
119
  /**
118
120
  * Writes two files into `<cwd>/src/lib/`:
119
- * • clefbase.json — a copy of the project config (secrets stripped for safety)
121
+ * • clefbase.json — a copy of the project config (adminSecret stripped)
120
122
  * • clefBase.ts — ready-to-import service exports
121
123
  *
122
- * Safe to call multiple times — the TS file is regenerated on every call so it
123
- * stays in sync with the enabled services.
124
+ * Safe to call multiple times — always regenerated in sync with enabled services.
124
125
  */
125
126
  function scaffoldLib(cfg, cwd = process.cwd()) {
126
127
  const libDir = path_1.default.join(cwd, "src", "lib");
127
128
  fs_1.default.mkdirSync(libDir, { recursive: true });
128
- // ── 1. Config copy (strip adminSecret for safety) ─────────────────────────
129
+ // ── 1. Config copy (adminSecret omitted — not needed in client code) ──────
129
130
  const publicCfg = {
130
131
  serverUrl: cfg.serverUrl,
131
132
  projectId: cfg.projectId,
132
133
  apiKey: cfg.apiKey,
133
- // adminSecret intentionally omitted — not needed in client code
134
134
  services: cfg.services,
135
135
  ...(cfg.hosting ? { hosting: cfg.hosting } : {}),
136
136
  };
@@ -146,7 +146,7 @@ function scaffoldLib(cfg, cwd = process.cwd()) {
146
146
  }
147
147
  /** Build the content of src/lib/clefBase.ts based on enabled services. */
148
148
  function buildLibTs(cfg) {
149
- const { database, auth, storage } = cfg.services;
149
+ const { database, auth, storage, functions: fns } = cfg.services;
150
150
  // Collect SDK imports
151
151
  const sdkImports = ["initClefbase"];
152
152
  if (database)
@@ -155,6 +155,8 @@ function buildLibTs(cfg) {
155
155
  sdkImports.push("getAuth");
156
156
  if (storage)
157
157
  sdkImports.push("getStorage");
158
+ if (fns)
159
+ sdkImports.push("getFunctions", "httpsCallable");
158
160
  // Collect type imports
159
161
  const typeImports = [];
160
162
  if (database)
@@ -163,13 +165,22 @@ function buildLibTs(cfg) {
163
165
  typeImports.push("Auth");
164
166
  if (storage)
165
167
  typeImports.push("ClefbaseStorage");
168
+ if (fns)
169
+ typeImports.push("ClefbaseFunctions");
166
170
  const lines = [
167
171
  `/**`,
168
172
  ` * Clefbase — pre-initialised service exports`,
169
173
  ` *`,
170
174
  ` * Usage:`,
171
- ` * import { db, auth, storage } from "@lib/clefBase";`,
172
- ` * import db from "@lib/clefBase";`,
175
+ ...(database ? [` * import { db } from "@lib/clefBase";`] : []),
176
+ ...(auth ? [` * import { auth } from "@lib/clefBase";`] : []),
177
+ ...(storage ? [` * import { storage } from "@lib/clefBase";`] : []),
178
+ ...(fns ? [
179
+ ` * import { fns, httpsCallable } from "@lib/clefBase";`,
180
+ ` *`,
181
+ ` * const greet = httpsCallable<{ name: string }, { message: string }>(fns, "greetUser");`,
182
+ ` * const { data } = await greet({ name: "Alice" });`,
183
+ ] : []),
173
184
  ` */`,
174
185
  ``,
175
186
  `import { ${sdkImports.join(", ")} } from "clefbase";`,
@@ -189,7 +200,8 @@ function buildLibTs(cfg) {
189
200
  ``,
190
201
  ];
191
202
  // ── Service instances ──────────────────────────────────────────────────────
192
- if (database || auth || storage) {
203
+ const hasServices = database || auth || storage || fns;
204
+ if (hasServices) {
193
205
  lines.push("// ─── Services ───────────────────────────────────────────────────────────────");
194
206
  lines.push("");
195
207
  }
@@ -208,9 +220,22 @@ function buildLibTs(cfg) {
208
220
  lines.push(`export const storage: ClefbaseStorage = getStorage(app);`);
209
221
  lines.push("");
210
222
  }
211
- // ── Re-export app for advanced use ────────────────────────────────────────
223
+ if (fns) {
224
+ lines.push(`/** Clefbase Functions — deploy and call serverless functions. */`);
225
+ lines.push(`export const fns: ClefbaseFunctions = getFunctions(app);`);
226
+ lines.push("");
227
+ lines.push(`/**`);
228
+ lines.push(` * Create a typed callable reference to an HTTP-triggered function.`);
229
+ lines.push(` *`);
230
+ lines.push(` * @example`);
231
+ lines.push(` * const greet = httpsCallable<{ name: string }, { message: string }>(fns, "greetUser");`);
232
+ lines.push(` * const { data } = await greet({ name: "Alice" });`);
233
+ lines.push(` */`);
234
+ lines.push(`export { httpsCallable };`);
235
+ lines.push("");
236
+ }
237
+ // ── Re-export app ─────────────────────────────────────────────────────────
212
238
  lines.push(`// ─── Advanced ────────────────────────────────────────────────────────────────`, ``, `/** The underlying ClefbaseApp instance (for advanced use). */`, `export { app };`, ``);
213
- lines.push("");
214
239
  return lines.join("\n");
215
240
  }
216
241
  // ─── Hosting sub-flow ─────────────────────────────────────────────────────────
@@ -304,6 +329,8 @@ function printUsageHint(cfg) {
304
329
  namedImports.push("auth");
305
330
  if (cfg.services.storage)
306
331
  namedImports.push("storage");
332
+ if (cfg.services.functions)
333
+ namedImports.push("fns", "httpsCallable");
307
334
  console.log(chalk_1.default.bold(" Quick start:"));
308
335
  console.log();
309
336
  if (namedImports.length > 0) {
@@ -324,6 +351,16 @@ function printUsageHint(cfg) {
324
351
  console.log();
325
352
  console.log(chalk_1.default.cyan(` await storage.ref("uploads/photo.jpg").upload(file);`));
326
353
  }
354
+ if (cfg.services.functions) {
355
+ console.log();
356
+ console.log(chalk_1.default.bold(" Functions:"));
357
+ console.log(chalk_1.default.cyan(` // Deploy from a file`));
358
+ console.log(chalk_1.default.cyan(` $ clefbase functions:deploy -f ./src/functions/hello.ts`));
359
+ console.log();
360
+ console.log(chalk_1.default.cyan(` // Call from your app`));
361
+ console.log(chalk_1.default.cyan(` const greet = httpsCallable(fns, "greetUser");`));
362
+ console.log(chalk_1.default.cyan(` const { data } = await greet({ name: "Alice" });`));
363
+ }
327
364
  if (cfg.services.hosting && cfg.hosting) {
328
365
  console.log();
329
366
  console.log(chalk_1.default.bold(" Deploy:"));
@@ -13,7 +13,7 @@ const fs_1 = __importDefault(require("fs"));
13
13
  const path_1 = __importDefault(require("path"));
14
14
  // ─── File helpers ─────────────────────────────────────────────────────────────
15
15
  const CONFIG_FILE = "clefbase.json";
16
- /** Walk up the directory tree looking for clefbase.json (max 5 levels). */
16
+ /** Walk up the directory tree looking for clefbase.json (max 6 levels). */
17
17
  function findConfigPath(cwd = process.cwd()) {
18
18
  let dir = cwd;
19
19
  for (let i = 0; i < 6; i++) {
@@ -32,7 +32,12 @@ function loadConfig(cwd = process.cwd()) {
32
32
  if (!p)
33
33
  return null;
34
34
  try {
35
- return JSON.parse(fs_1.default.readFileSync(p, "utf-8"));
35
+ const raw = JSON.parse(fs_1.default.readFileSync(p, "utf-8"));
36
+ // Back-compat: configs written before functions was added won't have the field
37
+ if (raw.services && raw.services.functions === undefined) {
38
+ raw.services.functions = false;
39
+ }
40
+ return raw;
36
41
  }
37
42
  catch {
38
43
  return null;
@@ -8,11 +8,12 @@ const chalk_1 = __importDefault(require("chalk"));
8
8
  const init_1 = require("./commands/init");
9
9
  const deploy_1 = require("./commands/deploy");
10
10
  const info_1 = require("./commands/info");
11
+ const functions_1 = require("./commands/functions");
11
12
  const package_json_1 = require("../../package.json");
12
13
  const program = new commander_1.Command();
13
14
  program
14
15
  .name("clefbase")
15
- .description("Clefbase CLI — initialise projects, deploy sites, manage hosting")
16
+ .description("Clefbase CLI — initialise projects, deploy sites, manage functions")
16
17
  .version(package_json_1.version);
17
18
  // ─── init ─────────────────────────────────────────────────────────────────────
18
19
  program
@@ -105,6 +106,83 @@ program
105
106
  fatal(err);
106
107
  }
107
108
  });
109
+ // ─── functions:list ───────────────────────────────────────────────────────────
110
+ program
111
+ .command("functions:list")
112
+ .alias("fn:list")
113
+ .description("List all deployed functions for this project")
114
+ .action(async () => {
115
+ try {
116
+ await (0, functions_1.runFunctionsList)();
117
+ }
118
+ catch (err) {
119
+ fatal(err);
120
+ }
121
+ });
122
+ // ─── functions:deploy ─────────────────────────────────────────────────────────
123
+ program
124
+ .command("functions:deploy")
125
+ .alias("fn:deploy")
126
+ .description("Deploy (or redeploy) a function from a source file or interactively")
127
+ .option("-n, --name <name>", "Function name")
128
+ .option("-f, --file <path>", "Path to source file (.js, .ts, .py)")
129
+ .option("-r, --runtime <runtime>", "Runtime: node | python (auto-detected from file ext)")
130
+ .option("-t, --trigger <type>", "Trigger type (http, schedule, onDocumentCreate, …)")
131
+ .option("-c, --cron <expr>", "Cron expression for schedule triggers")
132
+ .option("-C, --collection <path>", "Collection path for document triggers")
133
+ .option("-T, --timeout <ms>", "Execution timeout in milliseconds (default: 30000)")
134
+ .option("-e, --entry <name>", "Exported function name to call (default: handler)")
135
+ .option("--env <KEY=VALUE...>", "Environment variable(s) — repeatable")
136
+ .action(async (opts) => {
137
+ try {
138
+ await (0, functions_1.runFunctionsDeploy)(opts);
139
+ }
140
+ catch (err) {
141
+ fatal(err);
142
+ }
143
+ });
144
+ // ─── functions:call ───────────────────────────────────────────────────────────
145
+ program
146
+ .command("functions:call <name>")
147
+ .alias("fn:call")
148
+ .description("Call an HTTP-triggered function and print its return value")
149
+ .option("-d, --data <json>", "JSON payload to pass as ctx.data")
150
+ .action(async (name, opts) => {
151
+ try {
152
+ await (0, functions_1.runFunctionsCall)(name, opts);
153
+ }
154
+ catch (err) {
155
+ fatal(err);
156
+ }
157
+ });
158
+ // ─── functions:delete ─────────────────────────────────────────────────────────
159
+ program
160
+ .command("functions:delete <name>")
161
+ .alias("fn:delete")
162
+ .description("Delete a deployed function")
163
+ .option("-y, --force", "Skip confirmation prompt")
164
+ .action(async (name, opts) => {
165
+ try {
166
+ await (0, functions_1.runFunctionsDelete)(name, opts);
167
+ }
168
+ catch (err) {
169
+ fatal(err);
170
+ }
171
+ });
172
+ // ─── functions:logs ───────────────────────────────────────────────────────────
173
+ program
174
+ .command("functions:logs <name>")
175
+ .alias("fn:logs")
176
+ .description("Show recent execution history for a function")
177
+ .option("-l, --limit <n>", "Number of executions to show (default: 20, max: 100)")
178
+ .action(async (name, opts) => {
179
+ try {
180
+ await (0, functions_1.runFunctionsLogs)(name, opts);
181
+ }
182
+ catch (err) {
183
+ fatal(err);
184
+ }
185
+ });
108
186
  // ─── info ─────────────────────────────────────────────────────────────────────
109
187
  program
110
188
  .command("info")
@@ -120,16 +198,29 @@ program
120
198
  // ─── help footer ─────────────────────────────────────────────────────────────
121
199
  program.addHelpText("after", `
122
200
  ${chalk_1.default.bold("Examples:")}
123
- ${chalk_1.default.cyan("clefbase init")} Set up a new project
124
- ${chalk_1.default.cyan("clefbase deploy")} Deploy your built site
125
- ${chalk_1.default.cyan("clefbase deploy -d ./dist")} Deploy from a specific directory
126
- ${chalk_1.default.cyan("clefbase deploy -m \"v2 release\"")} Deploy with a release note
127
- ${chalk_1.default.cyan("clefbase hosting:init")} Link or create a hosted site
128
- ${chalk_1.default.cyan("clefbase hosting:status")} Show current live deploy
129
- ${chalk_1.default.cyan("clefbase hosting:sites")} List all sites
130
- ${chalk_1.default.cyan("clefbase hosting:dns")} Show DNS status
131
- ${chalk_1.default.cyan("clefbase hosting:dns:reprovision")} Fix / create the preview CNAME
132
- ${chalk_1.default.cyan("clefbase info")} Show config & connection status
201
+ ${chalk_1.default.bold("Project:")}
202
+ ${chalk_1.default.cyan("clefbase init")} Set up a new project
203
+ ${chalk_1.default.cyan("clefbase info")} Show config & connection status
204
+
205
+ ${chalk_1.default.bold("Hosting:")}
206
+ ${chalk_1.default.cyan("clefbase deploy")} Deploy your built site
207
+ ${chalk_1.default.cyan("clefbase deploy -d ./dist -m \"v2\"")} Deploy from a dir with a note
208
+ ${chalk_1.default.cyan("clefbase hosting:init")} Link or create a hosted site
209
+ ${chalk_1.default.cyan("clefbase hosting:status")} Show current live deploy
210
+ ${chalk_1.default.cyan("clefbase hosting:sites")} List all sites
211
+ ${chalk_1.default.cyan("clefbase hosting:dns")} Show DNS status
212
+ ${chalk_1.default.cyan("clefbase hosting:dns:reprovision")} Fix / create the preview CNAME
213
+
214
+ ${chalk_1.default.bold("Functions:")}
215
+ ${chalk_1.default.cyan("clefbase functions:list")} List all deployed functions
216
+ ${chalk_1.default.cyan("clefbase functions:deploy")} Interactive deploy wizard
217
+ ${chalk_1.default.cyan("clefbase functions:deploy -f ./fn.ts")} Deploy from a file (auto-detects runtime)
218
+ ${chalk_1.default.cyan("clefbase functions:deploy -n greet -f fn.ts --trigger http")}
219
+ ${chalk_1.default.cyan("clefbase functions:call greetUser")} Call an HTTP function
220
+ ${chalk_1.default.cyan("clefbase functions:call greetUser -d '{\"name\":\"Alice\"}'}")}
221
+ ${chalk_1.default.cyan("clefbase functions:logs greetUser")} View execution history
222
+ ${chalk_1.default.cyan("clefbase functions:logs greetUser -l 50")} View last 50 executions
223
+ ${chalk_1.default.cyan("clefbase functions:delete oldFunction")} Delete a function
133
224
  `);
134
225
  program.parse(process.argv);
135
226
  // ─── Helper ───────────────────────────────────────────────────────────────────