clefbase 2.0.0 → 2.0.2

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.js CHANGED
@@ -33684,8 +33684,7 @@ function writeEnvExample(cfg, cwd = process.cwd()) {
33684
33684
  "# Clefbase \u2014 copy to .env and fill in your values",
33685
33685
  `CLEFBASE_SERVER_URL=${cfg.serverUrl}`,
33686
33686
  `CLEFBASE_PROJECT_ID=${cfg.projectId}`,
33687
- "CLEFBASE_API_KEY=your_api_key_here",
33688
- "CLEFBASE_ADMIN_SECRET=your_admin_secret_here"
33687
+ "CLEFBASE_API_KEY=your_api_key_here"
33689
33688
  ];
33690
33689
  import_fs2.default.writeFileSync(p, lines.join("\n") + "\n");
33691
33690
  }
@@ -33721,49 +33720,46 @@ async function apiFetch(url, opts) {
33721
33720
  function base(cfg) {
33722
33721
  return cfg.serverUrl.replace(/\/+$/, "");
33723
33722
  }
33724
- function adminHeaders(cfg) {
33725
- return { "Content-Type": "application/json", "x-admin-secret": cfg.adminSecret };
33726
- }
33727
33723
  function cfxHeaders(cfg) {
33728
33724
  return { "Content-Type": "application/json", "x-cfx-key": cfg.apiKey };
33729
33725
  }
33730
33726
  async function listSites(cfg) {
33731
33727
  return apiFetch(
33732
- `${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites`,
33733
- { headers: adminHeaders(cfg) }
33728
+ `${base(cfg)}/hosting/sites`,
33729
+ { headers: cfxHeaders(cfg) }
33734
33730
  );
33735
33731
  }
33736
33732
  async function createSite(cfg, name, description) {
33737
33733
  return apiFetch(
33738
- `${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites`,
33739
- { method: "POST", headers: adminHeaders(cfg), body: JSON.stringify({ name, description }) }
33734
+ `${base(cfg)}/hosting/sites`,
33735
+ { method: "POST", headers: cfxHeaders(cfg), body: JSON.stringify({ name, description }) }
33740
33736
  );
33741
33737
  }
33742
33738
  async function getDnsStatus(cfg, siteId) {
33743
33739
  return apiFetch(
33744
- `${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites/${siteId}/dns`,
33745
- { headers: adminHeaders(cfg) }
33740
+ `${base(cfg)}/hosting/sites/${siteId}/dns`,
33741
+ { headers: cfxHeaders(cfg) }
33746
33742
  );
33747
33743
  }
33748
33744
  async function reprovisionDns(cfg, siteId) {
33749
33745
  return apiFetch(
33750
- `${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites/${siteId}/dns/provision`,
33751
- { method: "POST", headers: adminHeaders(cfg), body: JSON.stringify({}) }
33746
+ `${base(cfg)}/hosting/sites/${siteId}/dns/reprovision`,
33747
+ { method: "POST", headers: cfxHeaders(cfg), body: JSON.stringify({}) }
33752
33748
  );
33753
33749
  }
33754
33750
  async function createDeploy(cfg, siteId, entrypoint = "index.html") {
33755
33751
  return apiFetch(
33756
- `${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites/${siteId}/deploys`,
33752
+ `${base(cfg)}/hosting/sites/${siteId}/deploys`,
33757
33753
  {
33758
33754
  method: "POST",
33759
- headers: adminHeaders(cfg),
33755
+ headers: cfxHeaders(cfg),
33760
33756
  body: JSON.stringify({ entrypoint, deployedBy: "clefbase-cli" })
33761
33757
  }
33762
33758
  );
33763
33759
  }
33764
33760
  async function uploadFileBatch(cfg, deployId, files) {
33765
33761
  const fetchFn = await getFetch();
33766
- const url = `${base(cfg)}/api/hosting/databases/${cfg.projectId}/deploys/${deployId}/files/batch`;
33762
+ const url = `${base(cfg)}/hosting/deploys/${deployId}/files/batch`;
33767
33763
  let res;
33768
33764
  if (typeof FormData !== "undefined") {
33769
33765
  const form = new FormData();
@@ -33776,7 +33772,7 @@ async function uploadFileBatch(cfg, deployId, files) {
33776
33772
  }
33777
33773
  res = await fetchFn(url, {
33778
33774
  method: "POST",
33779
- headers: { "x-admin-secret": cfg.adminSecret },
33775
+ headers: { "x-cfx-key": cfg.apiKey },
33780
33776
  body: form
33781
33777
  });
33782
33778
  } else {
@@ -33792,7 +33788,7 @@ async function uploadFileBatch(cfg, deployId, files) {
33792
33788
  }
33793
33789
  res = await fetchFn(url, {
33794
33790
  method: "POST",
33795
- headers: { "x-admin-secret": cfg.adminSecret, ...form.getHeaders() },
33791
+ headers: { "x-cfx-key": cfg.apiKey, ...form.getHeaders() },
33796
33792
  body: form
33797
33793
  });
33798
33794
  }
@@ -33809,10 +33805,10 @@ async function uploadFileBatch(cfg, deployId, files) {
33809
33805
  }
33810
33806
  async function finalizeDeploy(cfg, deployId, message) {
33811
33807
  return apiFetch(
33812
- `${base(cfg)}/api/hosting/databases/${cfg.projectId}/deploys/${deployId}/finalize`,
33808
+ `${base(cfg)}/hosting/deploys/${deployId}/finalize`,
33813
33809
  {
33814
33810
  method: "POST",
33815
- headers: adminHeaders(cfg),
33811
+ headers: cfxHeaders(cfg),
33816
33812
  body: JSON.stringify({ message: message ?? "Deployed via clefbase CLI" })
33817
33813
  }
33818
33814
  );
@@ -33820,8 +33816,8 @@ async function finalizeDeploy(cfg, deployId, message) {
33820
33816
  async function getActiveDeploy(cfg, siteId) {
33821
33817
  try {
33822
33818
  return await apiFetch(
33823
- `${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites/${siteId}/active`,
33824
- { headers: adminHeaders(cfg) }
33819
+ `${base(cfg)}/hosting/sites/${siteId}/active`,
33820
+ { headers: cfxHeaders(cfg) }
33825
33821
  );
33826
33822
  } catch (err) {
33827
33823
  if (err.status === 404) return null;
@@ -33903,18 +33899,10 @@ async function runInit(cwd = process.cwd()) {
33903
33899
  mask: "\u25CF",
33904
33900
  validate: (v) => v.trim().length > 0 || "Required"
33905
33901
  }]);
33906
- const { adminSecret } = await lib_default.prompt([{
33907
- type: "password",
33908
- name: "adminSecret",
33909
- message: "Admin Secret (x-admin-secret, needed for hosting)",
33910
- mask: "\u25CF",
33911
- validate: (v) => v.trim().length > 0 || "Required"
33912
- }]);
33913
33902
  const cfg = {
33914
33903
  serverUrl: serverUrl.replace(/\/+$/, ""),
33915
33904
  projectId: projectId.trim(),
33916
33905
  apiKey: apiKey.trim(),
33917
- adminSecret: adminSecret.trim(),
33918
33906
  services: { database: false, auth: false, storage: false, hosting: false, functions: false }
33919
33907
  };
33920
33908
  const connSpinner = ora2("Testing connection\u2026").start();
@@ -33947,6 +33935,10 @@ async function runInit(cwd = process.cwd()) {
33947
33935
  if (cfg.services.hosting) {
33948
33936
  await setupHosting(cfg, cwd);
33949
33937
  }
33938
+ let functionsRuntime;
33939
+ if (cfg.services.functions) {
33940
+ functionsRuntime = await setupFunctions(cfg, cwd);
33941
+ }
33950
33942
  const configPath = saveConfig(cfg, cwd);
33951
33943
  ensureGitignore(cwd);
33952
33944
  writeEnvExample(cfg, cwd);
@@ -33961,9 +33953,591 @@ async function runInit(cwd = process.cwd()) {
33961
33953
  console.log(source_default.dim(` Lib config: ${libResult.configCopy}`));
33962
33954
  console.log(source_default.dim(` Lib entry: ${libResult.libFile}`));
33963
33955
  }
33956
+ if (cfg.services.functions) {
33957
+ console.log(source_default.dim(` Functions: functions/ (${functionsRuntime ?? "node"})`));
33958
+ console.log(source_default.dim(` run: node functions/deploy.mjs`));
33959
+ }
33964
33960
  console.log();
33965
33961
  printUsageHint(cfg);
33966
33962
  }
33963
+ async function setupFunctions(cfg, cwd) {
33964
+ console.log();
33965
+ console.log(source_default.bold(" Functions"));
33966
+ console.log();
33967
+ const { runtime } = await lib_default.prompt([{
33968
+ type: "list",
33969
+ name: "runtime",
33970
+ message: "Default functions runtime",
33971
+ choices: [
33972
+ { name: "Node.js / TypeScript (recommended)", value: "node" },
33973
+ { name: "Python 3", value: "python" },
33974
+ { name: "Both (create examples for each)", value: "both" }
33975
+ ],
33976
+ default: "node"
33977
+ }]);
33978
+ const scaffoldResult = scaffoldFunctions(cfg, cwd, runtime);
33979
+ const sp = ora2("Creating functions/ folder\u2026").start();
33980
+ sp.succeed(
33981
+ source_default.green(
33982
+ `functions/ created (${scaffoldResult.files.length} file${scaffoldResult.files.length !== 1 ? "s" : ""})`
33983
+ )
33984
+ );
33985
+ return runtime;
33986
+ }
33987
+ function scaffoldFunctions(cfg, cwd = process.cwd(), runtime = "node") {
33988
+ const dir = import_path2.default.join(cwd, "functions");
33989
+ const files = [];
33990
+ import_fs3.default.mkdirSync(dir, { recursive: true });
33991
+ const write = (rel, content) => {
33992
+ const abs = import_path2.default.join(dir, rel);
33993
+ import_fs3.default.mkdirSync(import_path2.default.dirname(abs), { recursive: true });
33994
+ if (!import_fs3.default.existsSync(abs)) {
33995
+ import_fs3.default.writeFileSync(abs, content);
33996
+ files.push(import_path2.default.join("functions", rel));
33997
+ }
33998
+ };
33999
+ const wantNode = runtime === "node" || runtime === "both";
34000
+ const wantPython = runtime === "python" || runtime === "both";
34001
+ write("README.md", buildFunctionsReadme(cfg, runtime));
34002
+ write(".env", buildFunctionsEnv(cfg));
34003
+ write(".env.example", buildFunctionsEnvExample(cfg));
34004
+ write(".gitignore", FUNCTIONS_GITIGNORE);
34005
+ if (wantNode) {
34006
+ write("src/hello.ts", NODE_HELLO_TS);
34007
+ write("src/onUserCreate.ts", NODE_ON_USER_CREATE_TS);
34008
+ write("src/scheduled.ts", NODE_SCHEDULED_TS);
34009
+ write("tsconfig.json", FUNCTIONS_TSCONFIG);
34010
+ }
34011
+ if (wantPython) {
34012
+ write("python/hello.py", PYTHON_HELLO_PY);
34013
+ write("python/on_user_create.py", PYTHON_ON_USER_CREATE_PY);
34014
+ write("python/scheduled.py", PYTHON_SCHEDULED_PY);
34015
+ write("python/requirements.txt", PYTHON_REQUIREMENTS_TXT);
34016
+ }
34017
+ write("package.json", buildFunctionsPackageJson(cfg, runtime));
34018
+ write("deploy.mjs", buildDeployMjs(cfg, runtime));
34019
+ return { dir, files };
34020
+ }
34021
+ function buildFunctionsReadme(cfg, runtime) {
34022
+ const wantNode = runtime === "node" || runtime === "both";
34023
+ const wantPython = runtime === "python" || runtime === "both";
34024
+ const baseUrl = cfg.serverUrl.replace(/\/+$/, "");
34025
+ return `# Clefbase Functions
34026
+
34027
+ Serverless functions for project \`${cfg.projectId}\`.
34028
+
34029
+ Functions run on the Clefbase server and are triggered by HTTP calls,
34030
+ database events, auth events, storage events, or a cron schedule.
34031
+ No infrastructure to manage \u2014 just write code and deploy.
34032
+
34033
+ ---
34034
+
34035
+ ## Quick start
34036
+ ${wantNode ? `
34037
+ ### Node.js / TypeScript
34038
+
34039
+ \`\`\`bash
34040
+ # 1. Install the Clefbase CLI (if not already)
34041
+ npm install -g clefbase
34042
+
34043
+ # 2. Deploy a function
34044
+ clefbase functions:deploy -f src/hello.ts --name hello --trigger http
34045
+
34046
+ # 3. Call it
34047
+ clefbase functions:call hello -d '{"name":"World"}'
34048
+ # or via HTTP
34049
+ curl -X POST ${baseUrl}/functions/call/hello \\
34050
+ -H "x-cfx-key: <YOUR_API_KEY>" \\
34051
+ -H "Content-Type: application/json" \\
34052
+ -d '{"data":{"name":"World"}}'
34053
+ \`\`\`
34054
+ ` : ""}${wantPython ? `
34055
+ ### Python 3
34056
+
34057
+ \`\`\`bash
34058
+ # Deploy a Python function
34059
+ clefbase functions:deploy -f python/hello.py --name hello-py --runtime python --trigger http
34060
+
34061
+ # Call it
34062
+ clefbase functions:call hello-py -d '{"name":"World"}'
34063
+ \`\`\`
34064
+ ` : ""}
34065
+ ---
34066
+
34067
+ ## Writing functions
34068
+
34069
+ Every function must export a single async **\`handler\`** (or the name you
34070
+ pass as \`--entry\`). It receives one argument \u2014 a **\`FunctionContext\`** \u2014 and
34071
+ can return any JSON-serialisable value.
34072
+
34073
+ ### FunctionContext shape
34074
+
34075
+ \`\`\`ts
34076
+ interface FunctionContext {
34077
+ /** Payload supplied by the caller (HTTP) or the event (triggers). */
34078
+ data: unknown;
34079
+
34080
+ /** Populated when the caller includes a valid auth token. */
34081
+ auth?: {
34082
+ uid: string;
34083
+ email?: string;
34084
+ metadata: Record<string, unknown>;
34085
+ };
34086
+
34087
+ /** Describes what fired the function. */
34088
+ trigger: {
34089
+ type: string; // "http" | "schedule" | "onDocumentCreate" | \u2026
34090
+ timestamp: string; // ISO 8601
34091
+ document?: unknown; // populated for document triggers
34092
+ before?: unknown; // populated for onDocumentUpdate / onDocumentWrite
34093
+ after?: unknown; // populated for onDocumentUpdate / onDocumentWrite
34094
+ user?: unknown; // populated for onUserCreate / onUserDelete
34095
+ file?: unknown; // populated for onFileUpload / onFileDelete
34096
+ };
34097
+
34098
+ /** Key-value env vars you passed at deploy time. */
34099
+ env: Record<string, string>;
34100
+ }
34101
+ \`\`\`
34102
+
34103
+ ### Trigger types
34104
+
34105
+ | Type | When it fires |
34106
+ |------|---------------|
34107
+ | \`http\` | \`POST /functions/call/:name\` |
34108
+ | \`schedule\` | Cron timer (e.g. \`0 * * * *\` = every hour) |
34109
+ | \`onDocumentCreate\` | A document is created in the watched collection |
34110
+ | \`onDocumentUpdate\` | A document is updated |
34111
+ | \`onDocumentDelete\` | A document is deleted |
34112
+ | \`onDocumentWrite\` | Any of create / update / delete |
34113
+ | \`onUserCreate\` | A new user account is created |
34114
+ | \`onUserDelete\` | A user account is deleted |
34115
+ | \`onFileUpload\` | A file is uploaded to Storage |
34116
+ | \`onFileDelete\` | A file is deleted from Storage |
34117
+
34118
+ ---
34119
+
34120
+ ## Deployment examples
34121
+
34122
+ \`\`\`bash
34123
+ # HTTP function
34124
+ clefbase functions:deploy -f src/hello.ts --trigger http
34125
+
34126
+ # Scheduled (every day at midnight UTC)
34127
+ clefbase functions:deploy -f src/scheduled.ts --trigger schedule --cron "0 0 * * *"
34128
+
34129
+ # Document trigger (fires on every new "orders" document)
34130
+ clefbase functions:deploy -f src/onOrder.ts --trigger onDocumentCreate --collection orders
34131
+
34132
+ # With env vars and a custom timeout
34133
+ clefbase functions:deploy -f src/sendEmail.ts \\
34134
+ --trigger http \\
34135
+ --env SENDGRID_KEY=SG.xxx \\
34136
+ --timeout 15000
34137
+
34138
+ # Use the deploy script in this folder (deploys everything at once)
34139
+ node deploy.mjs
34140
+ \`\`\`
34141
+
34142
+ ---
34143
+
34144
+ ## Calling HTTP functions
34145
+
34146
+ ### From the Clefbase SDK
34147
+
34148
+ \`\`\`ts
34149
+ import { getFunctions, httpsCallable } from "clefbase";
34150
+ // or: import { fns } from "@lib/clefBase";
34151
+
34152
+ const greet = httpsCallable<{ name: string }, { message: string }>(fns, "hello");
34153
+ const { data, durationMs } = await greet({ name: "Alice" });
34154
+ console.log(data.message); // "Hello, Alice!"
34155
+ console.log(durationMs); // e.g. 42
34156
+ \`\`\`
34157
+
34158
+ ### Auth-aware calls
34159
+
34160
+ \`\`\`ts
34161
+ import { getAuth, setAuthToken } from "clefbase";
34162
+
34163
+ const { token } = await getAuth(app).signIn("alice@example.com", "password");
34164
+ setAuthToken(app, token);
34165
+
34166
+ // ctx.auth.uid and ctx.auth.email are now available inside the function
34167
+ const { data } = await callFunction(fns, "getProfile");
34168
+ \`\`\`
34169
+
34170
+ ### Raw HTTP (no SDK)
34171
+
34172
+ \`\`\`bash
34173
+ curl -X POST ${baseUrl}/functions/call/<functionName> \\
34174
+ -H "x-cfx-key: <YOUR_API_KEY>" \\
34175
+ -H "Content-Type: application/json" \\
34176
+ -H "Authorization: Bearer <USER_JWT>" # optional auth
34177
+ -d '{"data": { "key": "value" }}'
34178
+ \`\`\`
34179
+
34180
+ ---
34181
+
34182
+ ## Viewing logs
34183
+
34184
+ \`\`\`bash
34185
+ # Last 20 executions
34186
+ clefbase functions:logs hello
34187
+
34188
+ # Last 50
34189
+ clefbase functions:logs hello -l 50
34190
+
34191
+ # All functions
34192
+ clefbase functions:list
34193
+ \`\`\`
34194
+
34195
+ ---
34196
+
34197
+ ## Managing functions
34198
+
34199
+ \`\`\`bash
34200
+ clefbase functions:list # see all deployed functions
34201
+ clefbase functions:delete oldFunction # delete (prompts for confirmation)
34202
+ clefbase functions:delete oldFunction -y # delete without confirmation
34203
+ \`\`\`
34204
+
34205
+ ---
34206
+
34207
+ ## Environment variables
34208
+
34209
+ Secrets and config for your functions live in \`functions/.env\`.
34210
+ They are **never** committed to git (already in \`.gitignore\`).
34211
+
34212
+ Pass them at deploy time with \`--env KEY=VALUE\` or edit \`deploy.sh\`
34213
+ to read them automatically from \`.env\`.
34214
+
34215
+ ---
34216
+
34217
+ ## Project info
34218
+
34219
+ | Key | Value |
34220
+ |-----|-------|
34221
+ | Project ID | \`${cfg.projectId}\` |
34222
+ | Server | \`${baseUrl}\` |
34223
+ | Functions base URL | \`${baseUrl}/functions\` |
34224
+ `;
34225
+ }
34226
+ function buildFunctionsEnv(cfg) {
34227
+ return `# Clefbase Functions \u2014 secrets for this project
34228
+ # This file is git-ignored. Do NOT commit it.
34229
+ # Copy values from your Clefbase dashboard.
34230
+
34231
+ CLEFBASE_SERVER_URL=${cfg.serverUrl}
34232
+ CLEFBASE_PROJECT_ID=${cfg.projectId}
34233
+ CLEFBASE_API_KEY=${cfg.apiKey}
34234
+
34235
+ # Add your own secrets below:
34236
+ # SENDGRID_API_KEY=
34237
+ # STRIPE_SECRET_KEY=
34238
+ # DATABASE_URL=
34239
+ `;
34240
+ }
34241
+ function buildFunctionsEnvExample(cfg) {
34242
+ return `# Clefbase Functions \u2014 environment variable template
34243
+ # Copy this to .env and fill in your values.
34244
+
34245
+ CLEFBASE_SERVER_URL=${cfg.serverUrl}
34246
+ CLEFBASE_PROJECT_ID=${cfg.projectId}
34247
+ CLEFBASE_API_KEY=your_api_key_here
34248
+
34249
+ # Add your own secrets below:
34250
+ # SENDGRID_API_KEY=
34251
+ # STRIPE_SECRET_KEY=
34252
+ # DATABASE_URL=
34253
+ `;
34254
+ }
34255
+ var FUNCTIONS_GITIGNORE = `# Dependencies
34256
+ node_modules/
34257
+
34258
+ # Python
34259
+ __pycache__/
34260
+ *.pyc
34261
+ *.pyo
34262
+ .venv/
34263
+ venv/
34264
+
34265
+ # Secrets \u2014 never commit these
34266
+ .env
34267
+
34268
+ # Build artefacts
34269
+ dist/
34270
+ *.js.map
34271
+ `;
34272
+ var FUNCTIONS_TSCONFIG = `{
34273
+ "compilerOptions": {
34274
+ "target": "ES2022",
34275
+ "module": "ESNext",
34276
+ "moduleResolution": "bundler",
34277
+ "lib": ["ES2022"],
34278
+ "strict": true,
34279
+ "esModuleInterop": true,
34280
+ "skipLibCheck": true,
34281
+ "outDir": "dist"
34282
+ },
34283
+ "include": ["src/**/*"],
34284
+ "exclude": ["node_modules", "dist"]
34285
+ }
34286
+ `;
34287
+ var NODE_HELLO_TS = `/**
34288
+ * hello.ts \u2014 HTTP-triggered "hello world" function.
34289
+ *
34290
+ * Deploy:
34291
+ * clefbase functions:deploy -f src/hello.ts --name hello --trigger http
34292
+ *
34293
+ * Call:
34294
+ * clefbase functions:call hello -d '{"name":"World"}'
34295
+ */
34296
+
34297
+ import type { FunctionContext } from "clefbase";
34298
+
34299
+ interface Input { name?: string }
34300
+ interface Output { message: string; timestamp: string }
34301
+
34302
+ export async function handler(ctx: FunctionContext): Promise<Output> {
34303
+ const { name = "World" } = (ctx.data ?? {}) as Input;
34304
+
34305
+ return {
34306
+ message: \`Hello, \${name}!\`,
34307
+ timestamp: ctx.trigger.timestamp,
34308
+ };
34309
+ }
34310
+ `;
34311
+ var NODE_ON_USER_CREATE_TS = `/**
34312
+ * onUserCreate.ts \u2014 fires whenever a new user signs up.
34313
+ *
34314
+ * Deploy:
34315
+ * clefbase functions:deploy -f src/onUserCreate.ts \\
34316
+ * --name onUserCreate --trigger onUserCreate
34317
+ *
34318
+ * ctx.trigger.user contains the new user's profile.
34319
+ */
34320
+
34321
+ import type { FunctionContext } from "clefbase";
34322
+
34323
+ export async function handler(ctx: FunctionContext): Promise<void> {
34324
+ const user = ctx.trigger.user as {
34325
+ uid: string;
34326
+ email?: string;
34327
+ metadata: Record<string, unknown>;
34328
+ } | undefined;
34329
+
34330
+ if (!user) return;
34331
+
34332
+ console.log(\`New user: \${user.uid} email: \${user.email ?? "\u2014"}\`);
34333
+
34334
+ // Example: send a welcome email, add to a mailing list, seed default data\u2026
34335
+ // const emailKey = ctx.env["SENDGRID_API_KEY"];
34336
+ }
34337
+ `;
34338
+ var NODE_SCHEDULED_TS = `/**
34339
+ * scheduled.ts \u2014 runs on a cron schedule.
34340
+ *
34341
+ * Deploy (every day at midnight UTC):
34342
+ * clefbase functions:deploy -f src/scheduled.ts \\
34343
+ * --name dailyCleanup --trigger schedule --cron "0 0 * * *"
34344
+ *
34345
+ * Common cron expressions:
34346
+ * "* * * * *" every minute
34347
+ * "0 * * * *" every hour
34348
+ * "0 9 * * 1" every Monday at 09:00 UTC
34349
+ */
34350
+
34351
+ import type { FunctionContext } from "clefbase";
34352
+
34353
+ export async function handler(ctx: FunctionContext): Promise<{ cleaned: number }> {
34354
+ console.log("Running scheduled cleanup at", ctx.trigger.timestamp);
34355
+
34356
+ // TODO: your scheduled work here
34357
+ const cleaned = 0;
34358
+
34359
+ return { cleaned };
34360
+ }
34361
+ `;
34362
+ var PYTHON_HELLO_PY = `"""
34363
+ hello.py \u2014 HTTP-triggered "hello world" function.
34364
+
34365
+ Deploy:
34366
+ clefbase functions:deploy -f python/hello.py \\
34367
+ --name hello-py --runtime python --trigger http
34368
+
34369
+ Call:
34370
+ clefbase functions:call hello-py -d '{"name":"World"}'
34371
+ """
34372
+
34373
+
34374
+ async def handler(ctx):
34375
+ """
34376
+ ctx.data \u2014 payload from the caller
34377
+ ctx.auth \u2014 populated when a valid auth token is included
34378
+ ctx.trigger \u2014 describes what fired this function
34379
+ ctx.env \u2014 env vars you passed at deploy time
34380
+ """
34381
+ data = ctx.get("data") or {}
34382
+ name = data.get("name", "World")
34383
+
34384
+ return {
34385
+ "message": f"Hello, {name}!",
34386
+ "timestamp": ctx["trigger"]["timestamp"],
34387
+ }
34388
+ `;
34389
+ var PYTHON_ON_USER_CREATE_PY = `"""
34390
+ on_user_create.py \u2014 fires whenever a new user signs up.
34391
+
34392
+ Deploy:
34393
+ clefbase functions:deploy -f python/on_user_create.py \\
34394
+ --name onUserCreate-py --runtime python --trigger onUserCreate
34395
+ """
34396
+
34397
+
34398
+ async def handler(ctx):
34399
+ user = (ctx.get("trigger") or {}).get("user") or {}
34400
+
34401
+ uid = user.get("uid", "unknown")
34402
+ email = user.get("email", "\u2014")
34403
+
34404
+ print(f"New user: {uid} email: {email}")
34405
+
34406
+ # Example: send a welcome email, seed default data, etc.
34407
+ # sendgrid_key = ctx["env"].get("SENDGRID_API_KEY")
34408
+ `;
34409
+ var PYTHON_SCHEDULED_PY = `"""
34410
+ scheduled.py \u2014 runs on a cron schedule.
34411
+
34412
+ Deploy (every day at midnight UTC):
34413
+ clefbase functions:deploy -f python/scheduled.py \\
34414
+ --name dailyCleanup-py --runtime python \\
34415
+ --trigger schedule --cron "0 0 * * *"
34416
+ """
34417
+
34418
+
34419
+ async def handler(ctx):
34420
+ print("Running scheduled cleanup at", ctx["trigger"]["timestamp"])
34421
+
34422
+ # TODO: your scheduled work here
34423
+ cleaned = 0
34424
+
34425
+ return {"cleaned": cleaned}
34426
+ `;
34427
+ var PYTHON_REQUIREMENTS_TXT = `# Add your Python dependencies here.
34428
+ # They are NOT automatically installed on the server \u2014 include only stdlib
34429
+ # and packages already available in the Clefbase Python runtime.
34430
+ #
34431
+ # For heavier workloads, consider the Node.js runtime and npm packages.
34432
+ #
34433
+ # httpx>=0.27
34434
+ # pydantic>=2.0
34435
+ `;
34436
+ function buildDeployMjs(cfg, runtime) {
34437
+ const wantNode = runtime === "node" || runtime === "both";
34438
+ const wantPython = runtime === "python" || runtime === "both";
34439
+ const fns = [];
34440
+ if (wantNode) {
34441
+ fns.push(
34442
+ { name: "hello", file: "src/hello.ts", runtime: "node", trigger: "http" },
34443
+ { name: "onUserCreate", file: "src/onUserCreate.ts", runtime: "node", trigger: "onUserCreate" },
34444
+ { name: "dailyCleanup", file: "src/scheduled.ts", runtime: "node", trigger: "schedule", cron: "0 0 * * *" }
34445
+ );
34446
+ }
34447
+ if (wantPython) {
34448
+ fns.push(
34449
+ { name: "hello-py", file: "python/hello.py", runtime: "python", trigger: "http" },
34450
+ { name: "onUserCreate-py", file: "python/on_user_create.py", runtime: "python", trigger: "onUserCreate" },
34451
+ { name: "dailyCleanup-py", file: "python/scheduled.py", runtime: "python", trigger: "schedule", cron: "0 0 * * *" }
34452
+ );
34453
+ }
34454
+ const fnJson = JSON.stringify(fns, null, 2);
34455
+ return `#!/usr/bin/env node
34456
+ // deploy.mjs \u2014 deploy all functions in this folder to Clefbase
34457
+ // Generated by \`clefbase init\` \u2022 project: ${cfg.projectId}
34458
+ //
34459
+ // Usage (any OS): node deploy.mjs
34460
+ //
34461
+ // Node.js 18+ required (uses built-in fetch + fs).
34462
+ // No extra dependencies \u2014 just the Clefbase CLI on your PATH.
34463
+
34464
+ import { execSync } from "node:child_process";
34465
+ import { existsSync, readFileSync } from "node:fs";
34466
+ import { fileURLToPath } from "node:url";
34467
+ import { dirname, join } from "node:path";
34468
+
34469
+ const __dirname = dirname(fileURLToPath(import.meta.url));
34470
+
34471
+ // \u2500\u2500 Load .env \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
34472
+
34473
+ const envPath = join(__dirname, ".env");
34474
+ if (existsSync(envPath)) {
34475
+ for (const line of readFileSync(envPath, "utf8").split("\\n")) {
34476
+ const match = line.match(/^\\s*([^#][^=]*)=(.*)$/);
34477
+ if (match) process.env[match[1].trim()] = match[2].trim();
34478
+ }
34479
+ }
34480
+
34481
+ // \u2500\u2500 Functions to deploy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
34482
+
34483
+ const functions = ${fnJson};
34484
+
34485
+ // \u2500\u2500 Deploy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
34486
+
34487
+ let passed = 0;
34488
+ let failed = 0;
34489
+
34490
+ for (const fn of functions) {
34491
+ const args = [
34492
+ \`--name \${fn.name}\`,
34493
+ \`--file \${fn.file}\`,
34494
+ \`--runtime \${fn.runtime}\`,
34495
+ \`--trigger \${fn.trigger}\`,
34496
+ fn.cron ? \`--cron "\${fn.cron}"\` : "",
34497
+ fn.collection ? \`--collection "\${fn.collection}"\` : "",
34498
+ ].filter(Boolean).join(" ");
34499
+
34500
+ console.log(\`\\n\u2192 Deploying "\${fn.name}"\u2026\`);
34501
+ try {
34502
+ execSync(\`clefbase functions:deploy \${args}\`, { stdio: "inherit", cwd: __dirname });
34503
+ passed++;
34504
+ } catch {
34505
+ console.error(\` \u2717 "\${fn.name}" failed\`);
34506
+ failed++;
34507
+ }
34508
+ }
34509
+
34510
+ // \u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
34511
+
34512
+ console.log(\`\\n\${passed} deployed, \${failed} failed \u2014 project: ${cfg.projectId}\\n\`);
34513
+ if (failed > 0) process.exit(1);
34514
+ `;
34515
+ }
34516
+ function buildFunctionsPackageJson(cfg, runtime) {
34517
+ const wantNode = runtime === "node" || runtime === "both";
34518
+ const scripts = {
34519
+ "deploy:all": "node deploy.mjs"
34520
+ };
34521
+ if (wantNode) {
34522
+ scripts["deploy:hello"] = "clefbase functions:deploy -f src/hello.ts --name hello --runtime node --trigger http";
34523
+ scripts["deploy:onUserCreate"] = "clefbase functions:deploy -f src/onUserCreate.ts --name onUserCreate --runtime node --trigger onUserCreate";
34524
+ scripts["deploy:scheduled"] = 'clefbase functions:deploy -f src/scheduled.ts --name dailyCleanup --runtime node --trigger schedule --cron "0 0 * * *"';
34525
+ }
34526
+ if (runtime === "python" || runtime === "both") {
34527
+ scripts["deploy:hello-py"] = "clefbase functions:deploy -f python/hello.py --name hello-py --runtime python --trigger http";
34528
+ scripts["deploy:onUserCreate-py"] = "clefbase functions:deploy -f python/on_user_create.py --name onUserCreate-py --runtime python --trigger onUserCreate";
34529
+ scripts["deploy:scheduled-py"] = 'clefbase functions:deploy -f python/scheduled.py --name dailyCleanup-py --runtime python --trigger schedule --cron "0 0 * * *"';
34530
+ }
34531
+ const pkg = {
34532
+ name: `${cfg.projectId}-functions`,
34533
+ version: "1.0.0",
34534
+ description: `Clefbase Functions for project ${cfg.projectId}`,
34535
+ private: true,
34536
+ type: "module",
34537
+ scripts
34538
+ };
34539
+ return JSON.stringify(pkg, null, 2) + "\n";
34540
+ }
33967
34541
  function scaffoldLib(cfg, cwd = process.cwd()) {
33968
34542
  const libDir = import_path2.default.join(cwd, "src", "lib");
33969
34543
  import_fs3.default.mkdirSync(libDir, { recursive: true });
@@ -34001,7 +34575,18 @@ function buildLibTs(cfg) {
34001
34575
  ` *`,
34002
34576
  ` * Usage:`,
34003
34577
  ...database ? [` * import { db } from "@lib/clefBase";`] : [],
34004
- ...auth ? [` * import { auth } from "@lib/clefBase";`] : [],
34578
+ ...auth ? [
34579
+ ` * import { auth } from "@lib/clefBase";`,
34580
+ ` *`,
34581
+ ` * // Email / password`,
34582
+ ` * const { user } = await auth.signIn("alice@example.com", "pass");`,
34583
+ ` *`,
34584
+ ` * // Google sign-in \u2014 add this to your root component / entry point:`,
34585
+ ` * await auth.handleGatewayCallback(); // handles ?cfx_token= on return`,
34586
+ ` *`,
34587
+ ` * // On sign-in button click:`,
34588
+ ` * await auth.signInWithGateway("google");`
34589
+ ] : [],
34005
34590
  ...storage ? [` * import { storage } from "@lib/clefBase";`] : [],
34006
34591
  ...fns ? [
34007
34592
  ` * import { fns, httpsCallable } from "@lib/clefBase";`,
@@ -34018,10 +34603,9 @@ function buildLibTs(cfg) {
34018
34603
  `// \u2500\u2500\u2500 App \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
34019
34604
  ``,
34020
34605
  `const app = initClefbase({`,
34021
- ` serverUrl: config.serverUrl,`,
34022
- ` projectId: config.projectId,`,
34023
- ` apiKey: config.apiKey,`,
34024
- ` adminSecret: "",`,
34606
+ ` serverUrl: config.serverUrl,`,
34607
+ ` projectId: config.projectId,`,
34608
+ ` apiKey: config.apiKey,`,
34025
34609
  `});`,
34026
34610
  ``
34027
34611
  ];
@@ -34039,6 +34623,20 @@ function buildLibTs(cfg) {
34039
34623
  lines.push(`/** Clefbase Auth \u2014 sign up, sign in, manage sessions. */`);
34040
34624
  lines.push(`export const auth: Auth = getAuth(app);`);
34041
34625
  lines.push("");
34626
+ lines.push(`/**`);
34627
+ lines.push(` * Call this once on every page load (before rendering your UI).`);
34628
+ lines.push(` * Detects the ?cfx_token= param the gateway appends after OAuth,`);
34629
+ lines.push(` * saves the session, and cleans the URL.`);
34630
+ lines.push(` *`);
34631
+ lines.push(` * @example`);
34632
+ lines.push(` * // React / Next.js \u2014 in your root layout or _app.tsx:`);
34633
+ lines.push(` * useEffect(() => { auth.handleGatewayCallback(); }, []);`);
34634
+ lines.push(` *`);
34635
+ lines.push(` * // Vanilla JS \u2014 at the top of your entry point:`);
34636
+ lines.push(` * await auth.handleGatewayCallback();`);
34637
+ lines.push(` */`);
34638
+ lines.push(`export { auth };`);
34639
+ lines.push("");
34042
34640
  }
34043
34641
  if (storage) {
34044
34642
  lines.push(`/** Clefbase Storage \u2014 upload and manage files. */`);
@@ -34069,7 +34667,6 @@ function buildLibTs(cfg) {
34069
34667
  return lines.join("\n");
34070
34668
  }
34071
34669
  async function setupHosting(cfg, cwd) {
34072
- var _a;
34073
34670
  console.log();
34074
34671
  console.log(source_default.bold(" Hosting"));
34075
34672
  console.log();
@@ -34086,6 +34683,7 @@ async function setupHosting(cfg, cwd) {
34086
34683
  }
34087
34684
  let siteId = "";
34088
34685
  let siteName = "";
34686
+ let previewUrl = "";
34089
34687
  if (existing.length > 0) {
34090
34688
  const choices = [
34091
34689
  ...existing.map((s) => ({ name: `${s.name} ${source_default.dim(s.id)}`, value: s.id })),
@@ -34098,8 +34696,10 @@ async function setupHosting(cfg, cwd) {
34098
34696
  choices
34099
34697
  }]);
34100
34698
  if (chosen !== "__new__") {
34699
+ const found = existing.find((s) => s.id === chosen);
34101
34700
  siteId = chosen;
34102
- siteName = ((_a = existing.find((s) => s.id === chosen)) == null ? void 0 : _a.name) ?? chosen;
34701
+ siteName = (found == null ? void 0 : found.name) ?? chosen;
34702
+ previewUrl = (found == null ? void 0 : found.previewUrl) ?? "";
34103
34703
  }
34104
34704
  }
34105
34705
  if (!siteId) {
@@ -34114,6 +34714,7 @@ async function setupHosting(cfg, cwd) {
34114
34714
  const site = await createSite(cfg, newName.trim());
34115
34715
  siteId = site.id;
34116
34716
  siteName = site.name;
34717
+ previewUrl = site.previewUrl ?? "";
34117
34718
  s.succeed(source_default.green(`Created "${siteName}" ${source_default.dim(siteId)}`));
34118
34719
  } catch (err) {
34119
34720
  s.fail(`Failed: ${err.message}`);
@@ -34137,6 +34738,7 @@ async function setupHosting(cfg, cwd) {
34137
34738
  cfg.hosting = {
34138
34739
  siteId,
34139
34740
  siteName,
34741
+ previewUrl,
34140
34742
  distDir: distDir.trim(),
34141
34743
  entrypoint: entrypoint.trim()
34142
34744
  };
@@ -34167,6 +34769,8 @@ function printUsageHint(cfg) {
34167
34769
  if (cfg.services.auth) {
34168
34770
  console.log();
34169
34771
  console.log(source_default.cyan(` const { user } = await auth.signIn("email", "pass");`));
34772
+ console.log(source_default.cyan(` await auth.signInWithGateway("google"); // redirects to auth.cleforyx.com`));
34773
+ console.log(source_default.cyan(` await auth.handleGatewayCallback(); // call on every page load`));
34170
34774
  }
34171
34775
  if (cfg.services.storage) {
34172
34776
  console.log();
@@ -34175,12 +34779,15 @@ function printUsageHint(cfg) {
34175
34779
  if (cfg.services.functions) {
34176
34780
  console.log();
34177
34781
  console.log(source_default.bold(" Functions:"));
34178
- console.log(source_default.cyan(` // Deploy from a file`));
34179
- console.log(source_default.cyan(` $ clefbase functions:deploy -f ./src/functions/hello.ts`));
34782
+ console.log(source_default.cyan(` # Edit your functions in functions/src/`));
34783
+ console.log(source_default.cyan(` $ clefbase functions:deploy -f functions/src/hello.ts`));
34180
34784
  console.log();
34181
- console.log(source_default.cyan(` // Call from your app`));
34785
+ console.log(source_default.cyan(` # Call from your app`));
34182
34786
  console.log(source_default.cyan(` const greet = httpsCallable(fns, "greetUser");`));
34183
34787
  console.log(source_default.cyan(` const { data } = await greet({ name: "Alice" });`));
34788
+ console.log();
34789
+ console.log(source_default.dim(` Full guide: functions/README.md`));
34790
+ console.log(source_default.dim(` Deploy all: node functions/deploy.mjs`));
34184
34791
  }
34185
34792
  if (cfg.services.hosting && cfg.hosting) {
34186
34793
  console.log();
@@ -34194,6 +34801,7 @@ function printUsageHint(cfg) {
34194
34801
  // src/cli/commands/deploy.ts
34195
34802
  var import_path3 = __toESM(require("path"));
34196
34803
  var import_fs4 = __toESM(require("fs"));
34804
+ var import_child_process2 = require("child_process");
34197
34805
  var SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", ".gitkeep", ".gitignore"]);
34198
34806
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svn", ".hg"]);
34199
34807
  function scanDir(dir, base2, out) {
@@ -34218,15 +34826,51 @@ function fmtBytes(n) {
34218
34826
  return `${(n / (1024 * 1024)).toFixed(2)} MB`;
34219
34827
  }
34220
34828
  async function runDeploy(opts) {
34221
- var _a, _b, _c, _d, _e, _f, _g, _h;
34222
34829
  const cwd = opts.cwd ?? process.cwd();
34223
34830
  const cfg = requireConfig(cwd);
34831
+ const deployHosting = !opts.only || opts.only === "hosting";
34832
+ const deployFunctions = !opts.only || opts.only === "functions";
34833
+ if (deployHosting && !cfg.services.hosting && opts.only === "hosting") {
34834
+ console.error(source_default.red("\n Hosting is not enabled for this project.\n Run `clefbase hosting:init` to set it up.\n"));
34835
+ process.exit(1);
34836
+ }
34837
+ if (deployFunctions && !cfg.services.functions && opts.only === "functions") {
34838
+ console.error(source_default.red("\n Functions is not enabled for this project.\n Run `clefbase init` and enable Functions to set it up.\n"));
34839
+ process.exit(1);
34840
+ }
34224
34841
  console.log();
34225
- console.log(source_default.bold.cyan(" Clefbase Deploy"));
34842
+ if (opts.only === "hosting") {
34843
+ console.log(source_default.bold.cyan(" Clefbase Deploy \xB7 Hosting"));
34844
+ } else if (opts.only === "functions") {
34845
+ console.log(source_default.bold.cyan(" Clefbase Deploy \xB7 Functions"));
34846
+ } else {
34847
+ console.log(source_default.bold.cyan(" Clefbase Deploy"));
34848
+ }
34226
34849
  console.log();
34850
+ if (deployFunctions && cfg.services.functions) {
34851
+ await runFunctionsDeploy({ cwd });
34852
+ if (deployHosting && cfg.services.hosting) {
34853
+ console.log();
34854
+ }
34855
+ }
34856
+ if (deployHosting && cfg.services.hosting) {
34857
+ await runHostingDeploy({ dir: opts.dir, message: opts.message, site: opts.site, cwd });
34858
+ }
34859
+ if (!deployHosting && !deployFunctions) {
34860
+ console.log(source_default.yellow(" Nothing to deploy \u2014 no services matched.\n"));
34861
+ }
34862
+ }
34863
+ async function runHostingDeploy(opts) {
34864
+ var _a, _b, _c, _d, _e, _f, _g, _h;
34865
+ const cwd = opts.cwd ?? process.cwd();
34866
+ const cfg = requireConfig(cwd);
34867
+ if (!cfg.services.hosting) {
34868
+ console.error(source_default.red("\n Hosting is not enabled for this project.\n Run `clefbase hosting:init` to set it up.\n"));
34869
+ process.exit(1);
34870
+ }
34227
34871
  let siteId = opts.site ?? ((_a = cfg.hosting) == null ? void 0 : _a.siteId) ?? "";
34228
34872
  let siteName = ((_b = cfg.hosting) == null ? void 0 : _b.siteName) ?? "";
34229
- let previewUrl = "";
34873
+ let previewUrl = ((_c = cfg.hosting) == null ? void 0 : _c.previewUrl) ?? "";
34230
34874
  if (!siteId) {
34231
34875
  const site = await pickOrCreateSite(cfg);
34232
34876
  siteId = site.id;
@@ -34236,12 +34880,10 @@ async function runDeploy(opts) {
34236
34880
  siteId,
34237
34881
  siteName,
34238
34882
  previewUrl,
34239
- distDir: ((_c = cfg.hosting) == null ? void 0 : _c.distDir) ?? "dist",
34240
- entrypoint: ((_d = cfg.hosting) == null ? void 0 : _d.entrypoint) ?? "index.html"
34883
+ distDir: ((_d = cfg.hosting) == null ? void 0 : _d.distDir) ?? "dist",
34884
+ entrypoint: ((_e = cfg.hosting) == null ? void 0 : _e.entrypoint) ?? "index.html"
34241
34885
  };
34242
34886
  saveConfig(cfg, cwd);
34243
- } else {
34244
- previewUrl = ((_e = cfg.hosting) == null ? void 0 : _e.previewUrl) ?? "";
34245
34887
  }
34246
34888
  const distDir = opts.dir ?? ((_f = cfg.hosting) == null ? void 0 : _f.distDir) ?? await promptDistDir(cwd);
34247
34889
  const absDir = import_path3.default.isAbsolute(distDir) ? distDir : import_path3.default.join(cwd, distDir);
@@ -34321,6 +34963,35 @@ async function runDeploy(opts) {
34321
34963
  }
34322
34964
  console.log();
34323
34965
  }
34966
+ async function runFunctionsDeploy(opts = {}) {
34967
+ const cwd = opts.cwd ?? process.cwd();
34968
+ const cfg = requireConfig(cwd);
34969
+ if (!cfg.services.functions) {
34970
+ console.error(source_default.red("\n Functions is not enabled for this project.\n Run `clefbase init` and enable Functions.\n"));
34971
+ process.exit(1);
34972
+ }
34973
+ const deployMjs = import_path3.default.join(cwd, "functions", "deploy.mjs");
34974
+ if (!import_fs4.default.existsSync(deployMjs)) {
34975
+ console.error(source_default.red(`
34976
+ \u2717 functions/deploy.mjs not found.`));
34977
+ console.error(source_default.dim(" Re-run `clefbase init` to regenerate the functions/ folder,"));
34978
+ console.error(source_default.dim(" or deploy individual functions with:"));
34979
+ console.error(source_default.cyan(" clefbase functions:deploy -f functions/src/hello.ts --name hello --trigger http\n"));
34980
+ process.exit(1);
34981
+ }
34982
+ console.log(source_default.bold(" Deploying functions\u2026"));
34983
+ console.log(source_default.dim(` Running: node functions/deploy.mjs
34984
+ `));
34985
+ try {
34986
+ (0, import_child_process2.execSync)("node deploy.mjs", {
34987
+ cwd: import_path3.default.join(cwd, "functions"),
34988
+ stdio: "inherit"
34989
+ });
34990
+ } catch {
34991
+ console.error(source_default.red("\n \u2717 One or more functions failed to deploy.\n"));
34992
+ process.exit(1);
34993
+ }
34994
+ }
34324
34995
  async function runHostingInit(cwd = process.cwd()) {
34325
34996
  var _a, _b;
34326
34997
  const cfg = requireConfig(cwd);
@@ -34647,7 +35318,7 @@ var RUNTIME_BADGE = {
34647
35318
  node: source_default.green("[JS/TS]"),
34648
35319
  python: source_default.blue("[PY] ")
34649
35320
  };
34650
- async function runFunctionsDeploy(opts) {
35321
+ async function runFunctionsDeploy2(opts) {
34651
35322
  const cwd = opts.cwd ?? process.cwd();
34652
35323
  const cfg = requireConfig(cwd);
34653
35324
  console.log();
@@ -34878,7 +35549,7 @@ async function promptRequired(message) {
34878
35549
  }
34879
35550
 
34880
35551
  // package.json
34881
- var version = "2.0.0";
35552
+ var version = "2.0.2";
34882
35553
 
34883
35554
  // src/cli/index.ts
34884
35555
  var program2 = new Command();
@@ -34890,9 +35561,30 @@ program2.command("init").description("Initialise a Clefbase project in the curre
34890
35561
  fatal(err);
34891
35562
  }
34892
35563
  });
34893
- program2.command("deploy").description("Deploy your built site to Clefbase Hosting").option("-d, --dir <path>", "Build output directory (overrides clefbase.json)").option("-s, --site <siteId>", "Site ID to deploy to (overrides clefbase.json)").option("-m, --message <text>", "Deploy message / release note").action(async (opts) => {
35564
+ program2.command("deploy").description("Deploy hosting and/or functions. Deploys both by default if both are enabled.").option("-d, --dir <path>", "Build output directory (overrides clefbase.json)").option("-s, --site <siteId>", "Site ID to deploy to (overrides clefbase.json)").option("-m, --message <text>", "Deploy message / release note").option("--only <target>", "Deploy only one target: hosting | functions").action(async (opts) => {
35565
+ const only = opts.only;
35566
+ if (only && only !== "hosting" && only !== "functions") {
35567
+ console.error(source_default.red(`
35568
+ --only must be "hosting" or "functions" (got: "${only}")
35569
+ `));
35570
+ process.exit(1);
35571
+ }
34894
35572
  try {
34895
- await runDeploy(opts);
35573
+ await runDeploy({ ...opts, only });
35574
+ } catch (err) {
35575
+ fatal(err);
35576
+ }
35577
+ });
35578
+ program2.command("deploy:hosting").description("Deploy your built site to Clefbase Hosting").option("-d, --dir <path>", "Build output directory (overrides clefbase.json)").option("-s, --site <siteId>", "Site ID to deploy to (overrides clefbase.json)").option("-m, --message <text>", "Deploy message / release note").action(async (opts) => {
35579
+ try {
35580
+ await runHostingDeploy(opts);
35581
+ } catch (err) {
35582
+ fatal(err);
35583
+ }
35584
+ });
35585
+ program2.command("deploy:functions").description("Deploy all functions via functions/deploy.mjs").action(async () => {
35586
+ try {
35587
+ await runFunctionsDeploy();
34896
35588
  } catch (err) {
34897
35589
  fatal(err);
34898
35590
  }
@@ -34939,9 +35631,9 @@ program2.command("functions:list").alias("fn:list").description("List all deploy
34939
35631
  fatal(err);
34940
35632
  }
34941
35633
  });
34942
- program2.command("functions:deploy").alias("fn:deploy").description("Deploy (or redeploy) a function from a source file or interactively").option("-n, --name <name>", "Function name").option("-f, --file <path>", "Path to source file (.js, .ts, .py)").option("-r, --runtime <runtime>", "Runtime: node | python (auto-detected from file ext)").option("-t, --trigger <type>", "Trigger type (http, schedule, onDocumentCreate, \u2026)").option("-c, --cron <expr>", "Cron expression for schedule triggers").option("-C, --collection <path>", "Collection path for document triggers").option("-T, --timeout <ms>", "Execution timeout in milliseconds (default: 30000)").option("-e, --entry <name>", "Exported function name to call (default: handler)").option("--env <KEY=VALUE...>", "Environment variable(s) \u2014 repeatable").action(async (opts) => {
35634
+ program2.command("functions:deploy").alias("fn:deploy").description("Deploy (or redeploy) a single function from a source file or interactively").option("-n, --name <name>", "Function name").option("-f, --file <path>", "Path to source file (.js, .ts, .py)").option("-r, --runtime <runtime>", "Runtime: node | python (auto-detected from file ext)").option("-t, --trigger <type>", "Trigger type (http, schedule, onDocumentCreate, \u2026)").option("-c, --cron <expr>", "Cron expression for schedule triggers").option("-C, --collection <path>", "Collection path for document triggers").option("-T, --timeout <ms>", "Execution timeout in milliseconds (default: 30000)").option("-e, --entry <name>", "Exported function name to call (default: handler)").option("--env <KEY=VALUE...>", "Environment variable(s) \u2014 repeatable").action(async (opts) => {
34943
35635
  try {
34944
- await runFunctionsDeploy(opts);
35636
+ await runFunctionsDeploy2(opts);
34945
35637
  } catch (err) {
34946
35638
  fatal(err);
34947
35639
  }
@@ -34980,9 +35672,15 @@ ${source_default.bold("Examples:")}
34980
35672
  ${source_default.cyan("clefbase init")} Set up a new project
34981
35673
  ${source_default.cyan("clefbase info")} Show config & connection status
34982
35674
 
35675
+ ${source_default.bold("Deploy:")}
35676
+ ${source_default.cyan("clefbase deploy")} Deploy functions + hosting (both if enabled)
35677
+ ${source_default.cyan("clefbase deploy --only hosting")} Deploy hosting only
35678
+ ${source_default.cyan("clefbase deploy --only functions")} Deploy all functions only
35679
+ ${source_default.cyan('clefbase deploy -d ./dist -m "v2"')} Deploy hosting from a dir with a note
35680
+ ${source_default.cyan("clefbase deploy:hosting")} Deploy hosting (alias)
35681
+ ${source_default.cyan("clefbase deploy:functions")} Deploy all functions via deploy.mjs (alias)
35682
+
34983
35683
  ${source_default.bold("Hosting:")}
34984
- ${source_default.cyan("clefbase deploy")} Deploy your built site
34985
- ${source_default.cyan('clefbase deploy -d ./dist -m "v2"')} Deploy from a dir with a note
34986
35684
  ${source_default.cyan("clefbase hosting:init")} Link or create a hosted site
34987
35685
  ${source_default.cyan("clefbase hosting:status")} Show current live deploy
34988
35686
  ${source_default.cyan("clefbase hosting:sites")} List all sites
@@ -34991,9 +35689,9 @@ ${source_default.bold("Examples:")}
34991
35689
 
34992
35690
  ${source_default.bold("Functions:")}
34993
35691
  ${source_default.cyan("clefbase functions:list")} List all deployed functions
34994
- ${source_default.cyan("clefbase functions:deploy")} Interactive deploy wizard
34995
- ${source_default.cyan("clefbase functions:deploy -f ./fn.ts")} Deploy from a file (auto-detects runtime)
34996
- ${source_default.cyan("clefbase functions:deploy -n greet -f fn.ts --trigger http")}
35692
+ ${source_default.cyan("clefbase functions:deploy")} Interactive deploy wizard (single function)
35693
+ ${source_default.cyan("clefbase functions:deploy -f functions/src/hello.ts --name hello --trigger http")}
35694
+ ${source_default.cyan('clefbase functions:deploy -f functions/src/scheduled.ts --name daily --trigger schedule --cron "0 0 * * *"')}
34997
35695
  ${source_default.cyan("clefbase functions:call greetUser")} Call an HTTP function
34998
35696
  ${source_default.cyan(`clefbase functions:call greetUser -d '{"name":"Alice"}'}`)}
34999
35697
  ${source_default.cyan("clefbase functions:logs greetUser")} View execution history