devflare 1.0.0-next.13 → 1.0.0-next.15
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/LLM.md +36 -1
- package/README.md +85 -0
- package/dist/{account-8psavtg6.js → account-spa7gzsn.js} +3 -2
- package/dist/bridge/miniflare.d.ts.map +1 -1
- package/dist/browser.d.ts +7 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/{build-e6kgjwr8.js → build-zv25ke4s.js} +10 -9
- package/dist/bundler/do-bundler.d.ts.map +1 -1
- package/dist/bundler/worker-bundler.d.ts.map +1 -1
- package/dist/bundler/worker-compat.d.ts +4 -0
- package/dist/bundler/worker-compat.d.ts.map +1 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/config.d.ts +4 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/config/compiler.d.ts.map +1 -1
- package/dist/config/index.d.ts +3 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/loader.d.ts +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/resource-resolution.d.ts +44 -0
- package/dist/config/resource-resolution.d.ts.map +1 -0
- package/dist/config/schema.d.ts +193 -28
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config-v9tr4rts.js +57 -0
- package/dist/{deploy-eeaqwxaa.js → deploy-6xmqvv06.js} +10 -9
- package/dist/dev-server/server.d.ts.map +1 -1
- package/dist/{dev-mqsffeeb.js → dev-ymtphbkg.js} +11 -5
- package/dist/{doctor-z4ffybce.js → doctor-xv4gm1h4.js} +3 -2
- package/dist/{index-nb0bqtx7.js → index-001mw014.js} +308 -2
- package/dist/{index-xxwbb2nt.js → index-0rsa2c1t.js} +5 -2
- package/dist/{index-0kzg8wed.js → index-3a4mmn57.js} +12 -6
- package/dist/{index-rfhx0yd5.js → index-5s1bz1e0.js} +12 -12
- package/dist/{index-n7rs26ft.js → index-6nb7w45m.js} +15 -13
- package/dist/index-7bq4xq84.js +197 -0
- package/dist/{index-dr6sbp8d.js → index-k8vh558d.js} +1 -1
- package/dist/{index-wyf3s77s.js → index-tksw7gpy.js} +162 -2
- package/dist/{index-8x16kn47.js → index-v43z02tr.js} +18 -8
- package/dist/{index-tfyxa77h.js → index-xdq9ery1.js} +1 -187
- package/dist/{index-zbvmtcn2.js → index-zvgc3e0c.js} +25 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/src/browser.js +17 -0
- package/dist/src/cli/index.js +1 -1
- package/dist/src/cloudflare/index.js +3 -2
- package/dist/src/index.js +16 -7
- package/dist/src/sveltekit/index.js +4 -3
- package/dist/src/test/index.js +6 -5
- package/dist/src/vite/index.js +4 -3
- package/dist/test/simple-context.d.ts.map +1 -1
- package/dist/{types-sffr9681.js → types-158m16vd.js} +4 -3
- package/dist/vite/plugin.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
apiGet,
|
|
3
|
+
apiPost,
|
|
4
|
+
getEffectiveAccountId,
|
|
5
|
+
getPrimaryAccount,
|
|
6
|
+
isAuthenticated,
|
|
7
|
+
kvGet,
|
|
8
|
+
kvPut
|
|
9
|
+
} from "./index-xdq9ery1.js";
|
|
10
|
+
|
|
11
|
+
// src/cloudflare/usage.ts
|
|
12
|
+
var DEVFLARE_KV_NAMESPACE_TITLE = "devflare-usage";
|
|
13
|
+
var USAGE_KEY_PREFIX = "usage:";
|
|
14
|
+
var LIMITS_KEY = "limits";
|
|
15
|
+
var DEFAULT_LIMITS = {
|
|
16
|
+
aiTokensPerDay: 1e4,
|
|
17
|
+
aiRequestsPerDay: 100,
|
|
18
|
+
vectorizeOpsPerDay: 1000,
|
|
19
|
+
enabled: true
|
|
20
|
+
};
|
|
21
|
+
async function getOrCreateUsageNamespace(accountId) {
|
|
22
|
+
const namespaces = await apiGet(`/accounts/${accountId}/storage/kv/namespaces`);
|
|
23
|
+
const existing = namespaces.find((ns) => ns.title === DEVFLARE_KV_NAMESPACE_TITLE);
|
|
24
|
+
if (existing) {
|
|
25
|
+
return existing.id;
|
|
26
|
+
}
|
|
27
|
+
const created = await apiPost(`/accounts/${accountId}/storage/kv/namespaces`, { title: DEVFLARE_KV_NAMESPACE_TITLE });
|
|
28
|
+
return created.id;
|
|
29
|
+
}
|
|
30
|
+
function getTodayDate() {
|
|
31
|
+
return new Date().toISOString().split("T")[0];
|
|
32
|
+
}
|
|
33
|
+
function buildUsageKey(service, date) {
|
|
34
|
+
return `${USAGE_KEY_PREFIX}${service}:${date}`;
|
|
35
|
+
}
|
|
36
|
+
async function getUsage(accountId, service, date) {
|
|
37
|
+
const targetDate = date ?? getTodayDate();
|
|
38
|
+
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
39
|
+
const key = buildUsageKey(service, targetDate);
|
|
40
|
+
const value = await kvGet(accountId, namespaceId, key);
|
|
41
|
+
if (value === null) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(value);
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function recordUsage(accountId, service, count = 1) {
|
|
51
|
+
const today = getTodayDate();
|
|
52
|
+
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
53
|
+
const key = buildUsageKey(service, today);
|
|
54
|
+
const existing = await getUsage(accountId, service, today);
|
|
55
|
+
const currentCount = existing?.count ?? 0;
|
|
56
|
+
const record = {
|
|
57
|
+
service,
|
|
58
|
+
date: today,
|
|
59
|
+
count: currentCount + count,
|
|
60
|
+
updatedAt: new Date().toISOString()
|
|
61
|
+
};
|
|
62
|
+
await kvPut(accountId, namespaceId, key, JSON.stringify(record));
|
|
63
|
+
return record;
|
|
64
|
+
}
|
|
65
|
+
async function resetUsage(accountId, service) {
|
|
66
|
+
const today = getTodayDate();
|
|
67
|
+
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
68
|
+
const key = buildUsageKey(service, today);
|
|
69
|
+
const record = {
|
|
70
|
+
service,
|
|
71
|
+
date: today,
|
|
72
|
+
count: 0,
|
|
73
|
+
updatedAt: new Date().toISOString()
|
|
74
|
+
};
|
|
75
|
+
await kvPut(accountId, namespaceId, key, JSON.stringify(record));
|
|
76
|
+
}
|
|
77
|
+
async function getLimits(accountId) {
|
|
78
|
+
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
79
|
+
const value = await kvGet(accountId, namespaceId, LIMITS_KEY);
|
|
80
|
+
if (value === null) {
|
|
81
|
+
return DEFAULT_LIMITS;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return { ...DEFAULT_LIMITS, ...JSON.parse(value) };
|
|
85
|
+
} catch {
|
|
86
|
+
return DEFAULT_LIMITS;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function setLimits(accountId, limits) {
|
|
90
|
+
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
91
|
+
const current = await getLimits(accountId);
|
|
92
|
+
const updated = {
|
|
93
|
+
...current,
|
|
94
|
+
...limits
|
|
95
|
+
};
|
|
96
|
+
await kvPut(accountId, namespaceId, LIMITS_KEY, JSON.stringify(updated));
|
|
97
|
+
return updated;
|
|
98
|
+
}
|
|
99
|
+
async function setLimitsEnabled(accountId, enabled) {
|
|
100
|
+
return setLimits(accountId, { enabled });
|
|
101
|
+
}
|
|
102
|
+
async function isWithinLimits(accountId, service) {
|
|
103
|
+
const limits = await getLimits(accountId);
|
|
104
|
+
if (!limits.enabled) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const usage = await getUsage(accountId, service);
|
|
108
|
+
const currentCount = usage?.count ?? 0;
|
|
109
|
+
switch (service) {
|
|
110
|
+
case "ai":
|
|
111
|
+
if (limits.aiRequestsPerDay && currentCount >= limits.aiRequestsPerDay) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
case "vectorize":
|
|
116
|
+
if (limits.vectorizeOpsPerDay && currentCount >= limits.vectorizeOpsPerDay) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
default:
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function getUsageSummary(accountId, service) {
|
|
125
|
+
const limits = await getLimits(accountId);
|
|
126
|
+
const usage = await getUsage(accountId, service);
|
|
127
|
+
const currentCount = usage?.count ?? 0;
|
|
128
|
+
let limit;
|
|
129
|
+
switch (service) {
|
|
130
|
+
case "ai":
|
|
131
|
+
limit = limits.aiRequestsPerDay;
|
|
132
|
+
break;
|
|
133
|
+
case "vectorize":
|
|
134
|
+
limit = limits.vectorizeOpsPerDay;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
const withinLimit = limit === undefined || currentCount < limit;
|
|
138
|
+
const percentUsed = limit ? currentCount / limit * 100 : undefined;
|
|
139
|
+
return {
|
|
140
|
+
service,
|
|
141
|
+
today: currentCount,
|
|
142
|
+
limit,
|
|
143
|
+
withinLimit,
|
|
144
|
+
percentUsed
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function getAllUsageSummaries(accountId) {
|
|
148
|
+
const trackedServices = ["ai", "vectorize"];
|
|
149
|
+
return Promise.all(trackedServices.map((s) => getUsageSummary(accountId, s)));
|
|
150
|
+
}
|
|
151
|
+
async function canProceedWithTest(accountId, service) {
|
|
152
|
+
const limits = await getLimits(accountId);
|
|
153
|
+
if (!limits.enabled) {
|
|
154
|
+
return { allowed: true };
|
|
155
|
+
}
|
|
156
|
+
const withinLimits = await isWithinLimits(accountId, service);
|
|
157
|
+
if (!withinLimits) {
|
|
158
|
+
const summary = await getUsageSummary(accountId, service);
|
|
159
|
+
return {
|
|
160
|
+
allowed: false,
|
|
161
|
+
reason: `Daily limit exceeded for ${service}: ${summary.today}/${summary.limit} (${summary.percentUsed?.toFixed(1)}%)`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return { allowed: true };
|
|
165
|
+
}
|
|
166
|
+
async function recordTestUsage(accountId, service, count = 1) {
|
|
167
|
+
await recordUsage(accountId, service, count);
|
|
168
|
+
}
|
|
169
|
+
async function shouldSkip(service) {
|
|
170
|
+
try {
|
|
171
|
+
const isAuth = await isAuthenticated();
|
|
172
|
+
if (!isAuth) {
|
|
173
|
+
console.log(`⏭️ ${service.toUpperCase()} tests skipped: Not authenticated. Run: bunx wrangler login`);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
const primary = await getPrimaryAccount();
|
|
177
|
+
if (!primary) {
|
|
178
|
+
console.log(`⏭️ ${service.toUpperCase()} tests skipped: No Cloudflare account found`);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
const { accountId } = await getEffectiveAccountId(primary.id);
|
|
182
|
+
try {
|
|
183
|
+
const { allowed, reason } = await canProceedWithTest(accountId, service);
|
|
184
|
+
if (!allowed) {
|
|
185
|
+
console.log(`⏭️ ${service.toUpperCase()} tests skipped: ${reason}`);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
} catch {}
|
|
189
|
+
return false;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
192
|
+
console.log(`⏭️ ${service.toUpperCase()} tests skipped: ${message}`);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { getUsage, recordUsage, resetUsage, getLimits, setLimits, setLimitsEnabled, isWithinLimits, getUsageSummary, getAllUsageSummaries, canProceedWithTest, recordTestUsage, shouldSkip };
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getEffectiveAccountId,
|
|
3
|
+
getPrimaryAccount,
|
|
4
|
+
listD1Databases
|
|
5
|
+
} from "./index-xdq9ery1.js";
|
|
6
|
+
|
|
1
7
|
// src/config/schema.ts
|
|
2
8
|
import { z } from "zod";
|
|
3
9
|
var dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -79,9 +85,20 @@ var sendEmailBindingSchema = z.object({
|
|
|
79
85
|
message: "sendEmail bindings must use either destinationAddress or allowedDestinationAddresses, not both",
|
|
80
86
|
path: ["allowedDestinationAddresses"]
|
|
81
87
|
});
|
|
88
|
+
var d1BindingByIdSchema = z.object({
|
|
89
|
+
id: z.string()
|
|
90
|
+
}).strict();
|
|
91
|
+
var d1BindingByNameSchema = z.object({
|
|
92
|
+
name: z.string()
|
|
93
|
+
}).strict();
|
|
94
|
+
var d1BindingSchema = z.union([
|
|
95
|
+
z.string(),
|
|
96
|
+
d1BindingByIdSchema,
|
|
97
|
+
d1BindingByNameSchema
|
|
98
|
+
]);
|
|
82
99
|
var bindingsSchema = z.object({
|
|
83
100
|
kv: z.record(z.string(), z.string()).optional(),
|
|
84
|
-
d1: z.record(z.string(),
|
|
101
|
+
d1: z.record(z.string(), d1BindingSchema).optional(),
|
|
85
102
|
r2: z.record(z.string(), z.string()).optional(),
|
|
86
103
|
durableObjects: z.record(z.string(), durableObjectBindingSchema).optional(),
|
|
87
104
|
queues: queuesConfigSchema.optional(),
|
|
@@ -269,6 +286,19 @@ function normalizeDOBinding(config) {
|
|
|
269
286
|
__ref: config.__ref
|
|
270
287
|
};
|
|
271
288
|
}
|
|
289
|
+
function normalizeD1Binding(config) {
|
|
290
|
+
if (typeof config === "string") {
|
|
291
|
+
return { databaseId: config };
|
|
292
|
+
}
|
|
293
|
+
if ("id" in config) {
|
|
294
|
+
return { databaseId: config.id };
|
|
295
|
+
}
|
|
296
|
+
return { name: config.name };
|
|
297
|
+
}
|
|
298
|
+
function getLocalD1DatabaseIdentifier(config) {
|
|
299
|
+
const normalized = normalizeD1Binding(config);
|
|
300
|
+
return normalized.databaseId ?? normalized.name ?? "";
|
|
301
|
+
}
|
|
272
302
|
|
|
273
303
|
// src/config/loader.ts
|
|
274
304
|
import { loadConfig as c12LoadConfig } from "c12";
|
|
@@ -340,4 +370,134 @@ ${issueMessages}`);
|
|
|
340
370
|
}
|
|
341
371
|
}
|
|
342
372
|
|
|
343
|
-
|
|
373
|
+
// src/config/resolve.ts
|
|
374
|
+
import { defu } from "defu";
|
|
375
|
+
function resolveConfigForEnvironment(config, environment) {
|
|
376
|
+
if (environment && config.env?.[environment]) {
|
|
377
|
+
return defu(config.env[environment], config);
|
|
378
|
+
}
|
|
379
|
+
return config;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/config/resource-resolution.ts
|
|
383
|
+
var defaultCloudflareApi = {
|
|
384
|
+
getPrimaryAccount,
|
|
385
|
+
getEffectiveAccountId,
|
|
386
|
+
listD1Databases
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
class ConfigResourceResolutionError extends Error {
|
|
390
|
+
code = "CONFIG_RESOURCE_RESOLUTION_ERROR";
|
|
391
|
+
constructor(message, cause) {
|
|
392
|
+
super(message);
|
|
393
|
+
this.name = "ConfigResourceResolutionError";
|
|
394
|
+
if (cause !== undefined) {
|
|
395
|
+
this.cause = cause;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function resolveCloudflareApi(overrides) {
|
|
400
|
+
return {
|
|
401
|
+
...defaultCloudflareApi,
|
|
402
|
+
...overrides ?? {}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function materializeLocalD1Bindings(bindings) {
|
|
406
|
+
return Object.fromEntries(Object.entries(bindings).map(([bindingName, bindingConfig]) => {
|
|
407
|
+
return [bindingName, getLocalD1DatabaseIdentifier(bindingConfig)];
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
async function resolveLookupAccountId(config, options, cloudflareApi) {
|
|
411
|
+
const explicitAccountId = options.accountId ?? config.accountId;
|
|
412
|
+
if (explicitAccountId) {
|
|
413
|
+
return explicitAccountId;
|
|
414
|
+
}
|
|
415
|
+
let primaryAccount;
|
|
416
|
+
try {
|
|
417
|
+
primaryAccount = await cloudflareApi.getPrimaryAccount();
|
|
418
|
+
} catch (error) {
|
|
419
|
+
throw new ConfigResourceResolutionError("Could not resolve D1 database names because Devflare could not read your Cloudflare accounts. Set accountId in devflare.config.ts, configure a workspace/global default account, or log in with Wrangler.", error);
|
|
420
|
+
}
|
|
421
|
+
if (!primaryAccount) {
|
|
422
|
+
throw new ConfigResourceResolutionError("Could not resolve D1 database names because no Cloudflare account is available. Set accountId in devflare.config.ts, configure a workspace/global default account, or log in with Wrangler.");
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const { accountId } = await cloudflareApi.getEffectiveAccountId(primaryAccount.id);
|
|
426
|
+
return accountId;
|
|
427
|
+
} catch (error) {
|
|
428
|
+
throw new ConfigResourceResolutionError(`Could not determine the effective Cloudflare account for D1 name resolution after selecting primary account ${primaryAccount.id}.`, error);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function formatMissingD1Bindings(missing) {
|
|
432
|
+
return missing.map(({ bindingName, databaseName }) => `${bindingName} → ${databaseName}`).join(", ");
|
|
433
|
+
}
|
|
434
|
+
function resolveConfigForLocalRuntime(config, environment) {
|
|
435
|
+
const resolvedConfig = resolveConfigForEnvironment(config, environment);
|
|
436
|
+
if (!resolvedConfig.bindings?.d1) {
|
|
437
|
+
return resolvedConfig;
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
...resolvedConfig,
|
|
441
|
+
bindings: {
|
|
442
|
+
...resolvedConfig.bindings,
|
|
443
|
+
d1: materializeLocalD1Bindings(resolvedConfig.bindings.d1)
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
async function resolveConfigResources(config, options = {}) {
|
|
448
|
+
const resolvedConfig = resolveConfigForEnvironment(config, options.environment);
|
|
449
|
+
const d1Bindings = resolvedConfig.bindings?.d1;
|
|
450
|
+
if (!d1Bindings) {
|
|
451
|
+
return resolvedConfig;
|
|
452
|
+
}
|
|
453
|
+
const pendingNameBindings = Object.entries(d1Bindings).map(([bindingName, bindingConfig]) => {
|
|
454
|
+
const normalized = normalizeD1Binding(bindingConfig);
|
|
455
|
+
return normalized.databaseId ? null : {
|
|
456
|
+
bindingName,
|
|
457
|
+
databaseName: normalized.name ?? ""
|
|
458
|
+
};
|
|
459
|
+
}).filter((binding) => binding !== null);
|
|
460
|
+
if (pendingNameBindings.length === 0) {
|
|
461
|
+
return {
|
|
462
|
+
...resolvedConfig,
|
|
463
|
+
bindings: {
|
|
464
|
+
...resolvedConfig.bindings,
|
|
465
|
+
d1: materializeLocalD1Bindings(d1Bindings)
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const cloudflareApi = resolveCloudflareApi(options.cloudflare);
|
|
470
|
+
const accountId = await resolveLookupAccountId(resolvedConfig, options, cloudflareApi);
|
|
471
|
+
let databases;
|
|
472
|
+
try {
|
|
473
|
+
databases = await cloudflareApi.listD1Databases(accountId);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
throw new ConfigResourceResolutionError(`Could not list D1 databases for Cloudflare account ${accountId} while resolving name-based D1 bindings.`, error);
|
|
476
|
+
}
|
|
477
|
+
const databaseIdsByName = new Map(databases.map((database) => [database.name, database.id]));
|
|
478
|
+
const missingBindings = pendingNameBindings.filter(({ databaseName }) => {
|
|
479
|
+
return !databaseIdsByName.has(databaseName);
|
|
480
|
+
});
|
|
481
|
+
if (missingBindings.length > 0) {
|
|
482
|
+
throw new ConfigResourceResolutionError(`Could not find D1 database(s) for ${formatMissingD1Bindings(missingBindings)} in Cloudflare account ${accountId}.`);
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
...resolvedConfig,
|
|
486
|
+
bindings: {
|
|
487
|
+
...resolvedConfig.bindings,
|
|
488
|
+
d1: Object.fromEntries(Object.entries(d1Bindings).map(([bindingName, bindingConfig]) => {
|
|
489
|
+
const normalized = normalizeD1Binding(bindingConfig);
|
|
490
|
+
return [
|
|
491
|
+
bindingName,
|
|
492
|
+
normalized.databaseId ?? databaseIdsByName.get(normalized.name ?? "") ?? ""
|
|
493
|
+
];
|
|
494
|
+
}))
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
async function loadResolvedConfig(options = {}) {
|
|
499
|
+
const config = await loadConfig(options);
|
|
500
|
+
return resolveConfigResources(config, options);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export { configSchema, normalizeDOBinding, normalizeD1Binding, getLocalD1DatabaseIdentifier, resolveConfigForEnvironment, ConfigResourceResolutionError, resolveConfigForLocalRuntime, resolveConfigResources, loadResolvedConfig, resolveConfigPath, loadConfig, ConfigNotFoundError, ConfigValidationError };
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/cli/index.ts
|
|
9
9
|
import { createConsola } from "consola";
|
|
10
|
-
var COMMANDS = ["init", "dev", "build", "deploy", "types", "doctor", "account", "ai", "remote", "help", "version"];
|
|
10
|
+
var COMMANDS = ["init", "dev", "build", "deploy", "types", "doctor", "config", "account", "ai", "remote", "help", "version"];
|
|
11
11
|
function parseArgs(argv) {
|
|
12
12
|
const args = [];
|
|
13
13
|
const options = {};
|
|
@@ -62,6 +62,7 @@ Commands:
|
|
|
62
62
|
deploy Deploy to Cloudflare
|
|
63
63
|
types Generate TypeScript types
|
|
64
64
|
doctor Check project configuration
|
|
65
|
+
config Print resolved Devflare/Wrangler config
|
|
65
66
|
account View Cloudflare account info
|
|
66
67
|
ai View AI models and pricing
|
|
67
68
|
remote Manage remote test mode (AI, Vectorize)
|
|
@@ -69,7 +70,8 @@ Commands:
|
|
|
69
70
|
version Show the installed devflare version
|
|
70
71
|
|
|
71
72
|
Common Options:
|
|
72
|
-
--config <path> Used by dev, build, deploy, types, and
|
|
73
|
+
--config <path> Used by dev, build, deploy, types, doctor, and config
|
|
74
|
+
--env <name> Used by build, deploy, and config to select config.env[name]
|
|
73
75
|
--debug Enable debug output for supported commands
|
|
74
76
|
-h, --help Show help
|
|
75
77
|
-v, --version Show version
|
|
@@ -89,6 +91,8 @@ Build / Deploy:
|
|
|
89
91
|
Types / Doctor:
|
|
90
92
|
types --output <path> Write generated types to a custom path
|
|
91
93
|
doctor --config <path> Check a specific devflare config file
|
|
94
|
+
config print --json Print resolved config as JSON
|
|
95
|
+
config print --format wrangler Print resolved Wrangler config JSON
|
|
92
96
|
|
|
93
97
|
Account / Remote:
|
|
94
98
|
account --account <id> Use a specific Cloudflare account
|
|
@@ -144,6 +148,8 @@ async function runCli(argv, options = {}) {
|
|
|
144
148
|
return runTypes(parsed, logger, options);
|
|
145
149
|
case "doctor":
|
|
146
150
|
return runDoctor(parsed, logger, options);
|
|
151
|
+
case "config":
|
|
152
|
+
return runConfig(parsed, logger, options);
|
|
147
153
|
case "account":
|
|
148
154
|
return runAccount(parsed, logger, options);
|
|
149
155
|
case "ai":
|
|
@@ -160,27 +166,31 @@ async function runInit(parsed, logger, options) {
|
|
|
160
166
|
return runInitCommand(parsed, logger, options);
|
|
161
167
|
}
|
|
162
168
|
async function runDev(parsed, logger, options) {
|
|
163
|
-
const { runDevCommand } = await import("./dev-
|
|
169
|
+
const { runDevCommand } = await import("./dev-ymtphbkg.js");
|
|
164
170
|
return runDevCommand(parsed, logger, options);
|
|
165
171
|
}
|
|
166
172
|
async function runBuild(parsed, logger, options) {
|
|
167
|
-
const { runBuildCommand } = await import("./build-
|
|
173
|
+
const { runBuildCommand } = await import("./build-zv25ke4s.js");
|
|
168
174
|
return runBuildCommand(parsed, logger, options);
|
|
169
175
|
}
|
|
170
176
|
async function runDeploy(parsed, logger, options) {
|
|
171
|
-
const { runDeployCommand } = await import("./deploy-
|
|
177
|
+
const { runDeployCommand } = await import("./deploy-6xmqvv06.js");
|
|
172
178
|
return runDeployCommand(parsed, logger, options);
|
|
173
179
|
}
|
|
174
180
|
async function runTypes(parsed, logger, options) {
|
|
175
|
-
const { runTypesCommand } = await import("./types-
|
|
181
|
+
const { runTypesCommand } = await import("./types-158m16vd.js");
|
|
176
182
|
return runTypesCommand(parsed, logger, options);
|
|
177
183
|
}
|
|
178
184
|
async function runDoctor(parsed, logger, options) {
|
|
179
|
-
const { runDoctorCommand } = await import("./doctor-
|
|
185
|
+
const { runDoctorCommand } = await import("./doctor-xv4gm1h4.js");
|
|
180
186
|
return runDoctorCommand(parsed, logger, options);
|
|
181
187
|
}
|
|
188
|
+
async function runConfig(parsed, logger, options) {
|
|
189
|
+
const { runConfigCommand } = await import("./config-v9tr4rts.js");
|
|
190
|
+
return runConfigCommand(parsed, logger, options);
|
|
191
|
+
}
|
|
182
192
|
async function runAccount(parsed, logger, options) {
|
|
183
|
-
const { runAccountCommand } = await import("./account-
|
|
193
|
+
const { runAccountCommand } = await import("./account-spa7gzsn.js");
|
|
184
194
|
return runAccountCommand(parsed, logger, options);
|
|
185
195
|
}
|
|
186
196
|
async function runAI() {
|
|
@@ -661,190 +661,4 @@ async function clearGlobalDefaultAccountId(anyAccountId) {
|
|
|
661
661
|
} catch {}
|
|
662
662
|
}
|
|
663
663
|
|
|
664
|
-
|
|
665
|
-
var DEVFLARE_KV_NAMESPACE_TITLE2 = "devflare-usage";
|
|
666
|
-
var USAGE_KEY_PREFIX = "usage:";
|
|
667
|
-
var LIMITS_KEY = "limits";
|
|
668
|
-
var DEFAULT_LIMITS = {
|
|
669
|
-
aiTokensPerDay: 1e4,
|
|
670
|
-
aiRequestsPerDay: 100,
|
|
671
|
-
vectorizeOpsPerDay: 1000,
|
|
672
|
-
enabled: true
|
|
673
|
-
};
|
|
674
|
-
async function getOrCreateUsageNamespace(accountId) {
|
|
675
|
-
const namespaces = await apiGet(`/accounts/${accountId}/storage/kv/namespaces`);
|
|
676
|
-
const existing = namespaces.find((ns) => ns.title === DEVFLARE_KV_NAMESPACE_TITLE2);
|
|
677
|
-
if (existing) {
|
|
678
|
-
return existing.id;
|
|
679
|
-
}
|
|
680
|
-
const created = await apiPost(`/accounts/${accountId}/storage/kv/namespaces`, { title: DEVFLARE_KV_NAMESPACE_TITLE2 });
|
|
681
|
-
return created.id;
|
|
682
|
-
}
|
|
683
|
-
function getTodayDate() {
|
|
684
|
-
return new Date().toISOString().split("T")[0];
|
|
685
|
-
}
|
|
686
|
-
function buildUsageKey(service, date) {
|
|
687
|
-
return `${USAGE_KEY_PREFIX}${service}:${date}`;
|
|
688
|
-
}
|
|
689
|
-
async function getUsage(accountId, service, date) {
|
|
690
|
-
const targetDate = date ?? getTodayDate();
|
|
691
|
-
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
692
|
-
const key = buildUsageKey(service, targetDate);
|
|
693
|
-
const value = await kvGet(accountId, namespaceId, key);
|
|
694
|
-
if (value === null) {
|
|
695
|
-
return null;
|
|
696
|
-
}
|
|
697
|
-
try {
|
|
698
|
-
return JSON.parse(value);
|
|
699
|
-
} catch {
|
|
700
|
-
return null;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
async function recordUsage(accountId, service, count = 1) {
|
|
704
|
-
const today = getTodayDate();
|
|
705
|
-
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
706
|
-
const key = buildUsageKey(service, today);
|
|
707
|
-
const existing = await getUsage(accountId, service, today);
|
|
708
|
-
const currentCount = existing?.count ?? 0;
|
|
709
|
-
const record = {
|
|
710
|
-
service,
|
|
711
|
-
date: today,
|
|
712
|
-
count: currentCount + count,
|
|
713
|
-
updatedAt: new Date().toISOString()
|
|
714
|
-
};
|
|
715
|
-
await kvPut(accountId, namespaceId, key, JSON.stringify(record));
|
|
716
|
-
return record;
|
|
717
|
-
}
|
|
718
|
-
async function resetUsage(accountId, service) {
|
|
719
|
-
const today = getTodayDate();
|
|
720
|
-
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
721
|
-
const key = buildUsageKey(service, today);
|
|
722
|
-
const record = {
|
|
723
|
-
service,
|
|
724
|
-
date: today,
|
|
725
|
-
count: 0,
|
|
726
|
-
updatedAt: new Date().toISOString()
|
|
727
|
-
};
|
|
728
|
-
await kvPut(accountId, namespaceId, key, JSON.stringify(record));
|
|
729
|
-
}
|
|
730
|
-
async function getLimits(accountId) {
|
|
731
|
-
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
732
|
-
const value = await kvGet(accountId, namespaceId, LIMITS_KEY);
|
|
733
|
-
if (value === null) {
|
|
734
|
-
return DEFAULT_LIMITS;
|
|
735
|
-
}
|
|
736
|
-
try {
|
|
737
|
-
return { ...DEFAULT_LIMITS, ...JSON.parse(value) };
|
|
738
|
-
} catch {
|
|
739
|
-
return DEFAULT_LIMITS;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
async function setLimits(accountId, limits) {
|
|
743
|
-
const namespaceId = await getOrCreateUsageNamespace(accountId);
|
|
744
|
-
const current = await getLimits(accountId);
|
|
745
|
-
const updated = {
|
|
746
|
-
...current,
|
|
747
|
-
...limits
|
|
748
|
-
};
|
|
749
|
-
await kvPut(accountId, namespaceId, LIMITS_KEY, JSON.stringify(updated));
|
|
750
|
-
return updated;
|
|
751
|
-
}
|
|
752
|
-
async function setLimitsEnabled(accountId, enabled) {
|
|
753
|
-
return setLimits(accountId, { enabled });
|
|
754
|
-
}
|
|
755
|
-
async function isWithinLimits(accountId, service) {
|
|
756
|
-
const limits = await getLimits(accountId);
|
|
757
|
-
if (!limits.enabled) {
|
|
758
|
-
return true;
|
|
759
|
-
}
|
|
760
|
-
const usage = await getUsage(accountId, service);
|
|
761
|
-
const currentCount = usage?.count ?? 0;
|
|
762
|
-
switch (service) {
|
|
763
|
-
case "ai":
|
|
764
|
-
if (limits.aiRequestsPerDay && currentCount >= limits.aiRequestsPerDay) {
|
|
765
|
-
return false;
|
|
766
|
-
}
|
|
767
|
-
return true;
|
|
768
|
-
case "vectorize":
|
|
769
|
-
if (limits.vectorizeOpsPerDay && currentCount >= limits.vectorizeOpsPerDay) {
|
|
770
|
-
return false;
|
|
771
|
-
}
|
|
772
|
-
return true;
|
|
773
|
-
default:
|
|
774
|
-
return true;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
async function getUsageSummary(accountId, service) {
|
|
778
|
-
const limits = await getLimits(accountId);
|
|
779
|
-
const usage = await getUsage(accountId, service);
|
|
780
|
-
const currentCount = usage?.count ?? 0;
|
|
781
|
-
let limit;
|
|
782
|
-
switch (service) {
|
|
783
|
-
case "ai":
|
|
784
|
-
limit = limits.aiRequestsPerDay;
|
|
785
|
-
break;
|
|
786
|
-
case "vectorize":
|
|
787
|
-
limit = limits.vectorizeOpsPerDay;
|
|
788
|
-
break;
|
|
789
|
-
}
|
|
790
|
-
const withinLimit = limit === undefined || currentCount < limit;
|
|
791
|
-
const percentUsed = limit ? currentCount / limit * 100 : undefined;
|
|
792
|
-
return {
|
|
793
|
-
service,
|
|
794
|
-
today: currentCount,
|
|
795
|
-
limit,
|
|
796
|
-
withinLimit,
|
|
797
|
-
percentUsed
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
async function getAllUsageSummaries(accountId) {
|
|
801
|
-
const trackedServices = ["ai", "vectorize"];
|
|
802
|
-
return Promise.all(trackedServices.map((s) => getUsageSummary(accountId, s)));
|
|
803
|
-
}
|
|
804
|
-
async function canProceedWithTest(accountId, service) {
|
|
805
|
-
const limits = await getLimits(accountId);
|
|
806
|
-
if (!limits.enabled) {
|
|
807
|
-
return { allowed: true };
|
|
808
|
-
}
|
|
809
|
-
const withinLimits = await isWithinLimits(accountId, service);
|
|
810
|
-
if (!withinLimits) {
|
|
811
|
-
const summary = await getUsageSummary(accountId, service);
|
|
812
|
-
return {
|
|
813
|
-
allowed: false,
|
|
814
|
-
reason: `Daily limit exceeded for ${service}: ${summary.today}/${summary.limit} (${summary.percentUsed?.toFixed(1)}%)`
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
return { allowed: true };
|
|
818
|
-
}
|
|
819
|
-
async function recordTestUsage(accountId, service, count = 1) {
|
|
820
|
-
await recordUsage(accountId, service, count);
|
|
821
|
-
}
|
|
822
|
-
async function shouldSkip(service) {
|
|
823
|
-
try {
|
|
824
|
-
const isAuth = await isAuthenticated();
|
|
825
|
-
if (!isAuth) {
|
|
826
|
-
console.log(`⏭️ ${service.toUpperCase()} tests skipped: Not authenticated. Run: bunx wrangler login`);
|
|
827
|
-
return true;
|
|
828
|
-
}
|
|
829
|
-
const primary = await getPrimaryAccount();
|
|
830
|
-
if (!primary) {
|
|
831
|
-
console.log(`⏭️ ${service.toUpperCase()} tests skipped: No Cloudflare account found`);
|
|
832
|
-
return true;
|
|
833
|
-
}
|
|
834
|
-
const { accountId } = await getEffectiveAccountId(primary.id);
|
|
835
|
-
try {
|
|
836
|
-
const { allowed, reason } = await canProceedWithTest(accountId, service);
|
|
837
|
-
if (!allowed) {
|
|
838
|
-
console.log(`⏭️ ${service.toUpperCase()} tests skipped: ${reason}`);
|
|
839
|
-
return true;
|
|
840
|
-
}
|
|
841
|
-
} catch {}
|
|
842
|
-
return false;
|
|
843
|
-
} catch (error) {
|
|
844
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
845
|
-
console.log(`⏭️ ${service.toUpperCase()} tests skipped: ${message}`);
|
|
846
|
-
return true;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
export { hasWranglerConfig, getWranglerAuth, getApiToken, isAuthenticated, CloudflareAPIError, AuthenticationError, getAccounts, getPrimaryAccount, getAccountById, listWorkers, listKVNamespaces, listD1Databases, listR2Buckets, listVectorizeIndexes, listAIModels, getServiceStatus, getAllServiceStatus, hasService, getAccountSummary, getWorkspaceAccountId, setWorkspaceAccountId, getGlobalDefaultAccountId, setGlobalDefaultAccountId, getEffectiveAccountId, clearGlobalDefaultAccountId, getUsage, recordUsage, resetUsage, getLimits, setLimits, setLimitsEnabled, isWithinLimits, getUsageSummary, getAllUsageSummaries, canProceedWithTest, recordTestUsage, shouldSkip };
|
|
664
|
+
export { hasWranglerConfig, getWranglerAuth, getApiToken, isAuthenticated, CloudflareAPIError, AuthenticationError, apiGet, apiPost, kvGet, kvPut, getAccounts, getPrimaryAccount, getAccountById, listWorkers, listKVNamespaces, listD1Databases, listR2Buckets, listVectorizeIndexes, listAIModels, getServiceStatus, getAllServiceStatus, hasService, getAccountSummary, getWorkspaceAccountId, setWorkspaceAccountId, getGlobalDefaultAccountId, setGlobalDefaultAccountId, getEffectiveAccountId, clearGlobalDefaultAccountId };
|