clefbase 1.5.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.d.ts +369 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +308 -0
- package/dist/ai.js.map +1 -0
- package/dist/app.d.ts +40 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +48 -3
- package/dist/app.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 +616 -18
- package/dist/cli-src/cli/config.js +0 -1
- package/dist/cli-src/cli/index.js +48 -9
- package/dist/cli.js +728 -57
- 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 +74 -36
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +85 -37
- 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 });
|
|
@@ -34018,10 +34592,9 @@ function buildLibTs(cfg) {
|
|
|
34018
34592
|
`// \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
34593
|
``,
|
|
34020
34594
|
`const app = initClefbase({`,
|
|
34021
|
-
` serverUrl:
|
|
34022
|
-
` projectId:
|
|
34023
|
-
` apiKey:
|
|
34024
|
-
` adminSecret: "",`,
|
|
34595
|
+
` serverUrl: config.serverUrl,`,
|
|
34596
|
+
` projectId: config.projectId,`,
|
|
34597
|
+
` apiKey: config.apiKey,`,
|
|
34025
34598
|
`});`,
|
|
34026
34599
|
``
|
|
34027
34600
|
];
|
|
@@ -34069,7 +34642,6 @@ function buildLibTs(cfg) {
|
|
|
34069
34642
|
return lines.join("\n");
|
|
34070
34643
|
}
|
|
34071
34644
|
async function setupHosting(cfg, cwd) {
|
|
34072
|
-
var _a;
|
|
34073
34645
|
console.log();
|
|
34074
34646
|
console.log(source_default.bold(" Hosting"));
|
|
34075
34647
|
console.log();
|
|
@@ -34086,6 +34658,7 @@ async function setupHosting(cfg, cwd) {
|
|
|
34086
34658
|
}
|
|
34087
34659
|
let siteId = "";
|
|
34088
34660
|
let siteName = "";
|
|
34661
|
+
let previewUrl = "";
|
|
34089
34662
|
if (existing.length > 0) {
|
|
34090
34663
|
const choices = [
|
|
34091
34664
|
...existing.map((s) => ({ name: `${s.name} ${source_default.dim(s.id)}`, value: s.id })),
|
|
@@ -34098,8 +34671,10 @@ async function setupHosting(cfg, cwd) {
|
|
|
34098
34671
|
choices
|
|
34099
34672
|
}]);
|
|
34100
34673
|
if (chosen !== "__new__") {
|
|
34674
|
+
const found = existing.find((s) => s.id === chosen);
|
|
34101
34675
|
siteId = chosen;
|
|
34102
|
-
siteName = (
|
|
34676
|
+
siteName = (found == null ? void 0 : found.name) ?? chosen;
|
|
34677
|
+
previewUrl = (found == null ? void 0 : found.previewUrl) ?? "";
|
|
34103
34678
|
}
|
|
34104
34679
|
}
|
|
34105
34680
|
if (!siteId) {
|
|
@@ -34114,6 +34689,7 @@ async function setupHosting(cfg, cwd) {
|
|
|
34114
34689
|
const site = await createSite(cfg, newName.trim());
|
|
34115
34690
|
siteId = site.id;
|
|
34116
34691
|
siteName = site.name;
|
|
34692
|
+
previewUrl = site.previewUrl ?? "";
|
|
34117
34693
|
s.succeed(source_default.green(`Created "${siteName}" ${source_default.dim(siteId)}`));
|
|
34118
34694
|
} catch (err) {
|
|
34119
34695
|
s.fail(`Failed: ${err.message}`);
|
|
@@ -34137,6 +34713,7 @@ async function setupHosting(cfg, cwd) {
|
|
|
34137
34713
|
cfg.hosting = {
|
|
34138
34714
|
siteId,
|
|
34139
34715
|
siteName,
|
|
34716
|
+
previewUrl,
|
|
34140
34717
|
distDir: distDir.trim(),
|
|
34141
34718
|
entrypoint: entrypoint.trim()
|
|
34142
34719
|
};
|
|
@@ -34175,12 +34752,15 @@ function printUsageHint(cfg) {
|
|
|
34175
34752
|
if (cfg.services.functions) {
|
|
34176
34753
|
console.log();
|
|
34177
34754
|
console.log(source_default.bold(" Functions:"));
|
|
34178
|
-
console.log(source_default.cyan(`
|
|
34179
|
-
console.log(source_default.cyan(` $ clefbase functions:deploy -f
|
|
34755
|
+
console.log(source_default.cyan(` # Edit your functions in functions/src/`));
|
|
34756
|
+
console.log(source_default.cyan(` $ clefbase functions:deploy -f functions/src/hello.ts`));
|
|
34180
34757
|
console.log();
|
|
34181
|
-
console.log(source_default.cyan(`
|
|
34758
|
+
console.log(source_default.cyan(` # Call from your app`));
|
|
34182
34759
|
console.log(source_default.cyan(` const greet = httpsCallable(fns, "greetUser");`));
|
|
34183
34760
|
console.log(source_default.cyan(` const { data } = await greet({ name: "Alice" });`));
|
|
34761
|
+
console.log();
|
|
34762
|
+
console.log(source_default.dim(` Full guide: functions/README.md`));
|
|
34763
|
+
console.log(source_default.dim(` Deploy all: node functions/deploy.mjs`));
|
|
34184
34764
|
}
|
|
34185
34765
|
if (cfg.services.hosting && cfg.hosting) {
|
|
34186
34766
|
console.log();
|
|
@@ -34194,6 +34774,7 @@ function printUsageHint(cfg) {
|
|
|
34194
34774
|
// src/cli/commands/deploy.ts
|
|
34195
34775
|
var import_path3 = __toESM(require("path"));
|
|
34196
34776
|
var import_fs4 = __toESM(require("fs"));
|
|
34777
|
+
var import_child_process2 = require("child_process");
|
|
34197
34778
|
var SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", ".gitkeep", ".gitignore"]);
|
|
34198
34779
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svn", ".hg"]);
|
|
34199
34780
|
function scanDir(dir, base2, out) {
|
|
@@ -34218,15 +34799,51 @@ function fmtBytes(n) {
|
|
|
34218
34799
|
return `${(n / (1024 * 1024)).toFixed(2)} MB`;
|
|
34219
34800
|
}
|
|
34220
34801
|
async function runDeploy(opts) {
|
|
34221
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
34222
34802
|
const cwd = opts.cwd ?? process.cwd();
|
|
34223
34803
|
const cfg = requireConfig(cwd);
|
|
34804
|
+
const deployHosting = !opts.only || opts.only === "hosting";
|
|
34805
|
+
const deployFunctions = !opts.only || opts.only === "functions";
|
|
34806
|
+
if (deployHosting && !cfg.services.hosting && opts.only === "hosting") {
|
|
34807
|
+
console.error(source_default.red("\n Hosting is not enabled for this project.\n Run `clefbase hosting:init` to set it up.\n"));
|
|
34808
|
+
process.exit(1);
|
|
34809
|
+
}
|
|
34810
|
+
if (deployFunctions && !cfg.services.functions && opts.only === "functions") {
|
|
34811
|
+
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"));
|
|
34812
|
+
process.exit(1);
|
|
34813
|
+
}
|
|
34224
34814
|
console.log();
|
|
34225
|
-
|
|
34815
|
+
if (opts.only === "hosting") {
|
|
34816
|
+
console.log(source_default.bold.cyan(" Clefbase Deploy \xB7 Hosting"));
|
|
34817
|
+
} else if (opts.only === "functions") {
|
|
34818
|
+
console.log(source_default.bold.cyan(" Clefbase Deploy \xB7 Functions"));
|
|
34819
|
+
} else {
|
|
34820
|
+
console.log(source_default.bold.cyan(" Clefbase Deploy"));
|
|
34821
|
+
}
|
|
34226
34822
|
console.log();
|
|
34823
|
+
if (deployFunctions && cfg.services.functions) {
|
|
34824
|
+
await runFunctionsDeploy({ cwd });
|
|
34825
|
+
if (deployHosting && cfg.services.hosting) {
|
|
34826
|
+
console.log();
|
|
34827
|
+
}
|
|
34828
|
+
}
|
|
34829
|
+
if (deployHosting && cfg.services.hosting) {
|
|
34830
|
+
await runHostingDeploy({ dir: opts.dir, message: opts.message, site: opts.site, cwd });
|
|
34831
|
+
}
|
|
34832
|
+
if (!deployHosting && !deployFunctions) {
|
|
34833
|
+
console.log(source_default.yellow(" Nothing to deploy \u2014 no services matched.\n"));
|
|
34834
|
+
}
|
|
34835
|
+
}
|
|
34836
|
+
async function runHostingDeploy(opts) {
|
|
34837
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
34838
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
34839
|
+
const cfg = requireConfig(cwd);
|
|
34840
|
+
if (!cfg.services.hosting) {
|
|
34841
|
+
console.error(source_default.red("\n Hosting is not enabled for this project.\n Run `clefbase hosting:init` to set it up.\n"));
|
|
34842
|
+
process.exit(1);
|
|
34843
|
+
}
|
|
34227
34844
|
let siteId = opts.site ?? ((_a = cfg.hosting) == null ? void 0 : _a.siteId) ?? "";
|
|
34228
34845
|
let siteName = ((_b = cfg.hosting) == null ? void 0 : _b.siteName) ?? "";
|
|
34229
|
-
let previewUrl = "";
|
|
34846
|
+
let previewUrl = ((_c = cfg.hosting) == null ? void 0 : _c.previewUrl) ?? "";
|
|
34230
34847
|
if (!siteId) {
|
|
34231
34848
|
const site = await pickOrCreateSite(cfg);
|
|
34232
34849
|
siteId = site.id;
|
|
@@ -34236,12 +34853,10 @@ async function runDeploy(opts) {
|
|
|
34236
34853
|
siteId,
|
|
34237
34854
|
siteName,
|
|
34238
34855
|
previewUrl,
|
|
34239
|
-
distDir: ((
|
|
34240
|
-
entrypoint: ((
|
|
34856
|
+
distDir: ((_d = cfg.hosting) == null ? void 0 : _d.distDir) ?? "dist",
|
|
34857
|
+
entrypoint: ((_e = cfg.hosting) == null ? void 0 : _e.entrypoint) ?? "index.html"
|
|
34241
34858
|
};
|
|
34242
34859
|
saveConfig(cfg, cwd);
|
|
34243
|
-
} else {
|
|
34244
|
-
previewUrl = ((_e = cfg.hosting) == null ? void 0 : _e.previewUrl) ?? "";
|
|
34245
34860
|
}
|
|
34246
34861
|
const distDir = opts.dir ?? ((_f = cfg.hosting) == null ? void 0 : _f.distDir) ?? await promptDistDir(cwd);
|
|
34247
34862
|
const absDir = import_path3.default.isAbsolute(distDir) ? distDir : import_path3.default.join(cwd, distDir);
|
|
@@ -34321,6 +34936,35 @@ async function runDeploy(opts) {
|
|
|
34321
34936
|
}
|
|
34322
34937
|
console.log();
|
|
34323
34938
|
}
|
|
34939
|
+
async function runFunctionsDeploy(opts = {}) {
|
|
34940
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
34941
|
+
const cfg = requireConfig(cwd);
|
|
34942
|
+
if (!cfg.services.functions) {
|
|
34943
|
+
console.error(source_default.red("\n Functions is not enabled for this project.\n Run `clefbase init` and enable Functions.\n"));
|
|
34944
|
+
process.exit(1);
|
|
34945
|
+
}
|
|
34946
|
+
const deployMjs = import_path3.default.join(cwd, "functions", "deploy.mjs");
|
|
34947
|
+
if (!import_fs4.default.existsSync(deployMjs)) {
|
|
34948
|
+
console.error(source_default.red(`
|
|
34949
|
+
\u2717 functions/deploy.mjs not found.`));
|
|
34950
|
+
console.error(source_default.dim(" Re-run `clefbase init` to regenerate the functions/ folder,"));
|
|
34951
|
+
console.error(source_default.dim(" or deploy individual functions with:"));
|
|
34952
|
+
console.error(source_default.cyan(" clefbase functions:deploy -f functions/src/hello.ts --name hello --trigger http\n"));
|
|
34953
|
+
process.exit(1);
|
|
34954
|
+
}
|
|
34955
|
+
console.log(source_default.bold(" Deploying functions\u2026"));
|
|
34956
|
+
console.log(source_default.dim(` Running: node functions/deploy.mjs
|
|
34957
|
+
`));
|
|
34958
|
+
try {
|
|
34959
|
+
(0, import_child_process2.execSync)("node deploy.mjs", {
|
|
34960
|
+
cwd: import_path3.default.join(cwd, "functions"),
|
|
34961
|
+
stdio: "inherit"
|
|
34962
|
+
});
|
|
34963
|
+
} catch {
|
|
34964
|
+
console.error(source_default.red("\n \u2717 One or more functions failed to deploy.\n"));
|
|
34965
|
+
process.exit(1);
|
|
34966
|
+
}
|
|
34967
|
+
}
|
|
34324
34968
|
async function runHostingInit(cwd = process.cwd()) {
|
|
34325
34969
|
var _a, _b;
|
|
34326
34970
|
const cfg = requireConfig(cwd);
|
|
@@ -34647,7 +35291,7 @@ var RUNTIME_BADGE = {
|
|
|
34647
35291
|
node: source_default.green("[JS/TS]"),
|
|
34648
35292
|
python: source_default.blue("[PY] ")
|
|
34649
35293
|
};
|
|
34650
|
-
async function
|
|
35294
|
+
async function runFunctionsDeploy2(opts) {
|
|
34651
35295
|
const cwd = opts.cwd ?? process.cwd();
|
|
34652
35296
|
const cfg = requireConfig(cwd);
|
|
34653
35297
|
console.log();
|
|
@@ -34878,7 +35522,7 @@ async function promptRequired(message) {
|
|
|
34878
35522
|
}
|
|
34879
35523
|
|
|
34880
35524
|
// package.json
|
|
34881
|
-
var version = "
|
|
35525
|
+
var version = "2.0.1";
|
|
34882
35526
|
|
|
34883
35527
|
// src/cli/index.ts
|
|
34884
35528
|
var program2 = new Command();
|
|
@@ -34890,9 +35534,30 @@ program2.command("init").description("Initialise a Clefbase project in the curre
|
|
|
34890
35534
|
fatal(err);
|
|
34891
35535
|
}
|
|
34892
35536
|
});
|
|
34893
|
-
program2.command("deploy").description("Deploy
|
|
35537
|
+
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) => {
|
|
35538
|
+
const only = opts.only;
|
|
35539
|
+
if (only && only !== "hosting" && only !== "functions") {
|
|
35540
|
+
console.error(source_default.red(`
|
|
35541
|
+
--only must be "hosting" or "functions" (got: "${only}")
|
|
35542
|
+
`));
|
|
35543
|
+
process.exit(1);
|
|
35544
|
+
}
|
|
35545
|
+
try {
|
|
35546
|
+
await runDeploy({ ...opts, only });
|
|
35547
|
+
} catch (err) {
|
|
35548
|
+
fatal(err);
|
|
35549
|
+
}
|
|
35550
|
+
});
|
|
35551
|
+
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) => {
|
|
35552
|
+
try {
|
|
35553
|
+
await runHostingDeploy(opts);
|
|
35554
|
+
} catch (err) {
|
|
35555
|
+
fatal(err);
|
|
35556
|
+
}
|
|
35557
|
+
});
|
|
35558
|
+
program2.command("deploy:functions").description("Deploy all functions via functions/deploy.mjs").action(async () => {
|
|
34894
35559
|
try {
|
|
34895
|
-
await
|
|
35560
|
+
await runFunctionsDeploy();
|
|
34896
35561
|
} catch (err) {
|
|
34897
35562
|
fatal(err);
|
|
34898
35563
|
}
|
|
@@ -34939,9 +35604,9 @@ program2.command("functions:list").alias("fn:list").description("List all deploy
|
|
|
34939
35604
|
fatal(err);
|
|
34940
35605
|
}
|
|
34941
35606
|
});
|
|
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) => {
|
|
35607
|
+
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
35608
|
try {
|
|
34944
|
-
await
|
|
35609
|
+
await runFunctionsDeploy2(opts);
|
|
34945
35610
|
} catch (err) {
|
|
34946
35611
|
fatal(err);
|
|
34947
35612
|
}
|
|
@@ -34980,9 +35645,15 @@ ${source_default.bold("Examples:")}
|
|
|
34980
35645
|
${source_default.cyan("clefbase init")} Set up a new project
|
|
34981
35646
|
${source_default.cyan("clefbase info")} Show config & connection status
|
|
34982
35647
|
|
|
35648
|
+
${source_default.bold("Deploy:")}
|
|
35649
|
+
${source_default.cyan("clefbase deploy")} Deploy functions + hosting (both if enabled)
|
|
35650
|
+
${source_default.cyan("clefbase deploy --only hosting")} Deploy hosting only
|
|
35651
|
+
${source_default.cyan("clefbase deploy --only functions")} Deploy all functions only
|
|
35652
|
+
${source_default.cyan('clefbase deploy -d ./dist -m "v2"')} Deploy hosting from a dir with a note
|
|
35653
|
+
${source_default.cyan("clefbase deploy:hosting")} Deploy hosting (alias)
|
|
35654
|
+
${source_default.cyan("clefbase deploy:functions")} Deploy all functions via deploy.mjs (alias)
|
|
35655
|
+
|
|
34983
35656
|
${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
35657
|
${source_default.cyan("clefbase hosting:init")} Link or create a hosted site
|
|
34987
35658
|
${source_default.cyan("clefbase hosting:status")} Show current live deploy
|
|
34988
35659
|
${source_default.cyan("clefbase hosting:sites")} List all sites
|
|
@@ -34991,9 +35662,9 @@ ${source_default.bold("Examples:")}
|
|
|
34991
35662
|
|
|
34992
35663
|
${source_default.bold("Functions:")}
|
|
34993
35664
|
${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(
|
|
35665
|
+
${source_default.cyan("clefbase functions:deploy")} Interactive deploy wizard (single function)
|
|
35666
|
+
${source_default.cyan("clefbase functions:deploy -f functions/src/hello.ts --name hello --trigger http")}
|
|
35667
|
+
${source_default.cyan('clefbase functions:deploy -f functions/src/scheduled.ts --name daily --trigger schedule --cron "0 0 * * *"')}
|
|
34997
35668
|
${source_default.cyan("clefbase functions:call greetUser")} Call an HTTP function
|
|
34998
35669
|
${source_default.cyan(`clefbase functions:call greetUser -d '{"name":"Alice"}'}`)}
|
|
34999
35670
|
${source_default.cyan("clefbase functions:logs greetUser")} View execution history
|