localflare-core 0.1.2 → 0.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rohan Prasad
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { Miniflare } from 'miniflare';
2
-
3
1
  interface WranglerConfig {
4
2
  name?: string;
5
3
  main?: string;
@@ -62,56 +60,74 @@ interface DiscoveredBindings {
62
60
  };
63
61
  vars: Record<string, string>;
64
62
  }
65
- interface WorkerLogEntry {
66
- level: 'log' | 'info' | 'warn' | 'error' | 'debug';
67
- message: string;
68
- }
69
- type WorkerLogCallback = (entry: WorkerLogEntry) => void;
70
- interface LocalFlareOptions {
71
- configPath?: string;
72
- port?: number;
73
- dashboardPort?: number;
74
- persistPath?: string;
75
- verbose?: boolean;
76
- onWorkerLog?: WorkerLogCallback;
77
- }
78
63
  interface BindingInfo {
79
64
  type: 'D1' | 'KV' | 'R2' | 'DO' | 'Queue' | 'Var';
80
65
  name: string;
81
66
  details: Record<string, unknown>;
82
67
  }
83
-
84
- declare class LocalFlare {
85
- private mf;
86
- private config;
87
- private bindings;
88
- private options;
89
- constructor(options?: LocalFlareOptions);
90
- start(): Promise<void>;
91
- stop(): Promise<void>;
92
- getD1Database(bindingName: string): Promise<any>;
93
- getKVNamespace(bindingName: string): Promise<any>;
94
- getR2Bucket(bindingName: string): Promise<any>;
95
- getDurableObjectNamespace(bindingName: string): Promise<any>;
96
- getQueueProducer<T = unknown>(bindingName: string): Promise<any>;
97
- getAllBindings<T = Record<string, unknown>>(): Promise<T>;
98
- getDiscoveredBindings(): DiscoveredBindings | null;
99
- getConfig(): WranglerConfig | null;
100
- getOptions(): Required<LocalFlareOptions>;
101
- getMiniflare(): Miniflare | null;
102
- isRunning(): boolean;
103
- setLogCallback(callback: WorkerLogCallback): void;
104
- private ensureRunning;
68
+ /**
69
+ * Manifest passed to the Localflare API worker via environment variable.
70
+ * Contains binding information discovered from user's wrangler.toml.
71
+ * Used by both CLI (to generate) and API (to consume).
72
+ */
73
+ interface LocalflareManifest {
74
+ name: string;
75
+ d1: {
76
+ binding: string;
77
+ database_name: string;
78
+ }[];
79
+ kv: {
80
+ binding: string;
81
+ }[];
82
+ r2: {
83
+ binding: string;
84
+ bucket_name: string;
85
+ }[];
86
+ queues: {
87
+ producers: {
88
+ binding: string;
89
+ queue: string;
90
+ }[];
91
+ consumers: {
92
+ queue: string;
93
+ max_batch_size?: number;
94
+ max_batch_timeout?: number;
95
+ max_retries?: number;
96
+ dead_letter_queue?: string;
97
+ }[];
98
+ };
99
+ do: {
100
+ binding: string;
101
+ className: string;
102
+ }[];
105
103
  }
106
104
 
107
105
  declare const WRANGLER_CONFIG_FILES: string[];
108
106
  /**
109
107
  * Find wrangler config file in a directory
110
- * Returns the path to the first found config file, or null if none found
108
+ * Searches for config files in priority order: toml > json > jsonc
109
+ * @param directory - Directory to search in
110
+ * @returns Path to the first found config file, or null if none found
111
111
  */
112
112
  declare function findWranglerConfig(directory: string): string | null;
113
+ /**
114
+ * Parse wrangler configuration file (TOML, JSON, or JSONC format)
115
+ * @param configPath - Path to the configuration file
116
+ * @returns Parsed configuration object
117
+ * @throws Error if the file doesn't exist or cannot be parsed
118
+ */
113
119
  declare function parseWranglerConfig(configPath: string): WranglerConfig;
120
+ /**
121
+ * Discover and extract all bindings from a wrangler configuration
122
+ * @param config - Parsed wrangler configuration
123
+ * @returns Discovered bindings organized by type
124
+ */
114
125
  declare function discoverBindings(config: WranglerConfig): DiscoveredBindings;
126
+ /**
127
+ * Generate a human-readable summary of discovered bindings
128
+ * @param bindings - Discovered bindings to summarize
129
+ * @returns Array of summary strings, one per binding type
130
+ */
115
131
  declare function getBindingSummary(bindings: DiscoveredBindings): string[];
116
132
 
117
- export { type BindingInfo, type D1DatabaseConfig, type DiscoveredBindings, type DurableObjectBinding, type KVNamespaceConfig, LocalFlare, type LocalFlareOptions, type QueueConsumerConfig, type QueueProducerConfig, type R2BucketConfig, WRANGLER_CONFIG_FILES, type WorkerLogCallback, type WorkerLogEntry, type WranglerConfig, discoverBindings, findWranglerConfig, getBindingSummary, parseWranglerConfig };
133
+ export { type BindingInfo, type D1DatabaseConfig, type DiscoveredBindings, type DurableObjectBinding, type KVNamespaceConfig, type LocalflareManifest, type QueueConsumerConfig, type QueueProducerConfig, type R2BucketConfig, WRANGLER_CONFIG_FILES, type WranglerConfig, discoverBindings, findWranglerConfig, getBindingSummary, parseWranglerConfig };
package/dist/index.js CHANGED
@@ -1,78 +1,13 @@
1
- // src/localflare.ts
2
- import { Miniflare } from "miniflare";
3
- import { resolve as resolve2, dirname } from "path";
4
- import * as esbuild from "esbuild";
5
- import * as readline from "readline";
6
-
7
1
  // src/config.ts
8
2
  import { readFileSync, existsSync } from "fs";
9
3
  import { resolve, extname } from "path";
10
4
  import { parse as parseToml } from "toml";
5
+ import parseJsonc from "tiny-jsonc";
11
6
  var WRANGLER_CONFIG_FILES = [
12
7
  "wrangler.toml",
13
8
  "wrangler.json",
14
9
  "wrangler.jsonc"
15
10
  ];
16
- function stripJsonComments(content) {
17
- let result = "";
18
- let inString = false;
19
- let inSingleLineComment = false;
20
- let inMultiLineComment = false;
21
- let i = 0;
22
- while (i < content.length) {
23
- const char = content[i];
24
- const nextChar = content[i + 1];
25
- if (inSingleLineComment) {
26
- if (char === "\n") {
27
- inSingleLineComment = false;
28
- result += char;
29
- }
30
- i++;
31
- continue;
32
- }
33
- if (inMultiLineComment) {
34
- if (char === "*" && nextChar === "/") {
35
- inMultiLineComment = false;
36
- i += 2;
37
- continue;
38
- }
39
- i++;
40
- continue;
41
- }
42
- if (inString) {
43
- result += char;
44
- if (char === "\\" && i + 1 < content.length) {
45
- result += nextChar;
46
- i += 2;
47
- continue;
48
- }
49
- if (char === '"') {
50
- inString = false;
51
- }
52
- i++;
53
- continue;
54
- }
55
- if (char === '"') {
56
- inString = true;
57
- result += char;
58
- i++;
59
- continue;
60
- }
61
- if (char === "/" && nextChar === "/") {
62
- inSingleLineComment = true;
63
- i += 2;
64
- continue;
65
- }
66
- if (char === "/" && nextChar === "*") {
67
- inMultiLineComment = true;
68
- i += 2;
69
- continue;
70
- }
71
- result += char;
72
- i++;
73
- }
74
- return result;
75
- }
76
11
  function findWranglerConfig(directory) {
77
12
  for (const filename of WRANGLER_CONFIG_FILES) {
78
13
  const configPath = resolve(directory, filename);
@@ -95,13 +30,14 @@ function parseWranglerConfig(configPath) {
95
30
  case ".json":
96
31
  return JSON.parse(content);
97
32
  case ".jsonc":
98
- return JSON.parse(stripJsonComments(content));
99
- default:
33
+ return parseJsonc.parse(content);
34
+ default: {
100
35
  const trimmed = content.trim();
101
36
  if (trimmed.startsWith("{")) {
102
- return JSON.parse(stripJsonComments(content));
37
+ return parseJsonc.parse(content);
103
38
  }
104
39
  return parseToml(content);
40
+ }
105
41
  }
106
42
  }
107
43
  function discoverBindings(config) {
@@ -120,239 +56,41 @@ function discoverBindings(config) {
120
56
  function getBindingSummary(bindings) {
121
57
  const summary = [];
122
58
  if (bindings.d1.length > 0) {
123
- summary.push(`D1 Databases: ${bindings.d1.map((d) => d.binding).join(", ")}`);
59
+ summary.push(
60
+ `D1 Databases: ${bindings.d1.map((d) => d.binding).join(", ")}`
61
+ );
124
62
  }
125
63
  if (bindings.kv.length > 0) {
126
- summary.push(`KV Namespaces: ${bindings.kv.map((k) => k.binding).join(", ")}`);
64
+ summary.push(
65
+ `KV Namespaces: ${bindings.kv.map((k) => k.binding).join(", ")}`
66
+ );
127
67
  }
128
68
  if (bindings.r2.length > 0) {
129
69
  summary.push(`R2 Buckets: ${bindings.r2.map((r) => r.binding).join(", ")}`);
130
70
  }
131
71
  if (bindings.durableObjects.length > 0) {
132
- summary.push(`Durable Objects: ${bindings.durableObjects.map((d) => d.name).join(", ")}`);
72
+ summary.push(
73
+ `Durable Objects: ${bindings.durableObjects.map((d) => d.name).join(", ")}`
74
+ );
133
75
  }
134
76
  if (bindings.queues.producers.length > 0) {
135
- summary.push(`Queue Producers: ${bindings.queues.producers.map((q) => q.binding).join(", ")}`);
77
+ summary.push(
78
+ `Queue Producers: ${bindings.queues.producers.map((q) => q.binding).join(", ")}`
79
+ );
136
80
  }
137
81
  if (bindings.queues.consumers.length > 0) {
138
- summary.push(`Queue Consumers: ${bindings.queues.consumers.map((q) => q.queue).join(", ")}`);
82
+ summary.push(
83
+ `Queue Consumers: ${bindings.queues.consumers.map((q) => q.queue).join(", ")}`
84
+ );
139
85
  }
140
86
  if (Object.keys(bindings.vars).length > 0) {
141
- summary.push(`Environment Variables: ${Object.keys(bindings.vars).join(", ")}`);
87
+ summary.push(
88
+ `Environment Variables: ${Object.keys(bindings.vars).join(", ")}`
89
+ );
142
90
  }
143
91
  return summary;
144
92
  }
145
-
146
- // src/localflare.ts
147
- var LocalFlare = class {
148
- mf = null;
149
- config = null;
150
- bindings = null;
151
- options;
152
- constructor(options = {}) {
153
- let configPath = options.configPath;
154
- if (!configPath) {
155
- const detected = findWranglerConfig(process.cwd());
156
- configPath = detected ?? "./wrangler.toml";
157
- }
158
- this.options = {
159
- configPath,
160
- port: options.port ?? 8787,
161
- dashboardPort: options.dashboardPort ?? 8788,
162
- persistPath: options.persistPath ?? ".localflare",
163
- verbose: options.verbose ?? false,
164
- onWorkerLog: options.onWorkerLog ?? (() => {
165
- })
166
- };
167
- }
168
- async start() {
169
- const configPath = resolve2(this.options.configPath);
170
- const rootDir = dirname(configPath);
171
- this.config = parseWranglerConfig(configPath);
172
- this.bindings = discoverBindings(this.config);
173
- if (this.options.verbose) {
174
- console.log("\n\u{1F4E6} Discovered bindings:");
175
- getBindingSummary(this.bindings).forEach((line) => {
176
- console.log(` ${line}`);
177
- });
178
- console.log("");
179
- }
180
- const persistRoot = resolve2(rootDir, this.options.persistPath);
181
- const entryPoint = resolve2(rootDir, this.config.main || "src/index.ts");
182
- const buildResult = await esbuild.build({
183
- entryPoints: [entryPoint],
184
- bundle: true,
185
- format: "esm",
186
- target: "es2022",
187
- write: false,
188
- minify: false,
189
- sourcemap: false,
190
- conditions: ["workerd", "worker", "browser"],
191
- external: ["cloudflare:*", "node:*"]
192
- });
193
- const scriptContent = buildResult.outputFiles?.[0]?.text;
194
- if (!scriptContent) {
195
- throw new Error("Failed to compile worker script");
196
- }
197
- const d1Databases = Object.fromEntries(
198
- this.bindings.d1.map((d) => [d.binding, d.database_id])
199
- );
200
- const kvNamespaces = Object.fromEntries(
201
- this.bindings.kv.map((k) => [k.binding, k.id])
202
- );
203
- const r2Buckets = Object.fromEntries(
204
- this.bindings.r2.map((r) => [r.binding, r.bucket_name])
205
- );
206
- const queueProducers = Object.fromEntries(
207
- this.bindings.queues.producers.map((q) => [q.binding, q.queue])
208
- );
209
- const queueConsumers = Object.fromEntries(
210
- this.bindings.queues.consumers.map((c) => [
211
- c.queue,
212
- {
213
- maxBatchSize: c.max_batch_size ?? 10,
214
- maxBatchTimeout: c.max_batch_timeout ?? 5,
215
- maxRetries: c.max_retries ?? 3,
216
- deadLetterQueue: c.dead_letter_queue
217
- }
218
- ])
219
- );
220
- const durableObjects = Object.fromEntries(
221
- this.bindings.durableObjects.map((d) => [d.name, d.class_name])
222
- );
223
- const handleRuntimeStdio = (stdout, stderr) => {
224
- if (this.options.verbose) {
225
- console.log("[LocalFlare] Runtime stdio handler connected");
226
- }
227
- const processStream = (stream, level) => {
228
- const rl = readline.createInterface({ input: stream });
229
- rl.on("line", (line) => {
230
- if (!line.trim()) return;
231
- let logLevel = level === "error" ? "error" : "log";
232
- if (line.includes("[warn]") || line.includes("WARNING")) {
233
- logLevel = "warn";
234
- } else if (line.includes("[info]") || line.includes("INFO")) {
235
- logLevel = "info";
236
- } else if (line.includes("[debug]") || line.includes("DEBUG")) {
237
- logLevel = "debug";
238
- } else if (line.includes("[error]") || line.includes("ERROR")) {
239
- logLevel = "error";
240
- }
241
- this.options.onWorkerLog({ level: logLevel, message: line });
242
- });
243
- };
244
- processStream(stdout, "log");
245
- processStream(stderr, "error");
246
- };
247
- const miniflareOptions = {
248
- modules: [
249
- {
250
- type: "ESModule",
251
- path: entryPoint,
252
- contents: scriptContent
253
- }
254
- ],
255
- port: this.options.port,
256
- verbose: this.options.verbose,
257
- kvPersist: `${persistRoot}/kv`,
258
- d1Persist: `${persistRoot}/d1`,
259
- r2Persist: `${persistRoot}/r2`,
260
- durableObjectsPersist: `${persistRoot}/do`,
261
- cachePersist: `${persistRoot}/cache`,
262
- // Explicit binding configurations
263
- bindings: this.bindings.vars,
264
- // Capture worker stdout/stderr for logging
265
- handleRuntimeStdio,
266
- // Use plain text logs instead of structured JSON for easier parsing
267
- structuredWorkerdLogs: false
268
- };
269
- if (Object.keys(d1Databases).length > 0) {
270
- miniflareOptions.d1Databases = d1Databases;
271
- }
272
- if (Object.keys(kvNamespaces).length > 0) {
273
- miniflareOptions.kvNamespaces = kvNamespaces;
274
- }
275
- if (Object.keys(r2Buckets).length > 0) {
276
- miniflareOptions.r2Buckets = r2Buckets;
277
- }
278
- if (Object.keys(queueProducers).length > 0) {
279
- miniflareOptions.queueProducers = queueProducers;
280
- }
281
- if (Object.keys(queueConsumers).length > 0) {
282
- miniflareOptions.queueConsumers = queueConsumers;
283
- }
284
- if (Object.keys(durableObjects).length > 0) {
285
- miniflareOptions.durableObjects = durableObjects;
286
- }
287
- this.mf = new Miniflare(miniflareOptions);
288
- const url = await this.mf.ready;
289
- console.log(`\u26A1 Worker running at ${url}`);
290
- }
291
- async stop() {
292
- if (this.mf) {
293
- await this.mf.dispose();
294
- this.mf = null;
295
- }
296
- }
297
- // Binding accessors - use generic types for portability
298
- // Consumers can cast to @cloudflare/workers-types if needed
299
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
300
- async getD1Database(bindingName) {
301
- this.ensureRunning();
302
- return this.mf.getD1Database(bindingName);
303
- }
304
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
305
- async getKVNamespace(bindingName) {
306
- this.ensureRunning();
307
- return this.mf.getKVNamespace(bindingName);
308
- }
309
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
310
- async getR2Bucket(bindingName) {
311
- this.ensureRunning();
312
- return this.mf.getR2Bucket(bindingName);
313
- }
314
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
315
- async getDurableObjectNamespace(bindingName) {
316
- this.ensureRunning();
317
- return this.mf.getDurableObjectNamespace(bindingName);
318
- }
319
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
- async getQueueProducer(bindingName) {
321
- this.ensureRunning();
322
- return this.mf.getQueueProducer(bindingName);
323
- }
324
- async getAllBindings() {
325
- this.ensureRunning();
326
- return this.mf.getBindings();
327
- }
328
- // Getters
329
- getDiscoveredBindings() {
330
- return this.bindings;
331
- }
332
- getConfig() {
333
- return this.config;
334
- }
335
- getOptions() {
336
- return this.options;
337
- }
338
- getMiniflare() {
339
- return this.mf;
340
- }
341
- isRunning() {
342
- return this.mf !== null;
343
- }
344
- // Set the worker log callback (must be called before start())
345
- setLogCallback(callback) {
346
- this.options.onWorkerLog = callback;
347
- }
348
- ensureRunning() {
349
- if (!this.mf) {
350
- throw new Error("LocalFlare is not running. Call start() first.");
351
- }
352
- }
353
- };
354
93
  export {
355
- LocalFlare,
356
94
  WRANGLER_CONFIG_FILES,
357
95
  discoverBindings,
358
96
  findWranglerConfig,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/localflare.ts","../src/config.ts"],"sourcesContent":["import { Miniflare } from 'miniflare'\nimport { resolve, dirname } from 'node:path'\nimport * as esbuild from 'esbuild'\nimport { Readable } from 'node:stream'\nimport * as readline from 'node:readline'\nimport { parseWranglerConfig, discoverBindings, getBindingSummary, findWranglerConfig } from './config.js'\nimport type { LocalFlareOptions, DiscoveredBindings, WranglerConfig, WorkerLogCallback } from './types.js'\n\nexport class LocalFlare {\n private mf: Miniflare | null = null\n private config: WranglerConfig | null = null\n private bindings: DiscoveredBindings | null = null\n private options: Required<LocalFlareOptions>\n\n constructor(options: LocalFlareOptions = {}) {\n // Auto-detect config if not provided\n let configPath = options.configPath\n if (!configPath) {\n const detected = findWranglerConfig(process.cwd())\n configPath = detected ?? './wrangler.toml' // Fallback for error messaging\n }\n\n this.options = {\n configPath,\n port: options.port ?? 8787,\n dashboardPort: options.dashboardPort ?? 8788,\n persistPath: options.persistPath ?? '.localflare',\n verbose: options.verbose ?? false,\n onWorkerLog: options.onWorkerLog ?? (() => {}),\n }\n }\n\n async start(): Promise<void> {\n const configPath = resolve(this.options.configPath)\n const rootDir = dirname(configPath)\n\n // Parse wrangler.toml\n this.config = parseWranglerConfig(configPath)\n this.bindings = discoverBindings(this.config)\n\n // Log discovered bindings\n if (this.options.verbose) {\n console.log('\\nšŸ“¦ Discovered bindings:')\n getBindingSummary(this.bindings).forEach(line => {\n console.log(` ${line}`)\n })\n console.log('')\n }\n\n // Build Miniflare options\n const persistRoot = resolve(rootDir, this.options.persistPath)\n\n // Compile the worker entry point with esbuild\n const entryPoint = resolve(rootDir, this.config.main || 'src/index.ts')\n const buildResult = await esbuild.build({\n entryPoints: [entryPoint],\n bundle: true,\n format: 'esm',\n target: 'es2022',\n write: false,\n minify: false,\n sourcemap: false,\n conditions: ['workerd', 'worker', 'browser'],\n external: ['cloudflare:*', 'node:*'],\n })\n\n const scriptContent = buildResult.outputFiles?.[0]?.text\n if (!scriptContent) {\n throw new Error('Failed to compile worker script')\n }\n\n // Build explicit binding configurations from parsed config\n // Miniflare v3 expects bindings as objects keyed by binding name\n const d1Databases = Object.fromEntries(\n this.bindings.d1.map(d => [d.binding, d.database_id])\n )\n\n const kvNamespaces = Object.fromEntries(\n this.bindings.kv.map(k => [k.binding, k.id])\n )\n\n const r2Buckets = Object.fromEntries(\n this.bindings.r2.map(r => [r.binding, r.bucket_name])\n )\n\n const queueProducers = Object.fromEntries(\n this.bindings.queues.producers.map(q => [q.binding, q.queue])\n )\n\n // Queue consumers configuration for Miniflare\n // Maps queue name to consumer options\n const queueConsumers = Object.fromEntries(\n this.bindings.queues.consumers.map(c => [\n c.queue,\n {\n maxBatchSize: c.max_batch_size ?? 10,\n maxBatchTimeout: c.max_batch_timeout ?? 5,\n maxRetries: c.max_retries ?? 3,\n deadLetterQueue: c.dead_letter_queue,\n },\n ])\n )\n\n const durableObjects = Object.fromEntries(\n this.bindings.durableObjects.map(d => [d.name, d.class_name])\n )\n\n // Create a handler for worker stdout/stderr to capture logs\n // Use arrow function to capture 'this' and access current callback\n const handleRuntimeStdio = (stdout: Readable, stderr: Readable) => {\n if (this.options.verbose) {\n console.log('[LocalFlare] Runtime stdio handler connected')\n }\n\n const processStream = (stream: Readable, level: 'log' | 'error') => {\n const rl = readline.createInterface({ input: stream })\n rl.on('line', (line) => {\n // Skip empty lines\n if (!line.trim()) return\n\n // Determine log level from content\n let logLevel: 'log' | 'info' | 'warn' | 'error' | 'debug' = level === 'error' ? 'error' : 'log'\n if (line.includes('[warn]') || line.includes('WARNING')) {\n logLevel = 'warn'\n } else if (line.includes('[info]') || line.includes('INFO')) {\n logLevel = 'info'\n } else if (line.includes('[debug]') || line.includes('DEBUG')) {\n logLevel = 'debug'\n } else if (line.includes('[error]') || line.includes('ERROR')) {\n logLevel = 'error'\n }\n\n // Access callback via this.options to allow setting it after start()\n this.options.onWorkerLog({ level: logLevel, message: line })\n })\n }\n\n processStream(stdout, 'log')\n processStream(stderr, 'error')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const miniflareOptions: any = {\n modules: [\n {\n type: 'ESModule',\n path: entryPoint,\n contents: scriptContent,\n },\n ],\n port: this.options.port,\n verbose: this.options.verbose,\n kvPersist: `${persistRoot}/kv`,\n d1Persist: `${persistRoot}/d1`,\n r2Persist: `${persistRoot}/r2`,\n durableObjectsPersist: `${persistRoot}/do`,\n cachePersist: `${persistRoot}/cache`,\n // Explicit binding configurations\n bindings: this.bindings.vars,\n // Capture worker stdout/stderr for logging\n handleRuntimeStdio,\n // Use plain text logs instead of structured JSON for easier parsing\n structuredWorkerdLogs: false,\n }\n\n // Only add bindings if they exist\n if (Object.keys(d1Databases).length > 0) {\n miniflareOptions.d1Databases = d1Databases\n }\n if (Object.keys(kvNamespaces).length > 0) {\n miniflareOptions.kvNamespaces = kvNamespaces\n }\n if (Object.keys(r2Buckets).length > 0) {\n miniflareOptions.r2Buckets = r2Buckets\n }\n if (Object.keys(queueProducers).length > 0) {\n miniflareOptions.queueProducers = queueProducers\n }\n if (Object.keys(queueConsumers).length > 0) {\n miniflareOptions.queueConsumers = queueConsumers\n }\n if (Object.keys(durableObjects).length > 0) {\n miniflareOptions.durableObjects = durableObjects\n }\n\n this.mf = new Miniflare(miniflareOptions)\n\n // Wait for Miniflare to be ready\n const url = await this.mf.ready\n console.log(`⚔ Worker running at ${url}`)\n }\n\n async stop(): Promise<void> {\n if (this.mf) {\n await this.mf.dispose()\n this.mf = null\n }\n }\n\n // Binding accessors - use generic types for portability\n // Consumers can cast to @cloudflare/workers-types if needed\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async getD1Database(bindingName: string): Promise<any> {\n this.ensureRunning()\n return this.mf!.getD1Database(bindingName)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async getKVNamespace(bindingName: string): Promise<any> {\n this.ensureRunning()\n return this.mf!.getKVNamespace(bindingName)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async getR2Bucket(bindingName: string): Promise<any> {\n this.ensureRunning()\n return this.mf!.getR2Bucket(bindingName)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async getDurableObjectNamespace(bindingName: string): Promise<any> {\n this.ensureRunning()\n return this.mf!.getDurableObjectNamespace(bindingName)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async getQueueProducer<T = unknown>(bindingName: string): Promise<any> {\n this.ensureRunning()\n return this.mf!.getQueueProducer<T>(bindingName)\n }\n\n async getAllBindings<T = Record<string, unknown>>(): Promise<T> {\n this.ensureRunning()\n return this.mf!.getBindings() as Promise<T>\n }\n\n // Getters\n getDiscoveredBindings(): DiscoveredBindings | null {\n return this.bindings\n }\n\n getConfig(): WranglerConfig | null {\n return this.config\n }\n\n getOptions(): Required<LocalFlareOptions> {\n return this.options\n }\n\n getMiniflare(): Miniflare | null {\n return this.mf\n }\n\n isRunning(): boolean {\n return this.mf !== null\n }\n\n // Set the worker log callback (must be called before start())\n setLogCallback(callback: WorkerLogCallback): void {\n this.options.onWorkerLog = callback\n }\n\n private ensureRunning(): void {\n if (!this.mf) {\n throw new Error('LocalFlare is not running. Call start() first.')\n }\n }\n}\n","import { readFileSync, existsSync } from 'node:fs'\nimport { resolve, extname } from 'node:path'\nimport { parse as parseToml } from 'toml'\nimport type { WranglerConfig, DiscoveredBindings } from './types.js'\n\n// Supported wrangler config file names in order of priority\nexport const WRANGLER_CONFIG_FILES = [\n 'wrangler.toml',\n 'wrangler.json',\n 'wrangler.jsonc',\n]\n\n/**\n * Strip JSON comments (both // and /* *\\/) for JSONC parsing\n */\nfunction stripJsonComments(content: string): string {\n let result = ''\n let inString = false\n let inSingleLineComment = false\n let inMultiLineComment = false\n let i = 0\n\n while (i < content.length) {\n const char = content[i]\n const nextChar = content[i + 1]\n\n if (inSingleLineComment) {\n if (char === '\\n') {\n inSingleLineComment = false\n result += char\n }\n i++\n continue\n }\n\n if (inMultiLineComment) {\n if (char === '*' && nextChar === '/') {\n inMultiLineComment = false\n i += 2\n continue\n }\n i++\n continue\n }\n\n if (inString) {\n result += char\n if (char === '\\\\' && i + 1 < content.length) {\n result += nextChar\n i += 2\n continue\n }\n if (char === '\"') {\n inString = false\n }\n i++\n continue\n }\n\n if (char === '\"') {\n inString = true\n result += char\n i++\n continue\n }\n\n if (char === '/' && nextChar === '/') {\n inSingleLineComment = true\n i += 2\n continue\n }\n\n if (char === '/' && nextChar === '*') {\n inMultiLineComment = true\n i += 2\n continue\n }\n\n result += char\n i++\n }\n\n return result\n}\n\n/**\n * Find wrangler config file in a directory\n * Returns the path to the first found config file, or null if none found\n */\nexport function findWranglerConfig(directory: string): string | null {\n for (const filename of WRANGLER_CONFIG_FILES) {\n const configPath = resolve(directory, filename)\n if (existsSync(configPath)) {\n return configPath\n }\n }\n return null\n}\n\nexport function parseWranglerConfig(configPath: string): WranglerConfig {\n const fullPath = resolve(configPath)\n\n if (!existsSync(fullPath)) {\n throw new Error(`Wrangler config not found at: ${fullPath}`)\n }\n\n const content = readFileSync(fullPath, 'utf-8')\n const ext = extname(fullPath).toLowerCase()\n\n switch (ext) {\n case '.toml':\n return parseToml(content) as WranglerConfig\n case '.json':\n return JSON.parse(content) as WranglerConfig\n case '.jsonc':\n return JSON.parse(stripJsonComments(content)) as WranglerConfig\n default:\n // Try to detect format from content\n const trimmed = content.trim()\n if (trimmed.startsWith('{')) {\n // Looks like JSON/JSONC\n return JSON.parse(stripJsonComments(content)) as WranglerConfig\n }\n // Default to TOML\n return parseToml(content) as WranglerConfig\n }\n}\n\nexport function discoverBindings(config: WranglerConfig): DiscoveredBindings {\n return {\n d1: config.d1_databases ?? [],\n kv: config.kv_namespaces ?? [],\n r2: config.r2_buckets ?? [],\n durableObjects: config.durable_objects?.bindings ?? [],\n queues: {\n producers: config.queues?.producers ?? [],\n consumers: config.queues?.consumers ?? [],\n },\n vars: config.vars ?? {},\n }\n}\n\nexport function getBindingSummary(bindings: DiscoveredBindings): string[] {\n const summary: string[] = []\n\n if (bindings.d1.length > 0) {\n summary.push(`D1 Databases: ${bindings.d1.map(d => d.binding).join(', ')}`)\n }\n if (bindings.kv.length > 0) {\n summary.push(`KV Namespaces: ${bindings.kv.map(k => k.binding).join(', ')}`)\n }\n if (bindings.r2.length > 0) {\n summary.push(`R2 Buckets: ${bindings.r2.map(r => r.binding).join(', ')}`)\n }\n if (bindings.durableObjects.length > 0) {\n summary.push(`Durable Objects: ${bindings.durableObjects.map(d => d.name).join(', ')}`)\n }\n if (bindings.queues.producers.length > 0) {\n summary.push(`Queue Producers: ${bindings.queues.producers.map(q => q.binding).join(', ')}`)\n }\n if (bindings.queues.consumers.length > 0) {\n summary.push(`Queue Consumers: ${bindings.queues.consumers.map(q => q.queue).join(', ')}`)\n }\n if (Object.keys(bindings.vars).length > 0) {\n summary.push(`Environment Variables: ${Object.keys(bindings.vars).join(', ')}`)\n }\n\n return summary\n}\n"],"mappings":"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","names":["resolve","resolve"]}
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, extname } from \"node:path\";\nimport { parse as parseToml } from \"toml\";\nimport parseJsonc from \"tiny-jsonc\";\nimport type { WranglerConfig, DiscoveredBindings } from \"./types.js\";\n\n// Supported wrangler config file names in order of priority\nexport const WRANGLER_CONFIG_FILES = [\n \"wrangler.toml\",\n \"wrangler.json\",\n \"wrangler.jsonc\",\n];\n\n/**\n * Find wrangler config file in a directory\n * Searches for config files in priority order: toml > json > jsonc\n * @param directory - Directory to search in\n * @returns Path to the first found config file, or null if none found\n */\nexport function findWranglerConfig(directory: string): string | null {\n for (const filename of WRANGLER_CONFIG_FILES) {\n const configPath = resolve(directory, filename);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n return null;\n}\n\n/**\n * Parse wrangler configuration file (TOML, JSON, or JSONC format)\n * @param configPath - Path to the configuration file\n * @returns Parsed configuration object\n * @throws Error if the file doesn't exist or cannot be parsed\n */\nexport function parseWranglerConfig(configPath: string): WranglerConfig {\n const fullPath = resolve(configPath);\n\n if (!existsSync(fullPath)) {\n throw new Error(`Wrangler config not found at: ${fullPath}`);\n }\n\n const content = readFileSync(fullPath, \"utf-8\");\n const ext = extname(fullPath).toLowerCase();\n\n switch (ext) {\n case \".toml\":\n return parseToml(content) as WranglerConfig;\n case \".json\":\n return JSON.parse(content) as WranglerConfig;\n case \".jsonc\":\n return parseJsonc.parse(content) as WranglerConfig;\n default: {\n // Try to detect format from content\n const trimmed = content.trim();\n if (trimmed.startsWith(\"{\")) {\n // Looks like JSON/JSONC - use JSONC parser to handle both\n return parseJsonc.parse(content) as WranglerConfig;\n }\n // Default to TOML\n return parseToml(content) as WranglerConfig;\n }\n }\n}\n\n/**\n * Discover and extract all bindings from a wrangler configuration\n * @param config - Parsed wrangler configuration\n * @returns Discovered bindings organized by type\n */\nexport function discoverBindings(config: WranglerConfig): DiscoveredBindings {\n return {\n d1: config.d1_databases ?? [],\n kv: config.kv_namespaces ?? [],\n r2: config.r2_buckets ?? [],\n durableObjects: config.durable_objects?.bindings ?? [],\n queues: {\n producers: config.queues?.producers ?? [],\n consumers: config.queues?.consumers ?? [],\n },\n vars: config.vars ?? {},\n };\n}\n\n/**\n * Generate a human-readable summary of discovered bindings\n * @param bindings - Discovered bindings to summarize\n * @returns Array of summary strings, one per binding type\n */\nexport function getBindingSummary(bindings: DiscoveredBindings): string[] {\n const summary: string[] = [];\n\n if (bindings.d1.length > 0) {\n summary.push(\n `D1 Databases: ${bindings.d1.map((d) => d.binding).join(\", \")}`\n );\n }\n if (bindings.kv.length > 0) {\n summary.push(\n `KV Namespaces: ${bindings.kv.map((k) => k.binding).join(\", \")}`\n );\n }\n if (bindings.r2.length > 0) {\n summary.push(`R2 Buckets: ${bindings.r2.map((r) => r.binding).join(\", \")}`);\n }\n if (bindings.durableObjects.length > 0) {\n summary.push(\n `Durable Objects: ${bindings.durableObjects\n .map((d) => d.name)\n .join(\", \")}`\n );\n }\n if (bindings.queues.producers.length > 0) {\n summary.push(\n `Queue Producers: ${bindings.queues.producers\n .map((q) => q.binding)\n .join(\", \")}`\n );\n }\n if (bindings.queues.consumers.length > 0) {\n summary.push(\n `Queue Consumers: ${bindings.queues.consumers\n .map((q) => q.queue)\n .join(\", \")}`\n );\n }\n if (Object.keys(bindings.vars).length > 0) {\n summary.push(\n `Environment Variables: ${Object.keys(bindings.vars).join(\", \")}`\n );\n }\n\n return summary;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "localflare-core",
3
- "version": "0.1.2",
4
- "description": "Core Miniflare wrapper for Localflare",
3
+ "version": "0.2.0-beta.1",
4
+ "description": "Core utilities for Localflare - config parsing and binding discovery",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -15,19 +15,18 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "esbuild": "^0.27.2",
19
- "miniflare": "^3.20241218.0",
18
+ "tiny-jsonc": "^1.0.2",
20
19
  "toml": "^3.0.0"
21
20
  },
22
21
  "devDependencies": {
23
- "@cloudflare/workers-types": "^4.20241218.0",
24
22
  "tsup": "^8.3.5",
25
23
  "typescript": "^5.7.2"
26
24
  },
27
25
  "keywords": [
28
26
  "cloudflare",
29
27
  "workers",
30
- "miniflare",
28
+ "wrangler",
29
+ "config",
31
30
  "local",
32
31
  "development"
33
32
  ],