@fold-run/cli 0.1.2 → 0.2.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/commands/completions.d.ts +1 -0
- package/dist/commands/completions.js +195 -0
- package/dist/commands/completions.js.map +1 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +75 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/delete.d.ts +1 -0
- package/dist/commands/delete.js +8 -0
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/deploy.d.ts +1 -0
- package/dist/commands/deploy.js +59 -17
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.js +53 -34
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +138 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/domains.d.ts +11 -0
- package/dist/commands/domains.js +111 -0
- package/dist/commands/domains.js.map +1 -0
- package/dist/commands/env-vars.d.ts +1 -0
- package/dist/commands/env-vars.js +8 -1
- package/dist/commands/env-vars.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +104 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/invoke.d.ts +9 -0
- package/dist/commands/invoke.js +89 -0
- package/dist/commands/invoke.js.map +1 -0
- package/dist/commands/link.d.ts +4 -0
- package/dist/commands/link.js +77 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.js +8 -6
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logs.js +31 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/schedules.d.ts +3 -1
- package/dist/commands/schedules.js +41 -21
- package/dist/commands/schedules.js.map +1 -1
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +9 -2
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/status.js +48 -28
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/templates.js +24 -17
- package/dist/commands/templates.js.map +1 -1
- package/dist/commands/tenant.d.ts +4 -0
- package/dist/commands/tenant.js +82 -0
- package/dist/commands/tenant.js.map +1 -0
- package/dist/commands/webhooks.d.ts +3 -1
- package/dist/commands/webhooks.js +31 -18
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +39 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.js +107 -34
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +1 -0
- package/dist/lib/api.js +30 -0
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/bundler.js +124 -18
- package/dist/lib/bundler.js.map +1 -1
- package/dist/lib/colorize.d.ts +5 -0
- package/dist/lib/colorize.js +28 -0
- package/dist/lib/colorize.js.map +1 -0
- package/dist/lib/cron.d.ts +13 -0
- package/dist/lib/cron.js +107 -0
- package/dist/lib/cron.js.map +1 -0
- package/dist/lib/manifest.d.ts +31 -0
- package/dist/lib/manifest.js +195 -0
- package/dist/lib/manifest.js.map +1 -0
- package/dist/lib/prompt.d.ts +27 -0
- package/dist/lib/prompt.js +160 -0
- package/dist/lib/prompt.js.map +1 -1
- package/dist/lib/spinner.d.ts +14 -0
- package/dist/lib/spinner.js +52 -0
- package/dist/lib/spinner.js.map +1 -0
- package/dist/lib/version-check.d.ts +9 -0
- package/dist/lib/version-check.js +54 -0
- package/dist/lib/version-check.js.map +1 -0
- package/package.json +10 -11
package/dist/lib/bundler.js
CHANGED
|
@@ -2,40 +2,113 @@ import { build } from 'esbuild';
|
|
|
2
2
|
/**
|
|
3
3
|
* Dev shim for @fold-run/runtime — stubs all platform bindings for local development.
|
|
4
4
|
* Injected by the esbuild plugin when bundling for `fold dev`.
|
|
5
|
+
*
|
|
6
|
+
* Stubs provide functional in-memory implementations for KV and Storage,
|
|
7
|
+
* logged no-ops for AI/Queue/Vectorize, and middleware support in defineHandler.
|
|
5
8
|
*/
|
|
6
9
|
const FOLD_RUNTIME_DEV_SHIM = `
|
|
7
10
|
class DevKV {
|
|
8
11
|
constructor() { this._store = new Map(); }
|
|
9
|
-
async get(key) {
|
|
10
|
-
|
|
12
|
+
async get(key, opts) {
|
|
13
|
+
const val = this._store.get(key);
|
|
14
|
+
if (val === undefined) return null;
|
|
15
|
+
if (opts?.type === 'json') return JSON.parse(val);
|
|
16
|
+
if (opts?.type === 'arrayBuffer') return new TextEncoder().encode(val).buffer;
|
|
17
|
+
return val;
|
|
18
|
+
}
|
|
19
|
+
async put(key, value, opts) {
|
|
20
|
+
const str = typeof value === 'string' ? value : '[binary]';
|
|
21
|
+
this._store.set(key, str);
|
|
22
|
+
if (opts?.expirationTtl) {
|
|
23
|
+
setTimeout(() => this._store.delete(key), opts.expirationTtl * 1000);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
11
26
|
async delete(key) { this._store.delete(key); }
|
|
12
27
|
async list(opts) {
|
|
13
|
-
const
|
|
14
|
-
|
|
28
|
+
const all = [...this._store.keys()];
|
|
29
|
+
const filtered = opts?.prefix ? all.filter(k => k.startsWith(opts.prefix)) : all;
|
|
30
|
+
const limited = filtered.slice(0, opts?.limit ?? 1000);
|
|
31
|
+
return { keys: limited.map(name => ({ name })), list_complete: limited.length === filtered.length, cursor: undefined };
|
|
15
32
|
}
|
|
16
33
|
}
|
|
34
|
+
|
|
17
35
|
class DevStorage {
|
|
18
|
-
|
|
19
|
-
async
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
constructor() { this._store = new Map(); }
|
|
37
|
+
async put(key, value) {
|
|
38
|
+
let body;
|
|
39
|
+
if (typeof value === 'string') body = value;
|
|
40
|
+
else if (value instanceof ArrayBuffer) body = new TextDecoder().decode(value);
|
|
41
|
+
else body = '[stream/blob]';
|
|
42
|
+
const obj = { key, size: body.length, etag: Math.random().toString(36).slice(2), uploaded: new Date(), body };
|
|
43
|
+
this._store.set(key, obj);
|
|
44
|
+
return { key: obj.key, size: obj.size, etag: obj.etag, uploaded: obj.uploaded };
|
|
45
|
+
}
|
|
46
|
+
async get(key) {
|
|
47
|
+
const obj = this._store.get(key);
|
|
48
|
+
if (!obj) return null;
|
|
49
|
+
const body = obj.body;
|
|
50
|
+
return {
|
|
51
|
+
key: obj.key, size: obj.size, etag: obj.etag, uploaded: obj.uploaded,
|
|
52
|
+
body: new ReadableStream({ start(c) { c.enqueue(new TextEncoder().encode(body)); c.close(); } }),
|
|
53
|
+
bodyUsed: false,
|
|
54
|
+
text: async () => body,
|
|
55
|
+
json: async () => JSON.parse(body),
|
|
56
|
+
arrayBuffer: async () => new TextEncoder().encode(body).buffer,
|
|
57
|
+
blob: async () => new Blob([body]),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async head(key) {
|
|
61
|
+
const obj = this._store.get(key);
|
|
62
|
+
if (!obj) return null;
|
|
63
|
+
return { key: obj.key, size: obj.size, etag: obj.etag, uploaded: obj.uploaded };
|
|
64
|
+
}
|
|
65
|
+
async delete(keys) {
|
|
66
|
+
const arr = Array.isArray(keys) ? keys : [keys];
|
|
67
|
+
for (const k of arr) this._store.delete(k);
|
|
68
|
+
}
|
|
69
|
+
async list(opts) {
|
|
70
|
+
const all = [...this._store.keys()];
|
|
71
|
+
const filtered = opts?.prefix ? all.filter(k => k.startsWith(opts.prefix)) : all;
|
|
72
|
+
const limited = filtered.slice(0, opts?.limit ?? 1000);
|
|
73
|
+
return {
|
|
74
|
+
objects: limited.map(k => { const o = this._store.get(k); return { key: o.key, size: o.size, etag: o.etag, uploaded: o.uploaded }; }),
|
|
75
|
+
truncated: limited.length < filtered.length,
|
|
76
|
+
cursor: undefined,
|
|
77
|
+
delimitedPrefixes: [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
22
80
|
}
|
|
81
|
+
|
|
23
82
|
class DevDB {
|
|
24
|
-
prepare() {
|
|
25
|
-
|
|
83
|
+
prepare(query) {
|
|
84
|
+
console.log('[fold dev] DB.prepare:', query.slice(0, 80));
|
|
85
|
+
const noop = { bind: () => noop, first: async () => null, all: async () => ({ results: [], success: true }), run: async () => ({ success: true, meta: {} }) };
|
|
26
86
|
return noop;
|
|
27
87
|
}
|
|
88
|
+
async batch(stmts) { console.log('[fold dev] DB.batch:', stmts.length, 'statements'); return []; }
|
|
89
|
+
async exec(query) { console.log('[fold dev] DB.exec:', query.slice(0, 80)); return { count: 0, duration: 0 }; }
|
|
28
90
|
}
|
|
91
|
+
|
|
29
92
|
class DevAI {
|
|
30
|
-
async run(model) {
|
|
31
|
-
console.warn('[fold dev] AI.run("' + model + '")
|
|
93
|
+
async run(model, inputs) {
|
|
94
|
+
console.warn('[fold dev] AI.run("' + model + '") — returns empty stub. Use fold deploy to test with real models.');
|
|
32
95
|
return {};
|
|
33
96
|
}
|
|
34
97
|
}
|
|
98
|
+
|
|
35
99
|
class DevQueue {
|
|
36
|
-
async send(body) { console.log('[fold dev] Queue.send:', body); }
|
|
100
|
+
async send(body) { console.log('[fold dev] Queue.send:', JSON.stringify(body).slice(0, 200)); }
|
|
37
101
|
async sendBatch(messages) { console.log('[fold dev] Queue.sendBatch:', messages.length, 'messages'); }
|
|
38
102
|
}
|
|
103
|
+
|
|
104
|
+
class DevVectorize {
|
|
105
|
+
async insert(vectors) { console.warn('[fold dev] Vectorize.insert:', vectors.length, 'vectors — stub'); return { count: vectors.length, ids: vectors.map(v => v.id) }; }
|
|
106
|
+
async upsert(vectors) { console.warn('[fold dev] Vectorize.upsert:', vectors.length, 'vectors — stub'); return { count: vectors.length, ids: vectors.map(v => v.id) }; }
|
|
107
|
+
async query() { return { matches: [], count: 0 }; }
|
|
108
|
+
async getByIds() { return []; }
|
|
109
|
+
async deleteByIds(ids) { return { count: ids.length, ids }; }
|
|
110
|
+
}
|
|
111
|
+
|
|
39
112
|
class FoldContext {
|
|
40
113
|
constructor(request, env) {
|
|
41
114
|
this.request = request;
|
|
@@ -44,33 +117,66 @@ class FoldContext {
|
|
|
44
117
|
this.db = new DevDB();
|
|
45
118
|
this.ai = new DevAI();
|
|
46
119
|
this.queue = new DevQueue();
|
|
120
|
+
this.vectorize = new DevVectorize();
|
|
47
121
|
this.tenantId = undefined;
|
|
122
|
+
this._waitUntil = null;
|
|
48
123
|
const managed = new Set(['AI', 'VECTORIZE', 'KV', 'DB', 'STORAGE', 'QUEUE', 'FOLD_TENANT_ID']);
|
|
49
124
|
this.env = Object.fromEntries(
|
|
50
125
|
Object.entries(env).filter(([k, v]) => !managed.has(k) && typeof v === 'string').map(([k, v]) => [k, v])
|
|
51
126
|
);
|
|
52
127
|
}
|
|
128
|
+
waitUntil(promise) { if (this._waitUntil) this._waitUntil(promise); }
|
|
53
129
|
json(data, status = 200) { return Response.json(data, { status }); }
|
|
54
130
|
text(body, status = 200) { return new Response(body, { status, headers: { 'content-type': 'text/plain' } }); }
|
|
131
|
+
async body() { return this.request.json(); }
|
|
132
|
+
query(name) { return new URL(this.request.url).searchParams.get(name); }
|
|
133
|
+
queries() { return Object.fromEntries(new URL(this.request.url).searchParams.entries()); }
|
|
134
|
+
header(name) { return this.request.headers.get(name); }
|
|
55
135
|
stream(generator) {
|
|
56
136
|
const { readable, writable } = new TransformStream();
|
|
57
137
|
const writer = writable.getWriter();
|
|
58
138
|
const enc = new TextEncoder();
|
|
59
139
|
(async () => {
|
|
60
140
|
try { for await (const chunk of generator()) await writer.write(enc.encode(chunk)); }
|
|
61
|
-
|
|
141
|
+
catch (err) { try { await writer.write(enc.encode('event: error\\ndata: ' + JSON.stringify({ error: err?.message ?? 'Stream error' }) + '\\n\\n')); } catch {} }
|
|
142
|
+
finally { try { await writer.close(); } catch {} }
|
|
62
143
|
})();
|
|
63
|
-
return new Response(readable, { headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' } });
|
|
144
|
+
return new Response(readable, { headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', 'connection': 'keep-alive' } });
|
|
64
145
|
}
|
|
65
146
|
}
|
|
147
|
+
|
|
148
|
+
function composeMiddleware(middleware, handler) {
|
|
149
|
+
return (ctx) => {
|
|
150
|
+
let index = 0;
|
|
151
|
+
const dispatch = () => {
|
|
152
|
+
if (index < middleware.length) { const mw = middleware[index++]; return Promise.resolve(mw(ctx, dispatch)); }
|
|
153
|
+
return Promise.resolve(handler(ctx));
|
|
154
|
+
};
|
|
155
|
+
return dispatch();
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
66
159
|
export function defineHandler(handlerOrOptions) {
|
|
67
|
-
const
|
|
160
|
+
const isOptions = typeof handlerOrOptions !== 'function';
|
|
161
|
+
const rawFetchFn = isOptions ? handlerOrOptions.fetch : handlerOrOptions;
|
|
162
|
+
const middleware = isOptions ? handlerOrOptions.middleware : undefined;
|
|
163
|
+
const scheduledFn = isOptions ? handlerOrOptions.scheduled : undefined;
|
|
164
|
+
const fetchFn = middleware?.length ? composeMiddleware(middleware, rawFetchFn) : rawFetchFn;
|
|
68
165
|
return {
|
|
69
|
-
async fetch(request, env) {
|
|
166
|
+
async fetch(request, env, ctx) {
|
|
70
167
|
const context = new FoldContext(request, env ?? {});
|
|
168
|
+
context._waitUntil = ctx?.waitUntil?.bind(ctx);
|
|
71
169
|
try { return await fetchFn(context); }
|
|
72
170
|
catch (err) { return Response.json({ error: err instanceof Error ? err.message : 'Internal error' }, { status: 500 }); }
|
|
73
171
|
},
|
|
172
|
+
...(scheduledFn && {
|
|
173
|
+
async scheduled(event, env, ctx) {
|
|
174
|
+
const req = new Request('https://scheduled.internal/__scheduled', { method: 'POST' });
|
|
175
|
+
const context = new FoldContext(req, env ?? {});
|
|
176
|
+
context._waitUntil = ctx?.waitUntil?.bind(ctx);
|
|
177
|
+
await scheduledFn(context, event);
|
|
178
|
+
}
|
|
179
|
+
}),
|
|
74
180
|
};
|
|
75
181
|
}
|
|
76
182
|
export { FoldContext };
|
|
@@ -103,7 +209,7 @@ export async function bundleFile(entryPoint, opts = {}) {
|
|
|
103
209
|
write: false,
|
|
104
210
|
format: 'esm',
|
|
105
211
|
target: 'esnext',
|
|
106
|
-
platform: 'browser',
|
|
212
|
+
platform: 'browser',
|
|
107
213
|
minify: !opts.dev,
|
|
108
214
|
treeShaking: true,
|
|
109
215
|
conditions: ['worker', 'browser'],
|
package/dist/lib/bundler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundler.js","sourceRoot":"","sources":["../../src/lib/bundler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAe,MAAM,SAAS,CAAC;AAE7C
|
|
1
|
+
{"version":3,"file":"bundler.js","sourceRoot":"","sources":["../../src/lib/bundler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAe,MAAM,SAAS,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8K7B,CAAC;AAEF,qEAAqE;AACrE,MAAM,oBAAoB,GAAW;IACnC,IAAI,EAAE,kBAAkB;IACxB,KAAK,CAAC,KAAK;QACT,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACzD,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,kBAAkB;SAC9B,CAAC,CAAC,CAAC;QACJ,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YACnE,QAAQ,EAAE,qBAAqB;YAC/B,MAAM,EAAE,IAAI;SACb,CAAC,CAAC,CAAC;IACN,CAAC;CACF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB,EAAE,OAA0B,EAAE;IAC/E,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC;QACzB,WAAW,EAAE,CAAC,UAAU,CAAC;QACzB,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG;QACjB,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;QACjC,2EAA2E;QAC3E,yEAAyE;QACzE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,mBAAmB,CAAC;QAC7E,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE;KAChD,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Lightweight JSON syntax highlighting for terminal output.
|
|
2
|
+
// No dependencies — uses ANSI escape codes directly.
|
|
3
|
+
const RESET = '\x1b[0m';
|
|
4
|
+
const CYAN = '\x1b[36m'; // keys
|
|
5
|
+
const GREEN = '\x1b[32m'; // strings
|
|
6
|
+
const YELLOW = '\x1b[33m'; // numbers
|
|
7
|
+
const MAGENTA = '\x1b[35m'; // booleans, null
|
|
8
|
+
/**
|
|
9
|
+
* Colorize a JSON string for terminal display.
|
|
10
|
+
* Applies syntax highlighting to keys, strings, numbers, booleans, and null.
|
|
11
|
+
*/
|
|
12
|
+
export function colorizeJson(json) {
|
|
13
|
+
return json.replace(/("(?:\\.|[^"\\])*")\s*(:)?|(\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|(\bnull\b|\btrue\b|\bfalse\b)/g, (match, str, colon, num, lit) => {
|
|
14
|
+
if (str) {
|
|
15
|
+
// It's a string — is it a key (followed by :) or a value?
|
|
16
|
+
if (colon) {
|
|
17
|
+
return `${CYAN}${str}${RESET}:`;
|
|
18
|
+
}
|
|
19
|
+
return `${GREEN}${str}${RESET}`;
|
|
20
|
+
}
|
|
21
|
+
if (num)
|
|
22
|
+
return `${YELLOW}${num}${RESET}`;
|
|
23
|
+
if (lit)
|
|
24
|
+
return `${MAGENTA}${lit}${RESET}`;
|
|
25
|
+
return match;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=colorize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colorize.js","sourceRoot":"","sources":["../../src/lib/colorize.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,qDAAqD;AAErD,MAAM,KAAK,GAAG,SAAS,CAAC;AACxB,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,OAAO;AAChC,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,UAAU;AACpC,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,UAAU;AACrC,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,iBAAiB;AAE7C;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,OAAO,CACjB,gGAAgG,EAChG,CAAC,KAAK,EAAE,GAAuB,EAAE,KAAyB,EAAE,GAAuB,EAAE,GAAuB,EAAE,EAAE;QAC9G,IAAI,GAAG,EAAE,CAAC;YACR,0DAA0D;YAC1D,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,GAAG,IAAI,GAAG,GAAG,GAAG,KAAK,GAAG,CAAC;YAClC,CAAC;YACD,OAAO,GAAG,KAAK,GAAG,GAAG,GAAG,KAAK,EAAE,CAAC;QAClC,CAAC;QACD,IAAI,GAAG;YAAE,OAAO,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK,EAAE,CAAC;QAC1C,IAAI,GAAG;YAAE,OAAO,GAAG,OAAO,GAAG,GAAG,GAAG,KAAK,EAAE,CAAC;QAC3C,OAAO,KAAK,CAAC;IACf,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface CronValidationResult {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
error?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Validate a cron expression.
|
|
7
|
+
* Returns { valid: true } or { valid: false, error: "..." }
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateCron(expression: string): CronValidationResult;
|
|
10
|
+
/**
|
|
11
|
+
* Format a cron validation error with usage hint.
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatCronError(error: string): string;
|
package/dist/lib/cron.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Cron expression validator for standard 5-field cron format:
|
|
2
|
+
// minute hour day-of-month month day-of-week
|
|
3
|
+
// Supports: numbers, ranges (1-5), steps (e.g. every 5), lists (1,3,5), wildcards (*)
|
|
4
|
+
const CRON_FIELDS = [
|
|
5
|
+
{ name: 'minute', min: 0, max: 59 },
|
|
6
|
+
{ name: 'hour', min: 0, max: 23 },
|
|
7
|
+
{ name: 'day of month', min: 1, max: 31 },
|
|
8
|
+
{ name: 'month', min: 1, max: 12 },
|
|
9
|
+
{ name: 'day of week', min: 0, max: 7 }, // 0 and 7 are both Sunday
|
|
10
|
+
];
|
|
11
|
+
// Validate a single cron field value (e.g., "5", "1-10", "1,5,10")
|
|
12
|
+
function validateField(value, field) {
|
|
13
|
+
// Wildcard
|
|
14
|
+
if (value === '*') {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
// Step values: */5 or 1-10/2
|
|
18
|
+
if (value.includes('/')) {
|
|
19
|
+
const [range, stepStr] = value.split('/');
|
|
20
|
+
const step = parseInt(stepStr, 10);
|
|
21
|
+
if (Number.isNaN(step) || step < 1) {
|
|
22
|
+
return `invalid step value "${stepStr}" in ${field.name}`;
|
|
23
|
+
}
|
|
24
|
+
// Validate the range part
|
|
25
|
+
if (range !== '*') {
|
|
26
|
+
const rangeError = validateField(range, field);
|
|
27
|
+
if (rangeError)
|
|
28
|
+
return rangeError;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// List values: 1,5,10
|
|
33
|
+
if (value.includes(',')) {
|
|
34
|
+
const parts = value.split(',');
|
|
35
|
+
for (const part of parts) {
|
|
36
|
+
const error = validateField(part.trim(), field);
|
|
37
|
+
if (error)
|
|
38
|
+
return error;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Range values: 1-10
|
|
43
|
+
if (value.includes('-')) {
|
|
44
|
+
const [startStr, endStr] = value.split('-');
|
|
45
|
+
const start = parseInt(startStr, 10);
|
|
46
|
+
const end = parseInt(endStr, 10);
|
|
47
|
+
if (Number.isNaN(start) || Number.isNaN(end)) {
|
|
48
|
+
return `invalid range "${value}" in ${field.name}`;
|
|
49
|
+
}
|
|
50
|
+
if (start < field.min || start > field.max) {
|
|
51
|
+
return `${field.name} start value ${start} out of range (${field.min}-${field.max})`;
|
|
52
|
+
}
|
|
53
|
+
if (end < field.min || end > field.max) {
|
|
54
|
+
return `${field.name} end value ${end} out of range (${field.min}-${field.max})`;
|
|
55
|
+
}
|
|
56
|
+
if (start > end) {
|
|
57
|
+
return `invalid range "${value}" in ${field.name}: start > end`;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
// Single number
|
|
62
|
+
const num = parseInt(value, 10);
|
|
63
|
+
if (Number.isNaN(num)) {
|
|
64
|
+
return `invalid value "${value}" in ${field.name}`;
|
|
65
|
+
}
|
|
66
|
+
if (num < field.min || num > field.max) {
|
|
67
|
+
return `${field.name} value ${num} out of range (${field.min}-${field.max})`;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate a cron expression.
|
|
73
|
+
* Returns { valid: true } or { valid: false, error: "..." }
|
|
74
|
+
*/
|
|
75
|
+
export function validateCron(expression) {
|
|
76
|
+
const trimmed = expression.trim();
|
|
77
|
+
if (!trimmed) {
|
|
78
|
+
return { valid: false, error: 'cron expression cannot be empty' };
|
|
79
|
+
}
|
|
80
|
+
const fields = trimmed.split(/\s+/);
|
|
81
|
+
if (fields.length !== 5) {
|
|
82
|
+
return {
|
|
83
|
+
valid: false,
|
|
84
|
+
error: `expected 5 fields (minute hour day month weekday), got ${fields.length}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
for (let i = 0; i < 5; i++) {
|
|
88
|
+
const error = validateField(fields[i], CRON_FIELDS[i]);
|
|
89
|
+
if (error) {
|
|
90
|
+
return { valid: false, error };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { valid: true };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Format a cron validation error with usage hint.
|
|
97
|
+
*/
|
|
98
|
+
export function formatCronError(error) {
|
|
99
|
+
return `Invalid cron expression: ${error}
|
|
100
|
+
|
|
101
|
+
Format: <minute> <hour> <day> <month> <weekday>
|
|
102
|
+
Examples:
|
|
103
|
+
*/5 * * * * Every 5 minutes
|
|
104
|
+
0 9 * * 1-5 9 AM on weekdays
|
|
105
|
+
0 0 1 * * Midnight on the 1st of each month`;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=cron.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.js","sourceRoot":"","sources":["../../src/lib/cron.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface FoldManifest {
|
|
2
|
+
name?: string;
|
|
3
|
+
entrypoint?: string;
|
|
4
|
+
tenant_id?: string;
|
|
5
|
+
intent?: {
|
|
6
|
+
trigger?: string;
|
|
7
|
+
bindings?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface ManifestValidationError {
|
|
11
|
+
field: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ManifestResult {
|
|
15
|
+
manifest: FoldManifest | null;
|
|
16
|
+
errors: ManifestValidationError[];
|
|
17
|
+
warnings: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load and validate fold.json from current directory.
|
|
21
|
+
* Returns manifest (if valid), errors (blocking), and warnings (non-blocking).
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadManifest(dir?: string): ManifestResult;
|
|
24
|
+
/**
|
|
25
|
+
* Format validation errors for display.
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatManifestErrors(errors: ManifestValidationError[]): string;
|
|
28
|
+
/**
|
|
29
|
+
* Format warnings for display.
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatManifestWarnings(warnings: string[]): string;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// fold.json manifest validation
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
const KNOWN_FIELDS = ['name', 'entrypoint', 'tenant_id', 'intent'];
|
|
5
|
+
const KNOWN_INTENT_FIELDS = ['trigger', 'bindings'];
|
|
6
|
+
const VALID_TRIGGERS = ['http', 'cron', 'webhook', 'queue'];
|
|
7
|
+
const VALID_BINDINGS = ['kv', 'storage', 'db', 'queue', 'ai'];
|
|
8
|
+
function validateType(value, field, expected) {
|
|
9
|
+
const actual = Array.isArray(value) ? 'array' : typeof value;
|
|
10
|
+
if (actual !== expected) {
|
|
11
|
+
return `"${field}" must be a ${expected}, got ${actual}`;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Load and validate fold.json from current directory.
|
|
17
|
+
* Returns manifest (if valid), errors (blocking), and warnings (non-blocking).
|
|
18
|
+
*/
|
|
19
|
+
export function loadManifest(dir) {
|
|
20
|
+
const manifestPath = resolve(dir ?? '.', 'fold.json');
|
|
21
|
+
const errors = [];
|
|
22
|
+
const warnings = [];
|
|
23
|
+
if (!existsSync(manifestPath)) {
|
|
24
|
+
return { manifest: null, errors: [], warnings: [] };
|
|
25
|
+
}
|
|
26
|
+
let raw;
|
|
27
|
+
try {
|
|
28
|
+
raw = readFileSync(manifestPath, 'utf-8');
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
errors.push({ field: 'file', message: `Could not read fold.json: ${err}` });
|
|
32
|
+
return { manifest: null, errors, warnings };
|
|
33
|
+
}
|
|
34
|
+
let data;
|
|
35
|
+
try {
|
|
36
|
+
data = JSON.parse(raw);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
const parseErr = err;
|
|
40
|
+
errors.push({ field: 'json', message: `Invalid JSON: ${parseErr.message}` });
|
|
41
|
+
return { manifest: null, errors, warnings };
|
|
42
|
+
}
|
|
43
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
44
|
+
errors.push({ field: 'root', message: 'fold.json must be an object' });
|
|
45
|
+
return { manifest: null, errors, warnings };
|
|
46
|
+
}
|
|
47
|
+
const obj = data;
|
|
48
|
+
// Check for unknown top-level fields
|
|
49
|
+
for (const key of Object.keys(obj)) {
|
|
50
|
+
if (!KNOWN_FIELDS.includes(key)) {
|
|
51
|
+
warnings.push(`Unknown field "${key}" in fold.json (will be ignored)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Validate name
|
|
55
|
+
if (obj.name !== undefined) {
|
|
56
|
+
const typeErr = validateType(obj.name, 'name', 'string');
|
|
57
|
+
if (typeErr) {
|
|
58
|
+
errors.push({ field: 'name', message: typeErr });
|
|
59
|
+
}
|
|
60
|
+
else if (typeof obj.name === 'string') {
|
|
61
|
+
if (obj.name.trim() === '') {
|
|
62
|
+
errors.push({ field: 'name', message: '"name" cannot be empty' });
|
|
63
|
+
}
|
|
64
|
+
else if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(obj.name)) {
|
|
65
|
+
errors.push({
|
|
66
|
+
field: 'name',
|
|
67
|
+
message: '"name" must be lowercase alphanumeric with hyphens (e.g., "my-function")',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Validate entrypoint
|
|
73
|
+
if (obj.entrypoint !== undefined) {
|
|
74
|
+
const typeErr = validateType(obj.entrypoint, 'entrypoint', 'string');
|
|
75
|
+
if (typeErr) {
|
|
76
|
+
errors.push({ field: 'entrypoint', message: typeErr });
|
|
77
|
+
}
|
|
78
|
+
else if (typeof obj.entrypoint === 'string') {
|
|
79
|
+
if (obj.entrypoint.trim() === '') {
|
|
80
|
+
errors.push({ field: 'entrypoint', message: '"entrypoint" cannot be empty' });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const entryPath = resolve(dir ?? '.', obj.entrypoint);
|
|
84
|
+
if (!existsSync(entryPath)) {
|
|
85
|
+
errors.push({
|
|
86
|
+
field: 'entrypoint',
|
|
87
|
+
message: `Entrypoint file not found: ${obj.entrypoint}`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Validate tenant_id
|
|
94
|
+
if (obj.tenant_id !== undefined) {
|
|
95
|
+
const typeErr = validateType(obj.tenant_id, 'tenant_id', 'string');
|
|
96
|
+
if (typeErr) {
|
|
97
|
+
errors.push({ field: 'tenant_id', message: typeErr });
|
|
98
|
+
}
|
|
99
|
+
else if (typeof obj.tenant_id === 'string' && obj.tenant_id.trim() === '') {
|
|
100
|
+
errors.push({ field: 'tenant_id', message: '"tenant_id" cannot be empty' });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Validate intent
|
|
104
|
+
if (obj.intent !== undefined) {
|
|
105
|
+
const typeErr = validateType(obj.intent, 'intent', 'object');
|
|
106
|
+
if (typeErr) {
|
|
107
|
+
errors.push({ field: 'intent', message: typeErr });
|
|
108
|
+
}
|
|
109
|
+
else if (typeof obj.intent === 'object' && obj.intent !== null && !Array.isArray(obj.intent)) {
|
|
110
|
+
const intent = obj.intent;
|
|
111
|
+
// Check for unknown intent fields
|
|
112
|
+
for (const key of Object.keys(intent)) {
|
|
113
|
+
if (!KNOWN_INTENT_FIELDS.includes(key)) {
|
|
114
|
+
warnings.push(`Unknown field "intent.${key}" (will be ignored)`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Validate trigger
|
|
118
|
+
if (intent.trigger !== undefined) {
|
|
119
|
+
const triggerErr = validateType(intent.trigger, 'intent.trigger', 'string');
|
|
120
|
+
if (triggerErr) {
|
|
121
|
+
errors.push({ field: 'intent.trigger', message: triggerErr });
|
|
122
|
+
}
|
|
123
|
+
else if (typeof intent.trigger === 'string' && !VALID_TRIGGERS.includes(intent.trigger)) {
|
|
124
|
+
warnings.push(`Unknown trigger "${intent.trigger}" (valid: ${VALID_TRIGGERS.join(', ')})`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Validate bindings
|
|
128
|
+
if (intent.bindings !== undefined) {
|
|
129
|
+
const bindingsErr = validateType(intent.bindings, 'intent.bindings', 'array');
|
|
130
|
+
if (bindingsErr) {
|
|
131
|
+
errors.push({ field: 'intent.bindings', message: bindingsErr });
|
|
132
|
+
}
|
|
133
|
+
else if (Array.isArray(intent.bindings)) {
|
|
134
|
+
for (let i = 0; i < intent.bindings.length; i++) {
|
|
135
|
+
const b = intent.bindings[i];
|
|
136
|
+
if (typeof b !== 'string') {
|
|
137
|
+
errors.push({
|
|
138
|
+
field: `intent.bindings[${i}]`,
|
|
139
|
+
message: `Binding must be a string, got ${typeof b}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else if (!VALID_BINDINGS.includes(b)) {
|
|
143
|
+
warnings.push(`Unknown binding "${b}" (valid: ${VALID_BINDINGS.join(', ')})`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (errors.length > 0) {
|
|
151
|
+
return { manifest: null, errors, warnings };
|
|
152
|
+
}
|
|
153
|
+
// Build validated manifest
|
|
154
|
+
const manifest = {};
|
|
155
|
+
if (typeof obj.name === 'string')
|
|
156
|
+
manifest.name = obj.name;
|
|
157
|
+
if (typeof obj.entrypoint === 'string')
|
|
158
|
+
manifest.entrypoint = obj.entrypoint;
|
|
159
|
+
if (typeof obj.tenant_id === 'string')
|
|
160
|
+
manifest.tenant_id = obj.tenant_id;
|
|
161
|
+
if (typeof obj.intent === 'object' && obj.intent !== null) {
|
|
162
|
+
const intent = obj.intent;
|
|
163
|
+
manifest.intent = {};
|
|
164
|
+
if (typeof intent.trigger === 'string')
|
|
165
|
+
manifest.intent.trigger = intent.trigger;
|
|
166
|
+
if (Array.isArray(intent.bindings)) {
|
|
167
|
+
manifest.intent.bindings = intent.bindings.filter((b) => typeof b === 'string');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { manifest, errors, warnings };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Format validation errors for display.
|
|
174
|
+
*/
|
|
175
|
+
export function formatManifestErrors(errors) {
|
|
176
|
+
const lines = ['Invalid fold.json:', ''];
|
|
177
|
+
for (const err of errors) {
|
|
178
|
+
lines.push(` \x1b[31m✗\x1b[0m ${err.message}`);
|
|
179
|
+
}
|
|
180
|
+
lines.push('');
|
|
181
|
+
lines.push('Example fold.json:');
|
|
182
|
+
lines.push(' {');
|
|
183
|
+
lines.push(' "name": "my-function",');
|
|
184
|
+
lines.push(' "entrypoint": "function.ts",');
|
|
185
|
+
lines.push(' "intent": { "bindings": ["kv"] }');
|
|
186
|
+
lines.push(' }');
|
|
187
|
+
return lines.join('\n');
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Format warnings for display.
|
|
191
|
+
*/
|
|
192
|
+
export function formatManifestWarnings(warnings) {
|
|
193
|
+
return warnings.map((w) => `\x1b[33mWarning:\x1b[0m ${w}`).join('\n');
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/lib/manifest.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/prompt.d.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt for yes/no confirmation. Returns true if user confirms.
|
|
3
|
+
* Defaults to 'no' if user just presses enter.
|
|
4
|
+
*/
|
|
5
|
+
export declare function confirm(message: string): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Prompt for a single line of text input.
|
|
8
|
+
* Returns the trimmed input, or defaultValue if the user presses Enter.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ask(message: string, defaultValue?: string): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Prompt the user to select from a list of options using arrow keys.
|
|
13
|
+
* Returns the selected option value. Falls back to numbered list on non-TTY.
|
|
14
|
+
*/
|
|
15
|
+
export declare function select<T extends string>(message: string, options: {
|
|
16
|
+
label: string;
|
|
17
|
+
value: T;
|
|
18
|
+
}[]): Promise<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Prompt for toggling multiple options on/off. Returns selected values.
|
|
21
|
+
* Space to toggle, Enter to confirm.
|
|
22
|
+
*/
|
|
23
|
+
export declare function multiSelect<T extends string>(message: string, options: {
|
|
24
|
+
label: string;
|
|
25
|
+
value: T;
|
|
26
|
+
default?: boolean;
|
|
27
|
+
}[]): Promise<T[]>;
|
|
1
28
|
/**
|
|
2
29
|
* Read a line of input with masked output (shows * for each character).
|
|
3
30
|
* Handles backspace and Ctrl+C.
|