clefbase 1.4.1 → 1.5.0
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 +19 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +25 -1
- package/dist/app.js.map +1 -1
- package/dist/cli-src/cli/api.js +33 -11
- package/dist/cli-src/cli/commands/functions.js +331 -0
- package/dist/cli-src/cli/commands/init.js +53 -16
- package/dist/cli-src/cli/config.js +7 -2
- package/dist/cli-src/cli/index.js +102 -11
- package/dist/cli.js +469 -66
- package/dist/functions.d.ts +94 -0
- package/dist/functions.d.ts.map +1 -0
- package/dist/functions.js +116 -0
- package/dist/functions.js.map +1 -0
- package/dist/index.d.ts +28 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -19
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +164 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
package/dist/app.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Database } from "./db";
|
|
|
3
3
|
import { Auth } from "./auth";
|
|
4
4
|
import { ClefbaseStorage } from "./storage";
|
|
5
5
|
import { ClefbaseHosting } from "./hosting";
|
|
6
|
+
import { ClefbaseFunctions } from "./functions";
|
|
6
7
|
import type { ClefbaseConfig } from "./types";
|
|
7
8
|
/**
|
|
8
9
|
* A Clefbase application instance.
|
|
@@ -14,10 +15,12 @@ export declare class ClefbaseApp {
|
|
|
14
15
|
/** @internal */ readonly _authHttp: HttpClient;
|
|
15
16
|
/** @internal */ readonly _storageHttp: HttpClient;
|
|
16
17
|
/** @internal */ readonly _hostingHttp: HttpClient;
|
|
18
|
+
/** @internal */ readonly _functionsHttp: HttpClient;
|
|
17
19
|
private _db;
|
|
18
20
|
private _auth;
|
|
19
21
|
private _storage;
|
|
20
22
|
private _hosting;
|
|
23
|
+
private _functions;
|
|
21
24
|
constructor(config: ClefbaseConfig);
|
|
22
25
|
/** @internal */
|
|
23
26
|
_getDb(): Database;
|
|
@@ -27,6 +30,8 @@ export declare class ClefbaseApp {
|
|
|
27
30
|
_getStorage(): ClefbaseStorage;
|
|
28
31
|
/** @internal */
|
|
29
32
|
_getHosting(): ClefbaseHosting;
|
|
33
|
+
/** @internal */
|
|
34
|
+
_getFunctions(): ClefbaseFunctions;
|
|
30
35
|
}
|
|
31
36
|
/**
|
|
32
37
|
* Initialise a Clefbase app. Call once at startup.
|
|
@@ -37,7 +42,7 @@ export declare class ClefbaseApp {
|
|
|
37
42
|
* serverUrl: "https://api.cleforyx.com",
|
|
38
43
|
* projectId: "my_project_abc",
|
|
39
44
|
* apiKey: "cfx_...",
|
|
40
|
-
* adminSecret: "
|
|
45
|
+
* adminSecret: "", // only needed for hosting
|
|
41
46
|
* });
|
|
42
47
|
*/
|
|
43
48
|
export declare function initClefbase(config: ClefbaseConfig, name?: string): ClefbaseApp;
|
|
@@ -51,4 +56,17 @@ export declare function getAuth(app?: ClefbaseApp): Auth;
|
|
|
51
56
|
export declare function getStorage(app?: ClefbaseApp): ClefbaseStorage;
|
|
52
57
|
/** Get the Hosting service. Requires `adminSecret` in config. */
|
|
53
58
|
export declare function getHosting(app?: ClefbaseApp): ClefbaseHosting;
|
|
59
|
+
/**
|
|
60
|
+
* Get the Functions service.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* import { initClefbase, getFunctions, httpsCallable } from "clefbase";
|
|
64
|
+
*
|
|
65
|
+
* const app = initClefbase({ serverUrl, projectId, apiKey, adminSecret: "" });
|
|
66
|
+
* const fns = getFunctions(app);
|
|
67
|
+
*
|
|
68
|
+
* const greet = httpsCallable<{ name: string }, { message: string }>(fns, "greetUser");
|
|
69
|
+
* const { data } = await greet({ name: "Alice" });
|
|
70
|
+
*/
|
|
71
|
+
export declare function getFunctions(app?: ClefbaseApp): ClefbaseFunctions;
|
|
54
72
|
//# sourceMappingURL=app.d.ts.map
|
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAI9C;;;GAGG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEhC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAI9C;;;GAGG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEhC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,EAAS,UAAU,CAAC;IACrD,gBAAgB,CAAC,QAAQ,CAAC,SAAS,EAAO,UAAU,CAAC;IACrD,gBAAgB,CAAC,QAAQ,CAAC,YAAY,EAAI,UAAU,CAAC;IACrD,gBAAgB,CAAC,QAAQ,CAAC,YAAY,EAAI,UAAU,CAAC;IACrD,gBAAgB,CAAC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC;IAErD,OAAO,CAAC,GAAG,CAAuC;IAClD,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,UAAU,CAAgC;gBAEtC,MAAM,EAAE,cAAc;IAalC,gBAAgB;IAChB,MAAM,IAAI,QAAQ;IAKlB,gBAAgB;IAChB,QAAQ,IAAI,IAAI;IAKhB,gBAAgB;IAChB,WAAW,IAAI,eAAe;IAU9B,gBAAgB;IAChB,WAAW,IAAI,eAAe;IAU9B,gBAAgB;IAChB,aAAa,IAAI,iBAAiB;CAKnC;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,cAAc,EACtB,IAAI,GAAE,MAAgB,GACrB,WAAW,CAKb;AAED,wEAAwE;AACxE,wBAAgB,MAAM,CAAC,IAAI,GAAE,MAAgB,GAAG,WAAW,CAQ1D;AAID,gCAAgC;AAChC,wBAAgB,WAAW,CAAC,GAAG,CAAC,EAAE,WAAW,GAAG,QAAQ,CAEvD;AAED,4BAA4B;AAC5B,wBAAgB,OAAO,CAAC,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAE/C;AAED,+BAA+B;AAC/B,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,WAAW,GAAG,eAAe,CAE7D;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,WAAW,GAAG,eAAe,CAE7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAEjE"}
|
package/dist/app.js
CHANGED
|
@@ -7,11 +7,13 @@ exports.getDatabase = getDatabase;
|
|
|
7
7
|
exports.getAuth = getAuth;
|
|
8
8
|
exports.getStorage = getStorage;
|
|
9
9
|
exports.getHosting = getHosting;
|
|
10
|
+
exports.getFunctions = getFunctions;
|
|
10
11
|
const http_1 = require("./http");
|
|
11
12
|
const db_1 = require("./db");
|
|
12
13
|
const auth_1 = require("./auth");
|
|
13
14
|
const storage_1 = require("./storage");
|
|
14
15
|
const hosting_1 = require("./hosting");
|
|
16
|
+
const functions_1 = require("./functions");
|
|
15
17
|
// ─── App ──────────────────────────────────────────────────────────────────────
|
|
16
18
|
/**
|
|
17
19
|
* A Clefbase application instance.
|
|
@@ -27,6 +29,7 @@ class ClefbaseApp {
|
|
|
27
29
|
this._authHttp = new http_1.HttpClient(`${base}/auth`, cfxKey);
|
|
28
30
|
this._storageHttp = new http_1.HttpClient(`${base}/storage`, cfxKey);
|
|
29
31
|
this._hostingHttp = new http_1.HttpClient(`${base}/api/hosting`, admin);
|
|
32
|
+
this._functionsHttp = new http_1.HttpClient(`${base}/functions`, cfxKey);
|
|
30
33
|
}
|
|
31
34
|
/** @internal */
|
|
32
35
|
_getDb() {
|
|
@@ -52,6 +55,12 @@ class ClefbaseApp {
|
|
|
52
55
|
this._hosting = new hosting_1.ClefbaseHosting(this._hostingHttp, this.config.projectId, this.config.serverUrl);
|
|
53
56
|
return this._hosting;
|
|
54
57
|
}
|
|
58
|
+
/** @internal */
|
|
59
|
+
_getFunctions() {
|
|
60
|
+
if (!this._functions)
|
|
61
|
+
this._functions = new functions_1.ClefbaseFunctions(this._functionsHttp);
|
|
62
|
+
return this._functions;
|
|
63
|
+
}
|
|
55
64
|
}
|
|
56
65
|
exports.ClefbaseApp = ClefbaseApp;
|
|
57
66
|
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
@@ -66,7 +75,7 @@ const registry = new Map();
|
|
|
66
75
|
* serverUrl: "https://api.cleforyx.com",
|
|
67
76
|
* projectId: "my_project_abc",
|
|
68
77
|
* apiKey: "cfx_...",
|
|
69
|
-
* adminSecret: "
|
|
78
|
+
* adminSecret: "", // only needed for hosting
|
|
70
79
|
* });
|
|
71
80
|
*/
|
|
72
81
|
function initClefbase(config, name = DEFAULT) {
|
|
@@ -101,4 +110,19 @@ function getStorage(app) {
|
|
|
101
110
|
function getHosting(app) {
|
|
102
111
|
return (app ?? getApp())._getHosting();
|
|
103
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Get the Functions service.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* import { initClefbase, getFunctions, httpsCallable } from "clefbase";
|
|
118
|
+
*
|
|
119
|
+
* const app = initClefbase({ serverUrl, projectId, apiKey, adminSecret: "" });
|
|
120
|
+
* const fns = getFunctions(app);
|
|
121
|
+
*
|
|
122
|
+
* const greet = httpsCallable<{ name: string }, { message: string }>(fns, "greetUser");
|
|
123
|
+
* const { data } = await greet({ name: "Alice" });
|
|
124
|
+
*/
|
|
125
|
+
function getFunctions(app) {
|
|
126
|
+
return (app ?? getApp())._getFunctions();
|
|
127
|
+
}
|
|
104
128
|
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":";;;AAqGA,oCAQC;AAGD,wBAQC;AAKD,kCAEC;AAGD,0BAEC;AAGD,gCAEC;AAGD,gCAEC;AAcD,oCAEC;AA9JD,iCAAoC;AACpC,6BAAgC;AAChC,iCAA8B;AAC9B,uCAA4C;AAC5C,uCAA4C;AAC5C,2CAAgD;AAGhD,iFAAiF;AAEjF;;;GAGG;AACH,MAAa,WAAW;IAetB,YAAY,MAAsB;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,MAAM,IAAI,GAAK,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,EAAE,WAAW,EAAO,MAAM,CAAC,MAAM,EAAE,CAAC;QACnD,MAAM,KAAK,GAAI,EAAE,gBAAgB,EAAE,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;QAE9D,IAAI,CAAC,OAAO,GAAU,IAAI,iBAAU,CAAC,GAAG,IAAI,KAAK,EAAW,MAAM,CAAC,CAAC;QACpE,IAAI,CAAC,SAAS,GAAQ,IAAI,iBAAU,CAAC,GAAG,IAAI,OAAO,EAAS,MAAM,CAAC,CAAC;QACpE,IAAI,CAAC,YAAY,GAAK,IAAI,iBAAU,CAAC,GAAG,IAAI,UAAU,EAAM,MAAM,CAAC,CAAC;QACpE,IAAI,CAAC,YAAY,GAAK,IAAI,iBAAU,CAAC,GAAG,IAAI,cAAc,EAAE,KAAK,CAAC,CAAC;QACnE,IAAI,CAAC,cAAc,GAAG,IAAI,iBAAU,CAAC,GAAG,IAAI,YAAY,EAAI,MAAM,CAAC,CAAC;IACtE,CAAC;IAED,gBAAgB;IAChB,MAAM;QACJ,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,GAAG,GAAG,IAAI,aAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;IAED,gBAAgB;IAChB,QAAQ;QACN,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,GAAG,IAAI,WAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,gBAAgB;IAChB,WAAW;QACT,IAAI,CAAC,IAAI,CAAC,QAAQ;YAChB,IAAI,CAAC,QAAQ,GAAG,IAAI,yBAAe,CACjC,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,MAAM,CAAC,SAAS,EACrB,IAAI,CAAC,QAAQ,EAAE,CAChB,CAAC;QACJ,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,gBAAgB;IAChB,WAAW;QACT,IAAI,CAAC,IAAI,CAAC,QAAQ;YAChB,IAAI,CAAC,QAAQ,GAAG,IAAI,yBAAe,CACjC,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,MAAM,CAAC,SAAS,EACrB,IAAI,CAAC,MAAM,CAAC,SAAS,CACtB,CAAC;QACJ,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,gBAAgB;IAChB,aAAa;QACX,IAAI,CAAC,IAAI,CAAC,UAAU;YAClB,IAAI,CAAC,UAAU,GAAG,IAAI,6BAAiB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;CACF;AApED,kCAoEC;AAED,iFAAiF;AAEjF,MAAM,OAAO,GAAG,WAAW,CAAC;AAC5B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,SAAgB,YAAY,CAC1B,MAAsB,EACtB,OAAe,OAAO;IAEtB,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACxB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,wEAAwE;AACxE,SAAgB,MAAM,CAAC,OAAe,OAAO;IAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,wDAAwD,CAC9E,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AAEjF,gCAAgC;AAChC,SAAgB,WAAW,CAAC,GAAiB;IAC3C,OAAO,CAAC,GAAG,IAAI,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AACpC,CAAC;AAED,4BAA4B;AAC5B,SAAgB,OAAO,CAAC,GAAiB;IACvC,OAAO,CAAC,GAAG,IAAI,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;AACtC,CAAC;AAED,+BAA+B;AAC/B,SAAgB,UAAU,CAAC,GAAiB;IAC1C,OAAO,CAAC,GAAG,IAAI,MAAM,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACzC,CAAC;AAED,iEAAiE;AACjE,SAAgB,UAAU,CAAC,GAAiB;IAC1C,OAAO,CAAC,GAAG,IAAI,MAAM,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,YAAY,CAAC,GAAiB;IAC5C,OAAO,CAAC,GAAG,IAAI,MAAM,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC;AAC3C,CAAC"}
|
package/dist/cli-src/cli/api.js
CHANGED
|
@@ -48,6 +48,11 @@ exports.createDeploy = createDeploy;
|
|
|
48
48
|
exports.uploadFileBatch = uploadFileBatch;
|
|
49
49
|
exports.finalizeDeploy = finalizeDeploy;
|
|
50
50
|
exports.getActiveDeploy = getActiveDeploy;
|
|
51
|
+
exports.apiFnList = apiFnList;
|
|
52
|
+
exports.apiFnDeploy = apiFnDeploy;
|
|
53
|
+
exports.apiFnDelete = apiFnDelete;
|
|
54
|
+
exports.apiFnCall = apiFnCall;
|
|
55
|
+
exports.apiFnExecutions = apiFnExecutions;
|
|
51
56
|
exports.testConnection = testConnection;
|
|
52
57
|
// ─── Error ────────────────────────────────────────────────────────────────────
|
|
53
58
|
class CliApiError extends Error {
|
|
@@ -88,23 +93,16 @@ function base(cfg) {
|
|
|
88
93
|
function adminHeaders(cfg) {
|
|
89
94
|
return { "Content-Type": "application/json", "x-admin-secret": cfg.adminSecret };
|
|
90
95
|
}
|
|
96
|
+
function cfxHeaders(cfg) {
|
|
97
|
+
return { "Content-Type": "application/json", "x-cfx-key": cfg.apiKey };
|
|
98
|
+
}
|
|
91
99
|
// ─── Hosting API ──────────────────────────────────────────────────────────────
|
|
92
100
|
async function listSites(cfg) {
|
|
93
101
|
return apiFetch(`${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites`, { headers: adminHeaders(cfg) });
|
|
94
102
|
}
|
|
95
103
|
async function createSite(cfg, name, description) {
|
|
96
|
-
return apiFetch(`${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites`, {
|
|
97
|
-
method: "POST",
|
|
98
|
-
headers: adminHeaders(cfg),
|
|
99
|
-
body: JSON.stringify({ name, description }),
|
|
100
|
-
});
|
|
104
|
+
return apiFetch(`${base(cfg)}/api/hosting/databases/${cfg.projectId}/sites`, { method: "POST", headers: adminHeaders(cfg), body: JSON.stringify({ name, description }) });
|
|
101
105
|
}
|
|
102
|
-
/**
|
|
103
|
-
* Delete a site.
|
|
104
|
-
*
|
|
105
|
-
* First call (confirm=false): returns { requiresConfirmation: true, dnsRecord?, message }
|
|
106
|
-
* Second call (confirm=true, deleteDns?): actually deletes
|
|
107
|
-
*/
|
|
108
106
|
async function deleteSite(cfg, siteId, opts = {}) {
|
|
109
107
|
const params = new URLSearchParams();
|
|
110
108
|
if (opts.confirm)
|
|
@@ -192,6 +190,30 @@ async function getActiveDeploy(cfg, siteId) {
|
|
|
192
190
|
throw err;
|
|
193
191
|
}
|
|
194
192
|
}
|
|
193
|
+
// ─── Functions API ────────────────────────────────────────────────────────────
|
|
194
|
+
//
|
|
195
|
+
// All functions routes use the project API key (x-cfx-key), not adminSecret.
|
|
196
|
+
// Base path: /functions
|
|
197
|
+
/** List all deployed functions for the linked project */
|
|
198
|
+
async function apiFnList(cfg) {
|
|
199
|
+
return apiFetch(`${base(cfg)}/functions/`, { headers: cfxHeaders(cfg) });
|
|
200
|
+
}
|
|
201
|
+
/** Deploy (or redeploy) a function from source code */
|
|
202
|
+
async function apiFnDeploy(cfg, options) {
|
|
203
|
+
return apiFetch(`${base(cfg)}/functions/deploy`, { method: "POST", headers: cfxHeaders(cfg), body: JSON.stringify(options) });
|
|
204
|
+
}
|
|
205
|
+
/** Delete a function by name */
|
|
206
|
+
async function apiFnDelete(cfg, name) {
|
|
207
|
+
return apiFetch(`${base(cfg)}/functions/${encodeURIComponent(name)}`, { method: "DELETE", headers: cfxHeaders(cfg) });
|
|
208
|
+
}
|
|
209
|
+
/** Call an HTTP function with a JSON payload. Returns the function's return value + durationMs */
|
|
210
|
+
async function apiFnCall(cfg, name, data) {
|
|
211
|
+
return apiFetch(`${base(cfg)}/functions/call/${encodeURIComponent(name)}`, { method: "POST", headers: cfxHeaders(cfg), body: JSON.stringify({ data: data ?? null }) });
|
|
212
|
+
}
|
|
213
|
+
/** Fetch execution history for a function (or all functions in the project) */
|
|
214
|
+
async function apiFnExecutions(cfg, name, limit = 30) {
|
|
215
|
+
return apiFetch(`${base(cfg)}/functions/${encodeURIComponent(name)}/executions?limit=${Math.min(limit, 100)}`, { headers: cfxHeaders(cfg) });
|
|
216
|
+
}
|
|
195
217
|
// ─── Connectivity ─────────────────────────────────────────────────────────────
|
|
196
218
|
async function testConnection(cfg) {
|
|
197
219
|
try {
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runFunctionsList = runFunctionsList;
|
|
7
|
+
exports.runFunctionsDeploy = runFunctionsDeploy;
|
|
8
|
+
exports.runFunctionsCall = runFunctionsCall;
|
|
9
|
+
exports.runFunctionsDelete = runFunctionsDelete;
|
|
10
|
+
exports.runFunctionsLogs = runFunctionsLogs;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const ora_1 = __importDefault(require("ora"));
|
|
15
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
16
|
+
const config_1 = require("../config");
|
|
17
|
+
const api_1 = require("../api");
|
|
18
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
19
|
+
function fmtDuration(ms) {
|
|
20
|
+
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(2)}s`;
|
|
21
|
+
}
|
|
22
|
+
function fmtDate(iso) {
|
|
23
|
+
return new Date(iso).toLocaleString(undefined, {
|
|
24
|
+
month: "short", day: "numeric", year: "numeric",
|
|
25
|
+
hour: "2-digit", minute: "2-digit",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function triggerLabel(fn) {
|
|
29
|
+
const t = fn.trigger;
|
|
30
|
+
if (t.type === "schedule")
|
|
31
|
+
return `schedule ${chalk_1.default.dim(t.cron ?? "")}`;
|
|
32
|
+
if (t.collection)
|
|
33
|
+
return `${t.type} ${chalk_1.default.dim(t.collection)}`;
|
|
34
|
+
if (t.bucket)
|
|
35
|
+
return `${t.type} ${chalk_1.default.dim(t.bucket)}`;
|
|
36
|
+
return t.type;
|
|
37
|
+
}
|
|
38
|
+
function statusColor(status) {
|
|
39
|
+
if (status === "active" || status === "success")
|
|
40
|
+
return chalk_1.default.green(status);
|
|
41
|
+
if (status === "error" || status === "timeout")
|
|
42
|
+
return chalk_1.default.red(status);
|
|
43
|
+
if (status === "disabled")
|
|
44
|
+
return chalk_1.default.dim(status);
|
|
45
|
+
return status;
|
|
46
|
+
}
|
|
47
|
+
// ─── functions:list ───────────────────────────────────────────────────────────
|
|
48
|
+
async function runFunctionsList(cwd = process.cwd()) {
|
|
49
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
50
|
+
const sp = (0, ora_1.default)("Fetching functions…").start();
|
|
51
|
+
let fns;
|
|
52
|
+
try {
|
|
53
|
+
fns = await (0, api_1.apiFnList)(cfg);
|
|
54
|
+
sp.succeed(`${fns.length} function${fns.length !== 1 ? "s" : ""}`);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
sp.fail(err.message);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (fns.length === 0) {
|
|
61
|
+
console.log(chalk_1.default.dim("\n No functions deployed yet. Run `clefbase functions:deploy` to get started.\n"));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log();
|
|
65
|
+
for (const fn of fns) {
|
|
66
|
+
const err = fn.errorCount > 0 ? chalk_1.default.red(` ${fn.errorCount} err`) : "";
|
|
67
|
+
console.log(` ${chalk_1.default.bold(fn.name.padEnd(28))}` +
|
|
68
|
+
`${RUNTIME_BADGE[fn.runtime] ?? fn.runtime} ` +
|
|
69
|
+
`${statusColor(fn.status).padEnd(14)} ` +
|
|
70
|
+
`${triggerLabel(fn)}`);
|
|
71
|
+
console.log(` ${chalk_1.default.dim("".padEnd(28))}` +
|
|
72
|
+
`invocations: ${fn.invocationCount}${err} ` +
|
|
73
|
+
`last: ${fn.lastInvokedAt ? fmtDate(fn.lastInvokedAt) : "—"}`);
|
|
74
|
+
console.log();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const RUNTIME_BADGE = {
|
|
78
|
+
node: chalk_1.default.green("[JS/TS]"),
|
|
79
|
+
python: chalk_1.default.blue("[PY] "),
|
|
80
|
+
};
|
|
81
|
+
// ─── functions:deploy ─────────────────────────────────────────────────────────
|
|
82
|
+
async function runFunctionsDeploy(opts) {
|
|
83
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
84
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(chalk_1.default.bold.cyan(" Deploy Function"));
|
|
87
|
+
console.log();
|
|
88
|
+
// ── Resolve function name ────────────────────────────────────────────────
|
|
89
|
+
const name = opts.name ?? await promptRequired("Function name");
|
|
90
|
+
// ── Resolve source ───────────────────────────────────────────────────────
|
|
91
|
+
let source;
|
|
92
|
+
if (opts.file) {
|
|
93
|
+
const absFile = path_1.default.isAbsolute(opts.file) ? opts.file : path_1.default.join(cwd, opts.file);
|
|
94
|
+
if (!fs_1.default.existsSync(absFile)) {
|
|
95
|
+
console.error(chalk_1.default.red(`\n File not found: ${absFile}\n`));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
source = fs_1.default.readFileSync(absFile, "utf-8");
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Interactive: ask for a file path or inline snippet
|
|
102
|
+
const { inputMode } = await inquirer_1.default.prompt([{
|
|
103
|
+
type: "list",
|
|
104
|
+
name: "inputMode",
|
|
105
|
+
message: "Source code",
|
|
106
|
+
choices: [
|
|
107
|
+
{ name: "From a file (recommended)", value: "file" },
|
|
108
|
+
{ name: "Inline snippet (paste in terminal)", value: "inline" },
|
|
109
|
+
],
|
|
110
|
+
}]);
|
|
111
|
+
if (inputMode === "file") {
|
|
112
|
+
const { filePath } = await inquirer_1.default.prompt([{
|
|
113
|
+
type: "input",
|
|
114
|
+
name: "filePath",
|
|
115
|
+
message: "Path to source file (.js, .ts, .py)",
|
|
116
|
+
validate: (v) => {
|
|
117
|
+
if (!v.trim())
|
|
118
|
+
return "Required";
|
|
119
|
+
const abs = path_1.default.isAbsolute(v) ? v : path_1.default.join(cwd, v);
|
|
120
|
+
return fs_1.default.existsSync(abs) || `File not found: ${abs}`;
|
|
121
|
+
},
|
|
122
|
+
}]);
|
|
123
|
+
const abs = path_1.default.isAbsolute(filePath) ? filePath : path_1.default.join(cwd, filePath.trim());
|
|
124
|
+
source = fs_1.default.readFileSync(abs, "utf-8");
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const { snippet } = await inquirer_1.default.prompt([{
|
|
128
|
+
type: "editor",
|
|
129
|
+
name: "snippet",
|
|
130
|
+
message: "Paste your function source (opens $EDITOR)",
|
|
131
|
+
}]);
|
|
132
|
+
source = snippet;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// ── Runtime ──────────────────────────────────────────────────────────────
|
|
136
|
+
let runtime = opts.runtime;
|
|
137
|
+
if (!runtime) {
|
|
138
|
+
// Auto-detect from file extension
|
|
139
|
+
if (opts.file) {
|
|
140
|
+
const ext = path_1.default.extname(opts.file).toLowerCase();
|
|
141
|
+
if (ext === ".py")
|
|
142
|
+
runtime = "python";
|
|
143
|
+
else
|
|
144
|
+
runtime = "node";
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const { rt } = await inquirer_1.default.prompt([{
|
|
148
|
+
type: "list",
|
|
149
|
+
name: "rt",
|
|
150
|
+
message: "Runtime",
|
|
151
|
+
choices: [
|
|
152
|
+
{ name: "Node.js / TypeScript (JS/TS)", value: "node" },
|
|
153
|
+
{ name: "Python 3 (PY)", value: "python" },
|
|
154
|
+
],
|
|
155
|
+
}]);
|
|
156
|
+
runtime = rt;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// ── Trigger ──────────────────────────────────────────────────────────────
|
|
160
|
+
let triggerType = opts.trigger;
|
|
161
|
+
if (!triggerType) {
|
|
162
|
+
const { tt } = await inquirer_1.default.prompt([{
|
|
163
|
+
type: "list",
|
|
164
|
+
name: "tt",
|
|
165
|
+
message: "Trigger type",
|
|
166
|
+
choices: [
|
|
167
|
+
{ name: "HTTP (POST /functions/call/:name)", value: "http" },
|
|
168
|
+
{ name: "Schedule (cron timer)", value: "schedule" },
|
|
169
|
+
{ name: "onDocumentCreate", value: "onDocumentCreate" },
|
|
170
|
+
{ name: "onDocumentUpdate", value: "onDocumentUpdate" },
|
|
171
|
+
{ name: "onDocumentDelete", value: "onDocumentDelete" },
|
|
172
|
+
{ name: "onDocumentWrite (create + update + delete)", value: "onDocumentWrite" },
|
|
173
|
+
{ name: "onUserCreate", value: "onUserCreate" },
|
|
174
|
+
{ name: "onUserDelete", value: "onUserDelete" },
|
|
175
|
+
{ name: "onFileUpload", value: "onFileUpload" },
|
|
176
|
+
{ name: "onFileDelete", value: "onFileDelete" },
|
|
177
|
+
],
|
|
178
|
+
}]);
|
|
179
|
+
triggerType = tt;
|
|
180
|
+
}
|
|
181
|
+
const trigger = { type: triggerType };
|
|
182
|
+
if (triggerType === "schedule") {
|
|
183
|
+
trigger.cron = opts.cron ?? await promptRequired("Cron expression (e.g. 0 * * * *)");
|
|
184
|
+
}
|
|
185
|
+
if (["onDocumentWrite", "onDocumentCreate", "onDocumentUpdate", "onDocumentDelete"].includes(triggerType)) {
|
|
186
|
+
trigger.collection = opts.collection ?? await promptRequired("Collection path (e.g. users)");
|
|
187
|
+
}
|
|
188
|
+
if (["onFileUpload", "onFileDelete"].includes(triggerType)) {
|
|
189
|
+
const { bucket } = await inquirer_1.default.prompt([{
|
|
190
|
+
type: "input",
|
|
191
|
+
name: "bucket",
|
|
192
|
+
message: "Bucket name filter (leave blank = all buckets)",
|
|
193
|
+
}]);
|
|
194
|
+
if (bucket.trim())
|
|
195
|
+
trigger.bucket = bucket.trim();
|
|
196
|
+
}
|
|
197
|
+
// ── Entry point & timeout ────────────────────────────────────────────────
|
|
198
|
+
const entryPoint = opts.entry ?? "handler";
|
|
199
|
+
const timeoutMs = opts.timeout ? parseInt(opts.timeout, 10) : 30000;
|
|
200
|
+
// ── Env vars ─────────────────────────────────────────────────────────────
|
|
201
|
+
const env = {};
|
|
202
|
+
for (const pair of (opts.env ?? [])) {
|
|
203
|
+
const idx = pair.indexOf("=");
|
|
204
|
+
if (idx > 0)
|
|
205
|
+
env[pair.slice(0, idx)] = pair.slice(idx + 1);
|
|
206
|
+
}
|
|
207
|
+
// ── Deploy ────────────────────────────────────────────────────────────────
|
|
208
|
+
const payload = { name, runtime, trigger, source, entryPoint, timeoutMs, env };
|
|
209
|
+
const sp = (0, ora_1.default)(`Deploying "${name}"…`).start();
|
|
210
|
+
try {
|
|
211
|
+
const result = await (0, api_1.apiFnDeploy)(cfg, payload);
|
|
212
|
+
sp.succeed(chalk_1.default.green(`Deployed "${result.name}" → ${statusColor(result.status)}`));
|
|
213
|
+
console.log();
|
|
214
|
+
console.log(` ${chalk_1.default.bold("Name:")} ${result.name}`);
|
|
215
|
+
console.log(` ${chalk_1.default.bold("Runtime:")} ${runtime}`);
|
|
216
|
+
console.log(` ${chalk_1.default.bold("Trigger:")} ${triggerLabel({ trigger })}`);
|
|
217
|
+
if (trigger.type === "http") {
|
|
218
|
+
console.log(` ${chalk_1.default.bold("Call:")} POST ${cfg.serverUrl.replace(/\/+$/, "")}/functions/call/${name}`);
|
|
219
|
+
}
|
|
220
|
+
console.log();
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
sp.fail(err.message);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// ─── functions:call ───────────────────────────────────────────────────────────
|
|
228
|
+
async function runFunctionsCall(name, opts) {
|
|
229
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
230
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
231
|
+
let data = null;
|
|
232
|
+
if (opts.data) {
|
|
233
|
+
try {
|
|
234
|
+
data = JSON.parse(opts.data);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
console.error(chalk_1.default.red(`\n --data must be valid JSON. Got: ${opts.data}\n`));
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const sp = (0, ora_1.default)(`Calling "${name}"…`).start();
|
|
242
|
+
try {
|
|
243
|
+
const result = await (0, api_1.apiFnCall)(cfg, name, data);
|
|
244
|
+
sp.succeed(`${chalk_1.default.green("✓")} ${name} ${chalk_1.default.dim(fmtDuration(result.durationMs))}`);
|
|
245
|
+
console.log();
|
|
246
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
247
|
+
console.log();
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
sp.fail(err.message);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// ─── functions:delete ─────────────────────────────────────────────────────────
|
|
255
|
+
async function runFunctionsDelete(name, opts) {
|
|
256
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
257
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
258
|
+
if (!opts.force) {
|
|
259
|
+
const { confirmed } = await inquirer_1.default.prompt([{
|
|
260
|
+
type: "confirm",
|
|
261
|
+
name: "confirmed",
|
|
262
|
+
message: `Delete function "${name}"? This cannot be undone.`,
|
|
263
|
+
default: false,
|
|
264
|
+
}]);
|
|
265
|
+
if (!confirmed) {
|
|
266
|
+
console.log(chalk_1.default.dim("\n Cancelled.\n"));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const sp = (0, ora_1.default)(`Deleting "${name}"…`).start();
|
|
271
|
+
try {
|
|
272
|
+
await (0, api_1.apiFnDelete)(cfg, name);
|
|
273
|
+
sp.succeed(chalk_1.default.green(`"${name}" deleted`));
|
|
274
|
+
console.log();
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
sp.fail(err.message);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// ─── functions:logs ───────────────────────────────────────────────────────────
|
|
282
|
+
async function runFunctionsLogs(name, opts) {
|
|
283
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
284
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
285
|
+
const limit = opts.limit ? parseInt(opts.limit, 10) : 20;
|
|
286
|
+
const sp = (0, ora_1.default)(`Fetching logs for "${name}"…`).start();
|
|
287
|
+
let execs;
|
|
288
|
+
try {
|
|
289
|
+
execs = await (0, api_1.apiFnExecutions)(cfg, name, limit);
|
|
290
|
+
sp.succeed(`${execs.length} execution${execs.length !== 1 ? "s" : ""}`);
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
sp.fail(err.message);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (execs.length === 0) {
|
|
297
|
+
console.log(chalk_1.default.dim(`\n No executions recorded yet for "${name}".\n`));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
console.log();
|
|
301
|
+
for (const ex of execs) {
|
|
302
|
+
const statusStr = statusColor(ex.status);
|
|
303
|
+
console.log(` ${statusStr.padEnd(16)}` +
|
|
304
|
+
`${chalk_1.default.dim(fmtDate(ex.startedAt))} ` +
|
|
305
|
+
`${chalk_1.default.dim(fmtDuration(ex.durationMs))} ` +
|
|
306
|
+
`${chalk_1.default.dim("via")} ${ex.triggeredBy}`);
|
|
307
|
+
if (ex.error) {
|
|
308
|
+
console.log(chalk_1.default.red(` ✗ ${ex.error.split("\n")[0]}`));
|
|
309
|
+
}
|
|
310
|
+
if (ex.logs.length > 0) {
|
|
311
|
+
for (const line of ex.logs) {
|
|
312
|
+
console.log(chalk_1.default.dim(` ▶ ${line}`));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (ex.result !== undefined && ex.result !== null) {
|
|
316
|
+
const preview = JSON.stringify(ex.result);
|
|
317
|
+
console.log(chalk_1.default.dim(` ← ${preview.length > 120 ? preview.slice(0, 120) + "…" : preview}`));
|
|
318
|
+
}
|
|
319
|
+
console.log();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// ─── Prompt helper ────────────────────────────────────────────────────────────
|
|
323
|
+
async function promptRequired(message) {
|
|
324
|
+
const { value } = await inquirer_1.default.prompt([{
|
|
325
|
+
type: "input",
|
|
326
|
+
name: "value",
|
|
327
|
+
message,
|
|
328
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
329
|
+
}]);
|
|
330
|
+
return value.trim();
|
|
331
|
+
}
|