localflare-core 0.0.1 → 0.1.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/README.md +76 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +154 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# localflare-core
|
|
2
|
+
|
|
3
|
+
Core Miniflare wrapper and configuration parser for [LocalFlare](https://www.npmjs.com/package/localflare).
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/localflare-core)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This package provides the core functionality for LocalFlare:
|
|
11
|
+
|
|
12
|
+
- **Miniflare Integration** - Wraps Miniflare to provide a consistent API for running Cloudflare Workers locally
|
|
13
|
+
- **Config Parser** - Reads and parses `wrangler.toml` configuration files
|
|
14
|
+
- **Bindings Support** - Full support for D1, KV, R2, Durable Objects, Queues, and more
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install localflare-core
|
|
20
|
+
# or
|
|
21
|
+
pnpm add localflare-core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { LocalFlareCore } from 'localflare-core';
|
|
28
|
+
|
|
29
|
+
const core = new LocalFlareCore({
|
|
30
|
+
configPath: './wrangler.toml',
|
|
31
|
+
port: 8787,
|
|
32
|
+
persist: '.localflare'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Start the worker
|
|
36
|
+
await core.start();
|
|
37
|
+
|
|
38
|
+
// Access bindings programmatically
|
|
39
|
+
const bindings = core.getBindings();
|
|
40
|
+
|
|
41
|
+
// Get D1 databases
|
|
42
|
+
const d1Databases = bindings.d1;
|
|
43
|
+
|
|
44
|
+
// Get KV namespaces
|
|
45
|
+
const kvNamespaces = bindings.kv;
|
|
46
|
+
|
|
47
|
+
// Stop when done
|
|
48
|
+
await core.stop();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Supported Bindings
|
|
52
|
+
|
|
53
|
+
| Binding | Support |
|
|
54
|
+
|---------|---------|
|
|
55
|
+
| D1 | ✅ Full |
|
|
56
|
+
| KV | ✅ Full |
|
|
57
|
+
| R2 | ✅ Full |
|
|
58
|
+
| Durable Objects | ✅ Full |
|
|
59
|
+
| Queues | ✅ Full |
|
|
60
|
+
| Service Bindings | ✅ Full |
|
|
61
|
+
| Cache API | ✅ Full |
|
|
62
|
+
| Hyperdrive | ✅ Full |
|
|
63
|
+
| Vectorize | ⚠️ Limited |
|
|
64
|
+
| Workers AI | ⚠️ Mock |
|
|
65
|
+
|
|
66
|
+
## Related Packages
|
|
67
|
+
|
|
68
|
+
| Package | Description |
|
|
69
|
+
|---------|-------------|
|
|
70
|
+
| [`localflare`](https://www.npmjs.com/package/localflare) | CLI tool (main package) |
|
|
71
|
+
| [`localflare-server`](https://www.npmjs.com/package/localflare-server) | Dashboard API server |
|
|
72
|
+
| [`localflare-dashboard`](https://www.npmjs.com/package/localflare-dashboard) | React dashboard UI |
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -62,12 +62,18 @@ interface DiscoveredBindings {
|
|
|
62
62
|
};
|
|
63
63
|
vars: Record<string, string>;
|
|
64
64
|
}
|
|
65
|
+
interface WorkerLogEntry {
|
|
66
|
+
level: 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
67
|
+
message: string;
|
|
68
|
+
}
|
|
69
|
+
type WorkerLogCallback = (entry: WorkerLogEntry) => void;
|
|
65
70
|
interface LocalFlareOptions {
|
|
66
71
|
configPath?: string;
|
|
67
72
|
port?: number;
|
|
68
73
|
dashboardPort?: number;
|
|
69
74
|
persistPath?: string;
|
|
70
75
|
verbose?: boolean;
|
|
76
|
+
onWorkerLog?: WorkerLogCallback;
|
|
71
77
|
}
|
|
72
78
|
interface BindingInfo {
|
|
73
79
|
type: 'D1' | 'KV' | 'R2' | 'DO' | 'Queue' | 'Var';
|
|
@@ -94,11 +100,18 @@ declare class LocalFlare {
|
|
|
94
100
|
getOptions(): Required<LocalFlareOptions>;
|
|
95
101
|
getMiniflare(): Miniflare | null;
|
|
96
102
|
isRunning(): boolean;
|
|
103
|
+
setLogCallback(callback: WorkerLogCallback): void;
|
|
97
104
|
private ensureRunning;
|
|
98
105
|
}
|
|
99
106
|
|
|
107
|
+
declare const WRANGLER_CONFIG_FILES: string[];
|
|
108
|
+
/**
|
|
109
|
+
* Find wrangler config file in a directory
|
|
110
|
+
* Returns the path to the first found config file, or null if none found
|
|
111
|
+
*/
|
|
112
|
+
declare function findWranglerConfig(directory: string): string | null;
|
|
100
113
|
declare function parseWranglerConfig(configPath: string): WranglerConfig;
|
|
101
114
|
declare function discoverBindings(config: WranglerConfig): DiscoveredBindings;
|
|
102
115
|
declare function getBindingSummary(bindings: DiscoveredBindings): string[];
|
|
103
116
|
|
|
104
|
-
export { type BindingInfo, type D1DatabaseConfig, type DiscoveredBindings, type DurableObjectBinding, type KVNamespaceConfig, LocalFlare, type LocalFlareOptions, type QueueConsumerConfig, type QueueProducerConfig, type R2BucketConfig, type WranglerConfig, discoverBindings, getBindingSummary, parseWranglerConfig };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -2,18 +2,107 @@
|
|
|
2
2
|
import { Miniflare } from "miniflare";
|
|
3
3
|
import { resolve as resolve2, dirname } from "path";
|
|
4
4
|
import * as esbuild from "esbuild";
|
|
5
|
+
import * as readline from "readline";
|
|
5
6
|
|
|
6
7
|
// src/config.ts
|
|
7
8
|
import { readFileSync, existsSync } from "fs";
|
|
8
|
-
import { resolve } from "path";
|
|
9
|
+
import { resolve, extname } from "path";
|
|
9
10
|
import { parse as parseToml } from "toml";
|
|
11
|
+
var WRANGLER_CONFIG_FILES = [
|
|
12
|
+
"wrangler.toml",
|
|
13
|
+
"wrangler.json",
|
|
14
|
+
"wrangler.jsonc"
|
|
15
|
+
];
|
|
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
|
+
function findWranglerConfig(directory) {
|
|
77
|
+
for (const filename of WRANGLER_CONFIG_FILES) {
|
|
78
|
+
const configPath = resolve(directory, filename);
|
|
79
|
+
if (existsSync(configPath)) {
|
|
80
|
+
return configPath;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
10
85
|
function parseWranglerConfig(configPath) {
|
|
11
86
|
const fullPath = resolve(configPath);
|
|
12
87
|
if (!existsSync(fullPath)) {
|
|
13
88
|
throw new Error(`Wrangler config not found at: ${fullPath}`);
|
|
14
89
|
}
|
|
15
90
|
const content = readFileSync(fullPath, "utf-8");
|
|
16
|
-
|
|
91
|
+
const ext = extname(fullPath).toLowerCase();
|
|
92
|
+
switch (ext) {
|
|
93
|
+
case ".toml":
|
|
94
|
+
return parseToml(content);
|
|
95
|
+
case ".json":
|
|
96
|
+
return JSON.parse(content);
|
|
97
|
+
case ".jsonc":
|
|
98
|
+
return JSON.parse(stripJsonComments(content));
|
|
99
|
+
default:
|
|
100
|
+
const trimmed = content.trim();
|
|
101
|
+
if (trimmed.startsWith("{")) {
|
|
102
|
+
return JSON.parse(stripJsonComments(content));
|
|
103
|
+
}
|
|
104
|
+
return parseToml(content);
|
|
105
|
+
}
|
|
17
106
|
}
|
|
18
107
|
function discoverBindings(config) {
|
|
19
108
|
return {
|
|
@@ -45,6 +134,9 @@ function getBindingSummary(bindings) {
|
|
|
45
134
|
if (bindings.queues.producers.length > 0) {
|
|
46
135
|
summary.push(`Queue Producers: ${bindings.queues.producers.map((q) => q.binding).join(", ")}`);
|
|
47
136
|
}
|
|
137
|
+
if (bindings.queues.consumers.length > 0) {
|
|
138
|
+
summary.push(`Queue Consumers: ${bindings.queues.consumers.map((q) => q.queue).join(", ")}`);
|
|
139
|
+
}
|
|
48
140
|
if (Object.keys(bindings.vars).length > 0) {
|
|
49
141
|
summary.push(`Environment Variables: ${Object.keys(bindings.vars).join(", ")}`);
|
|
50
142
|
}
|
|
@@ -58,12 +150,19 @@ var LocalFlare = class {
|
|
|
58
150
|
bindings = null;
|
|
59
151
|
options;
|
|
60
152
|
constructor(options = {}) {
|
|
153
|
+
let configPath = options.configPath;
|
|
154
|
+
if (!configPath) {
|
|
155
|
+
const detected = findWranglerConfig(process.cwd());
|
|
156
|
+
configPath = detected ?? "./wrangler.toml";
|
|
157
|
+
}
|
|
61
158
|
this.options = {
|
|
62
|
-
configPath
|
|
159
|
+
configPath,
|
|
63
160
|
port: options.port ?? 8787,
|
|
64
161
|
dashboardPort: options.dashboardPort ?? 8788,
|
|
65
162
|
persistPath: options.persistPath ?? ".localflare",
|
|
66
|
-
verbose: options.verbose ?? false
|
|
163
|
+
verbose: options.verbose ?? false,
|
|
164
|
+
onWorkerLog: options.onWorkerLog ?? (() => {
|
|
165
|
+
})
|
|
67
166
|
};
|
|
68
167
|
}
|
|
69
168
|
async start() {
|
|
@@ -88,7 +187,8 @@ var LocalFlare = class {
|
|
|
88
187
|
write: false,
|
|
89
188
|
minify: false,
|
|
90
189
|
sourcemap: false,
|
|
91
|
-
conditions: ["workerd", "worker", "browser"]
|
|
190
|
+
conditions: ["workerd", "worker", "browser"],
|
|
191
|
+
external: ["cloudflare:*", "node:*"]
|
|
92
192
|
});
|
|
93
193
|
const scriptContent = buildResult.outputFiles?.[0]?.text;
|
|
94
194
|
if (!scriptContent) {
|
|
@@ -106,9 +206,44 @@ var LocalFlare = class {
|
|
|
106
206
|
const queueProducers = Object.fromEntries(
|
|
107
207
|
this.bindings.queues.producers.map((q) => [q.binding, q.queue])
|
|
108
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
|
+
);
|
|
109
220
|
const durableObjects = Object.fromEntries(
|
|
110
221
|
this.bindings.durableObjects.map((d) => [d.name, d.class_name])
|
|
111
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
|
+
};
|
|
112
247
|
const miniflareOptions = {
|
|
113
248
|
modules: [
|
|
114
249
|
{
|
|
@@ -125,7 +260,11 @@ var LocalFlare = class {
|
|
|
125
260
|
durableObjectsPersist: `${persistRoot}/do`,
|
|
126
261
|
cachePersist: `${persistRoot}/cache`,
|
|
127
262
|
// Explicit binding configurations
|
|
128
|
-
bindings: this.bindings.vars
|
|
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
|
|
129
268
|
};
|
|
130
269
|
if (Object.keys(d1Databases).length > 0) {
|
|
131
270
|
miniflareOptions.d1Databases = d1Databases;
|
|
@@ -139,6 +278,9 @@ var LocalFlare = class {
|
|
|
139
278
|
if (Object.keys(queueProducers).length > 0) {
|
|
140
279
|
miniflareOptions.queueProducers = queueProducers;
|
|
141
280
|
}
|
|
281
|
+
if (Object.keys(queueConsumers).length > 0) {
|
|
282
|
+
miniflareOptions.queueConsumers = queueConsumers;
|
|
283
|
+
}
|
|
142
284
|
if (Object.keys(durableObjects).length > 0) {
|
|
143
285
|
miniflareOptions.durableObjects = durableObjects;
|
|
144
286
|
}
|
|
@@ -199,6 +341,10 @@ var LocalFlare = class {
|
|
|
199
341
|
isRunning() {
|
|
200
342
|
return this.mf !== null;
|
|
201
343
|
}
|
|
344
|
+
// Set the worker log callback (must be called before start())
|
|
345
|
+
setLogCallback(callback) {
|
|
346
|
+
this.options.onWorkerLog = callback;
|
|
347
|
+
}
|
|
202
348
|
ensureRunning() {
|
|
203
349
|
if (!this.mf) {
|
|
204
350
|
throw new Error("LocalFlare is not running. Call start() first.");
|
|
@@ -207,7 +353,9 @@ var LocalFlare = class {
|
|
|
207
353
|
};
|
|
208
354
|
export {
|
|
209
355
|
LocalFlare,
|
|
356
|
+
WRANGLER_CONFIG_FILES,
|
|
210
357
|
discoverBindings,
|
|
358
|
+
findWranglerConfig,
|
|
211
359
|
getBindingSummary,
|
|
212
360
|
parseWranglerConfig
|
|
213
361
|
};
|
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 { parseWranglerConfig, discoverBindings, getBindingSummary } from './config.js'\nimport type { LocalFlareOptions, DiscoveredBindings, WranglerConfig } 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 this.options = {\n configPath: options.configPath ?? './wrangler.toml',\n port: options.port ?? 8787,\n dashboardPort: options.dashboardPort ?? 8788,\n persistPath: options.persistPath ?? '.localflare',\n verbose: options.verbose ?? false,\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 })\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 const durableObjects = Object.fromEntries(\n this.bindings.durableObjects.map(d => [d.name, d.class_name])\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 }\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(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 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 } from 'node:path'\nimport { parse as parseToml } from 'toml'\nimport type { WranglerConfig, DiscoveredBindings } from './types.js'\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 return parseToml(content) as WranglerConfig\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 (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/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"]}
|