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.
Files changed (54) hide show
  1. package/LLM.md +36 -1
  2. package/README.md +85 -0
  3. package/dist/{account-8psavtg6.js → account-spa7gzsn.js} +3 -2
  4. package/dist/bridge/miniflare.d.ts.map +1 -1
  5. package/dist/browser.d.ts +7 -0
  6. package/dist/browser.d.ts.map +1 -1
  7. package/dist/{build-e6kgjwr8.js → build-zv25ke4s.js} +10 -9
  8. package/dist/bundler/do-bundler.d.ts.map +1 -1
  9. package/dist/bundler/worker-bundler.d.ts.map +1 -1
  10. package/dist/bundler/worker-compat.d.ts +4 -0
  11. package/dist/bundler/worker-compat.d.ts.map +1 -0
  12. package/dist/cli/commands/build.d.ts.map +1 -1
  13. package/dist/cli/commands/config.d.ts +4 -0
  14. package/dist/cli/commands/config.d.ts.map +1 -0
  15. package/dist/cli/commands/deploy.d.ts.map +1 -1
  16. package/dist/cli/index.d.ts.map +1 -1
  17. package/dist/config/compiler.d.ts.map +1 -1
  18. package/dist/config/index.d.ts +3 -2
  19. package/dist/config/index.d.ts.map +1 -1
  20. package/dist/config/loader.d.ts +1 -0
  21. package/dist/config/loader.d.ts.map +1 -1
  22. package/dist/config/resource-resolution.d.ts +44 -0
  23. package/dist/config/resource-resolution.d.ts.map +1 -0
  24. package/dist/config/schema.d.ts +193 -28
  25. package/dist/config/schema.d.ts.map +1 -1
  26. package/dist/config-v9tr4rts.js +57 -0
  27. package/dist/{deploy-eeaqwxaa.js → deploy-6xmqvv06.js} +10 -9
  28. package/dist/dev-server/server.d.ts.map +1 -1
  29. package/dist/{dev-mqsffeeb.js → dev-ymtphbkg.js} +11 -5
  30. package/dist/{doctor-z4ffybce.js → doctor-xv4gm1h4.js} +3 -2
  31. package/dist/{index-nb0bqtx7.js → index-001mw014.js} +308 -2
  32. package/dist/{index-xxwbb2nt.js → index-0rsa2c1t.js} +5 -2
  33. package/dist/{index-0kzg8wed.js → index-3a4mmn57.js} +12 -6
  34. package/dist/{index-rfhx0yd5.js → index-5s1bz1e0.js} +12 -12
  35. package/dist/{index-n7rs26ft.js → index-6nb7w45m.js} +15 -13
  36. package/dist/index-7bq4xq84.js +197 -0
  37. package/dist/{index-dr6sbp8d.js → index-k8vh558d.js} +1 -1
  38. package/dist/{index-wyf3s77s.js → index-tksw7gpy.js} +162 -2
  39. package/dist/{index-8x16kn47.js → index-v43z02tr.js} +18 -8
  40. package/dist/{index-tfyxa77h.js → index-xdq9ery1.js} +1 -187
  41. package/dist/{index-zbvmtcn2.js → index-zvgc3e0c.js} +25 -19
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/src/browser.js +17 -0
  45. package/dist/src/cli/index.js +1 -1
  46. package/dist/src/cloudflare/index.js +3 -2
  47. package/dist/src/index.js +16 -7
  48. package/dist/src/sveltekit/index.js +4 -3
  49. package/dist/src/test/index.js +6 -5
  50. package/dist/src/vite/index.js +4 -3
  51. package/dist/test/simple-context.d.ts.map +1 -1
  52. package/dist/{types-sffr9681.js → types-158m16vd.js} +4 -3
  53. package/dist/vite/plugin.d.ts.map +1 -1
  54. 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,6 +1,6 @@
1
1
  import {
2
2
  resolveConfigPath
3
- } from "./index-wyf3s77s.js";
3
+ } from "./index-tksw7gpy.js";
4
4
 
5
5
  // src/cli/config-path.ts
6
6
  import { stat } from "node:fs/promises";
@@ -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(), z.string()).optional(),
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
- export { configSchema, normalizeDOBinding, resolveConfigPath, loadConfig, ConfigNotFoundError, ConfigValidationError };
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 doctor
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-mqsffeeb.js");
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-e6kgjwr8.js");
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-eeaqwxaa.js");
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-sffr9681.js");
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-z4ffybce.js");
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-8psavtg6.js");
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
- // src/cloudflare/usage.ts
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 };