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/app.d.ts +6 -26
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +12 -31
- package/dist/app.js.map +1 -1
- package/dist/auth/index.d.ts +63 -114
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +97 -176
- package/dist/auth/index.js.map +1 -1
- package/dist/cli-src/cli/api.js +14 -14
- package/dist/cli-src/cli/commands/deploy.js +84 -16
- package/dist/cli-src/cli/commands/init.js +644 -19
- package/dist/cli-src/cli/config.js +0 -1
- package/dist/cli-src/cli/index.js +48 -9
- package/dist/cli.js +756 -58
- package/dist/hosting/index.d.ts +8 -98
- package/dist/hosting/index.d.ts.map +1 -1
- package/dist/hosting/index.js +37 -95
- package/dist/hosting/index.js.map +1 -1
- package/dist/index.d.ts +14 -60
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -65
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +0 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
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)}/
|
|
33733
|
-
{ headers:
|
|
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)}/
|
|
33739
|
-
{ method: "POST", headers:
|
|
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)}/
|
|
33745
|
-
{ headers:
|
|
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)}/
|
|
33751
|
-
{ method: "POST", headers:
|
|
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)}/
|
|
33752
|
+
`${base(cfg)}/hosting/sites/${siteId}/deploys`,
|
|
33757
33753
|
{
|
|
33758
33754
|
method: "POST",
|
|
33759
|
-
headers:
|
|
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)}/
|
|
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-
|
|
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-
|
|
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)}/
|
|
33808
|
+
`${base(cfg)}/hosting/deploys/${deployId}/finalize`,
|
|
33813
33809
|
{
|
|
33814
33810
|
method: "POST",
|
|
33815
|
-
headers:
|
|
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)}/
|
|
33824
|
-
{ headers:
|
|
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 ? [
|
|
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:
|
|
34022
|
-
` projectId:
|
|
34023
|
-
` 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 = (
|
|
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(`
|
|
34179
|
-
console.log(source_default.cyan(` $ clefbase functions:deploy -f
|
|
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(`
|
|
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
|
-
|
|
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: ((
|
|
34240
|
-
entrypoint: ((
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
34996
|
-
${source_default.cyan(
|
|
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
|