lopata 0.0.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 +15 -0
- package/package.json +51 -0
- package/runtime/bindings/ai.ts +132 -0
- package/runtime/bindings/analytics-engine.ts +96 -0
- package/runtime/bindings/browser.ts +64 -0
- package/runtime/bindings/cache.ts +179 -0
- package/runtime/bindings/cf-streams.ts +56 -0
- package/runtime/bindings/container-docker.ts +225 -0
- package/runtime/bindings/container.ts +662 -0
- package/runtime/bindings/crypto-extras.ts +89 -0
- package/runtime/bindings/d1.ts +315 -0
- package/runtime/bindings/do-executor-inprocess.ts +140 -0
- package/runtime/bindings/do-executor-worker.ts +368 -0
- package/runtime/bindings/do-executor.ts +45 -0
- package/runtime/bindings/do-websocket-bridge.ts +70 -0
- package/runtime/bindings/do-worker-entry.ts +220 -0
- package/runtime/bindings/do-worker-env.ts +74 -0
- package/runtime/bindings/durable-object.ts +992 -0
- package/runtime/bindings/email.ts +180 -0
- package/runtime/bindings/html-rewriter.ts +84 -0
- package/runtime/bindings/hyperdrive.ts +130 -0
- package/runtime/bindings/images.ts +381 -0
- package/runtime/bindings/kv.ts +359 -0
- package/runtime/bindings/queue.ts +507 -0
- package/runtime/bindings/r2.ts +759 -0
- package/runtime/bindings/rpc-stub.ts +267 -0
- package/runtime/bindings/scheduled.ts +172 -0
- package/runtime/bindings/service-binding.ts +217 -0
- package/runtime/bindings/static-assets.ts +481 -0
- package/runtime/bindings/websocket-pair.ts +182 -0
- package/runtime/bindings/workflow.ts +858 -0
- package/runtime/bunflare-config.ts +56 -0
- package/runtime/cli/cache.ts +39 -0
- package/runtime/cli/context.ts +105 -0
- package/runtime/cli/d1.ts +163 -0
- package/runtime/cli/dev.ts +392 -0
- package/runtime/cli/kv.ts +84 -0
- package/runtime/cli/queues.ts +109 -0
- package/runtime/cli/r2.ts +140 -0
- package/runtime/cli/traces.ts +251 -0
- package/runtime/cli.ts +102 -0
- package/runtime/config.ts +148 -0
- package/runtime/d1-migrate.ts +37 -0
- package/runtime/dashboard/api.ts +174 -0
- package/runtime/dashboard/app.tsx +220 -0
- package/runtime/dashboard/components/breadcrumb.tsx +16 -0
- package/runtime/dashboard/components/buttons.tsx +13 -0
- package/runtime/dashboard/components/code-block.tsx +5 -0
- package/runtime/dashboard/components/detail-field.tsx +8 -0
- package/runtime/dashboard/components/empty-state.tsx +8 -0
- package/runtime/dashboard/components/filter-input.tsx +11 -0
- package/runtime/dashboard/components/index.ts +16 -0
- package/runtime/dashboard/components/key-value-table.tsx +23 -0
- package/runtime/dashboard/components/modal.tsx +23 -0
- package/runtime/dashboard/components/page-header.tsx +11 -0
- package/runtime/dashboard/components/pill-button.tsx +14 -0
- package/runtime/dashboard/components/refresh-button.tsx +7 -0
- package/runtime/dashboard/components/service-info.tsx +45 -0
- package/runtime/dashboard/components/status-badge.tsx +7 -0
- package/runtime/dashboard/components/table-link.tsx +5 -0
- package/runtime/dashboard/components/table.tsx +26 -0
- package/runtime/dashboard/components.tsx +19 -0
- package/runtime/dashboard/index.html +23 -0
- package/runtime/dashboard/lib.ts +45 -0
- package/runtime/dashboard/rpc/client.ts +20 -0
- package/runtime/dashboard/rpc/handlers/ai.ts +71 -0
- package/runtime/dashboard/rpc/handlers/analytics-engine.ts +53 -0
- package/runtime/dashboard/rpc/handlers/cache.ts +24 -0
- package/runtime/dashboard/rpc/handlers/config.ts +137 -0
- package/runtime/dashboard/rpc/handlers/containers.ts +194 -0
- package/runtime/dashboard/rpc/handlers/d1.ts +84 -0
- package/runtime/dashboard/rpc/handlers/do.ts +117 -0
- package/runtime/dashboard/rpc/handlers/email.ts +82 -0
- package/runtime/dashboard/rpc/handlers/errors.ts +32 -0
- package/runtime/dashboard/rpc/handlers/generations.ts +60 -0
- package/runtime/dashboard/rpc/handlers/kv.ts +76 -0
- package/runtime/dashboard/rpc/handlers/overview.ts +94 -0
- package/runtime/dashboard/rpc/handlers/queue.ts +79 -0
- package/runtime/dashboard/rpc/handlers/r2.ts +72 -0
- package/runtime/dashboard/rpc/handlers/scheduled.ts +91 -0
- package/runtime/dashboard/rpc/handlers/traces.ts +64 -0
- package/runtime/dashboard/rpc/handlers/workers.ts +65 -0
- package/runtime/dashboard/rpc/handlers/workflows.ts +171 -0
- package/runtime/dashboard/rpc/hooks.ts +132 -0
- package/runtime/dashboard/rpc/server.ts +70 -0
- package/runtime/dashboard/rpc/types.ts +396 -0
- package/runtime/dashboard/sql-browser/data-browser-tab.tsx +122 -0
- package/runtime/dashboard/sql-browser/editable-cell.tsx +117 -0
- package/runtime/dashboard/sql-browser/filter-row.tsx +99 -0
- package/runtime/dashboard/sql-browser/history-panels.tsx +110 -0
- package/runtime/dashboard/sql-browser/hooks.ts +137 -0
- package/runtime/dashboard/sql-browser/index.ts +4 -0
- package/runtime/dashboard/sql-browser/insert-row-form.tsx +85 -0
- package/runtime/dashboard/sql-browser/modals.tsx +116 -0
- package/runtime/dashboard/sql-browser/schema-browser-tab.tsx +67 -0
- package/runtime/dashboard/sql-browser/sql-browser.tsx +52 -0
- package/runtime/dashboard/sql-browser/sql-console-tab.tsx +124 -0
- package/runtime/dashboard/sql-browser/table-data-view.tsx +566 -0
- package/runtime/dashboard/sql-browser/table-sidebar.tsx +38 -0
- package/runtime/dashboard/sql-browser/types.ts +61 -0
- package/runtime/dashboard/sql-browser/utils.ts +167 -0
- package/runtime/dashboard/style.css +177 -0
- package/runtime/dashboard/views/ai.tsx +152 -0
- package/runtime/dashboard/views/analytics-engine.tsx +169 -0
- package/runtime/dashboard/views/cache.tsx +93 -0
- package/runtime/dashboard/views/containers.tsx +197 -0
- package/runtime/dashboard/views/d1.tsx +81 -0
- package/runtime/dashboard/views/do.tsx +168 -0
- package/runtime/dashboard/views/email.tsx +235 -0
- package/runtime/dashboard/views/errors.tsx +558 -0
- package/runtime/dashboard/views/home.tsx +287 -0
- package/runtime/dashboard/views/kv.tsx +273 -0
- package/runtime/dashboard/views/queue.tsx +193 -0
- package/runtime/dashboard/views/r2.tsx +202 -0
- package/runtime/dashboard/views/scheduled.tsx +89 -0
- package/runtime/dashboard/views/trace-waterfall.tsx +410 -0
- package/runtime/dashboard/views/traces.tsx +768 -0
- package/runtime/dashboard/views/workers.tsx +55 -0
- package/runtime/dashboard/views/workflows.tsx +473 -0
- package/runtime/db.ts +258 -0
- package/runtime/env.ts +362 -0
- package/runtime/error-page/app.tsx +394 -0
- package/runtime/error-page/build.ts +269 -0
- package/runtime/error-page/index.html +16 -0
- package/runtime/error-page/style.css +31 -0
- package/runtime/execution-context.ts +18 -0
- package/runtime/file-watcher.ts +57 -0
- package/runtime/generation-manager.ts +230 -0
- package/runtime/generation.ts +411 -0
- package/runtime/plugin.ts +292 -0
- package/runtime/request-cf.ts +28 -0
- package/runtime/rpc-validate.ts +154 -0
- package/runtime/tracing/context.ts +40 -0
- package/runtime/tracing/db.ts +73 -0
- package/runtime/tracing/frames.ts +75 -0
- package/runtime/tracing/instrument.ts +186 -0
- package/runtime/tracing/span.ts +138 -0
- package/runtime/tracing/store.ts +499 -0
- package/runtime/tracing/types.ts +47 -0
- package/runtime/vite-plugin/config-plugin.ts +68 -0
- package/runtime/vite-plugin/dev-server-plugin.ts +493 -0
- package/runtime/vite-plugin/dist/index.mjs +52333 -0
- package/runtime/vite-plugin/globals-plugin.ts +94 -0
- package/runtime/vite-plugin/index.ts +43 -0
- package/runtime/vite-plugin/modules-plugin.ts +88 -0
- package/runtime/vite-plugin/react-router-plugin.ts +95 -0
- package/runtime/worker-registry.ts +52 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve, dirname } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface BunflareConfig {
|
|
5
|
+
/** Path to the main worker's wrangler config (HTTP entrypoint) */
|
|
6
|
+
main: string;
|
|
7
|
+
/** Auxiliary workers, each with a service name and wrangler config path */
|
|
8
|
+
workers?: Array<{
|
|
9
|
+
name: string;
|
|
10
|
+
config: string;
|
|
11
|
+
}>;
|
|
12
|
+
/** Enable real cron scheduling based on wrangler triggers.crons (default: false) */
|
|
13
|
+
cron?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* DO isolation mode:
|
|
16
|
+
* - "dev" (default) — all DO instances run in-process (fast, shared memory, hot reload)
|
|
17
|
+
* - "isolated" — each DO instance runs in a separate Bun Worker thread (faithful to CF production)
|
|
18
|
+
*/
|
|
19
|
+
isolation?: "dev" | "isolated";
|
|
20
|
+
/** Browser Rendering binding config for local dev */
|
|
21
|
+
browser?: {
|
|
22
|
+
/** WS endpoint of an existing Chrome instance. If set, uses puppeteer-core connect(). */
|
|
23
|
+
wsEndpoint?: string;
|
|
24
|
+
/** Path to Chrome executable (only used when spawning without wsEndpoint). */
|
|
25
|
+
executablePath?: string;
|
|
26
|
+
/** Headless mode (default: true, only used when spawning). */
|
|
27
|
+
headless?: boolean;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function defineConfig(config: BunflareConfig): BunflareConfig {
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Try to load `bunflare.config.ts` from the given directory.
|
|
37
|
+
* Returns null if the file doesn't exist.
|
|
38
|
+
* All paths in the returned config are resolved relative to baseDir.
|
|
39
|
+
*/
|
|
40
|
+
export async function loadBunflareConfig(baseDir: string): Promise<BunflareConfig | null> {
|
|
41
|
+
const configPath = join(baseDir, "bunflare.config.ts");
|
|
42
|
+
if (!existsSync(configPath)) return null;
|
|
43
|
+
|
|
44
|
+
const mod = await import(configPath);
|
|
45
|
+
const config: BunflareConfig = mod.default ?? mod;
|
|
46
|
+
|
|
47
|
+
// Resolve all paths relative to baseDir
|
|
48
|
+
config.main = resolve(baseDir, config.main);
|
|
49
|
+
if (config.workers) {
|
|
50
|
+
for (const worker of config.workers) {
|
|
51
|
+
worker.config = resolve(baseDir, worker.config);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return config;
|
|
56
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CliContext } from "./context";
|
|
2
|
+
import { parseFlag } from "./context";
|
|
3
|
+
|
|
4
|
+
export async function run(ctx: CliContext, args: string[]) {
|
|
5
|
+
const action = args[0];
|
|
6
|
+
|
|
7
|
+
switch (action) {
|
|
8
|
+
case "list": {
|
|
9
|
+
const db = ctx.db();
|
|
10
|
+
const rows = db.query<{ cache_name: string; cnt: number }, []>(
|
|
11
|
+
"SELECT cache_name, COUNT(*) as cnt FROM cache_entries GROUP BY cache_name ORDER BY cache_name",
|
|
12
|
+
).all();
|
|
13
|
+
if (rows.length === 0) {
|
|
14
|
+
console.log("(no cache entries)");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
console.log(`${row.cache_name} ${row.cnt} entries`);
|
|
19
|
+
}
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
case "purge": {
|
|
23
|
+
const cacheName = parseFlag(ctx.args, "--name");
|
|
24
|
+
const db = ctx.db();
|
|
25
|
+
let result;
|
|
26
|
+
if (cacheName) {
|
|
27
|
+
result = db.run("DELETE FROM cache_entries WHERE cache_name = ?", [cacheName]);
|
|
28
|
+
console.log(`Purged ${result.changes} entries from cache "${cacheName}"`);
|
|
29
|
+
} else {
|
|
30
|
+
result = db.run("DELETE FROM cache_entries");
|
|
31
|
+
console.log(`Purged ${result.changes} cache entries (all caches)`);
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
console.error("Usage: bunflare cache <list|purge> [options]");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
import type { WranglerConfig } from "../config";
|
|
3
|
+
import { autoLoadConfig, loadConfig } from "../config";
|
|
4
|
+
import { resolve, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
export interface CliContext {
|
|
7
|
+
args: string[];
|
|
8
|
+
config: () => Promise<WranglerConfig>;
|
|
9
|
+
db: () => Database;
|
|
10
|
+
dataDir: () => string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Parse a flag value from argv. Returns the value after the flag, or undefined. */
|
|
14
|
+
export function parseFlag(args: string[], name: string): string | undefined {
|
|
15
|
+
const idx = args.indexOf(name);
|
|
16
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Check if a boolean flag is present. */
|
|
20
|
+
export function hasFlag(args: string[], name: string): boolean {
|
|
21
|
+
return args.includes(name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Get positional args (everything that's not a flag or flag value). */
|
|
25
|
+
export function positionalArgs(args: string[], flags: string[]): string[] {
|
|
26
|
+
const result: string[] = [];
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
if (flags.includes(args[i]!)) {
|
|
29
|
+
i++; // skip flag value
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (args[i]!.startsWith("-")) continue;
|
|
33
|
+
result.push(args[i]!);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a single binding from config when multiple bindings of the same type exist.
|
|
40
|
+
* If there's exactly one, auto-select it. If multiple, require the flag.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveBinding<T extends { binding?: string; bucket_name?: string; queue?: string }>(
|
|
43
|
+
bindings: T[] | undefined,
|
|
44
|
+
flagValue: string | undefined,
|
|
45
|
+
typeName: string,
|
|
46
|
+
matchField: keyof T = "binding" as keyof T,
|
|
47
|
+
): T {
|
|
48
|
+
if (!bindings || bindings.length === 0) {
|
|
49
|
+
console.error(`No ${typeName} bindings configured.`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
if (bindings.length === 1) return bindings[0]!;
|
|
53
|
+
if (!flagValue) {
|
|
54
|
+
const names = bindings.map(b => String(b[matchField])).join(", ");
|
|
55
|
+
console.error(`Multiple ${typeName} bindings found: ${names}. Use the appropriate flag to select one.`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const match = bindings.find(b => String(b[matchField]) === flagValue);
|
|
59
|
+
if (!match) {
|
|
60
|
+
const names = bindings.map(b => String(b[matchField])).join(", ");
|
|
61
|
+
console.error(`${typeName} binding "${flagValue}" not found. Available: ${names}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
return match;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createContext(argv: string[]): CliContext {
|
|
68
|
+
// Strip "bun runtime/cli.ts" or similar prefix — find first non-file arg
|
|
69
|
+
const args = argv.slice(2);
|
|
70
|
+
|
|
71
|
+
const configPath = parseFlag(args, "--config") ?? parseFlag(args, "-c");
|
|
72
|
+
const envName = parseFlag(args, "--env") ?? parseFlag(args, "-e");
|
|
73
|
+
const baseDir = process.cwd();
|
|
74
|
+
|
|
75
|
+
let _config: WranglerConfig | null = null;
|
|
76
|
+
let _db: Database | null = null;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
args,
|
|
80
|
+
config: async () => {
|
|
81
|
+
if (_config) return _config;
|
|
82
|
+
_config = configPath
|
|
83
|
+
? await loadConfig(resolve(baseDir, configPath), envName)
|
|
84
|
+
: await autoLoadConfig(baseDir, envName);
|
|
85
|
+
return _config;
|
|
86
|
+
},
|
|
87
|
+
db: () => {
|
|
88
|
+
if (_db) return _db;
|
|
89
|
+
const { Database } = require("bun:sqlite") as typeof import("bun:sqlite");
|
|
90
|
+
const { mkdirSync } = require("node:fs") as typeof import("node:fs");
|
|
91
|
+
const dataDir = join(baseDir, ".bunflare");
|
|
92
|
+
mkdirSync(dataDir, { recursive: true });
|
|
93
|
+
mkdirSync(join(dataDir, "r2"), { recursive: true });
|
|
94
|
+
mkdirSync(join(dataDir, "d1"), { recursive: true });
|
|
95
|
+
const dbPath = join(dataDir, "data.sqlite");
|
|
96
|
+
_db = new Database(dbPath, { create: true });
|
|
97
|
+
_db.run("PRAGMA journal_mode=WAL");
|
|
98
|
+
// Run schema migrations
|
|
99
|
+
const { runMigrations } = require("../db") as typeof import("../db");
|
|
100
|
+
runMigrations(_db);
|
|
101
|
+
return _db;
|
|
102
|
+
},
|
|
103
|
+
dataDir: () => join(baseDir, ".bunflare"),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import type { CliContext } from "./context";
|
|
5
|
+
import { parseFlag, resolveBinding } from "./context";
|
|
6
|
+
import { openD1Database } from "../bindings/d1";
|
|
7
|
+
|
|
8
|
+
export async function run(ctx: CliContext, args: string[]) {
|
|
9
|
+
const action = args[0];
|
|
10
|
+
|
|
11
|
+
switch (action) {
|
|
12
|
+
case "list": {
|
|
13
|
+
const config = await ctx.config();
|
|
14
|
+
const databases = config.d1_databases ?? [];
|
|
15
|
+
if (databases.length === 0) {
|
|
16
|
+
console.log("No D1 databases configured.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const db of databases) {
|
|
20
|
+
console.log(`${db.binding} ${db.database_name} ${db.database_id}`);
|
|
21
|
+
}
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
case "execute": {
|
|
25
|
+
const dbName = args[1];
|
|
26
|
+
if (!dbName) {
|
|
27
|
+
console.error("Usage: bunflare d1 execute <database> --command 'SQL' | --file <path>");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const config = await ctx.config();
|
|
31
|
+
const databases = config.d1_databases ?? [];
|
|
32
|
+
const dbConfig = databases.find(
|
|
33
|
+
d => d.binding === dbName || d.database_name === dbName,
|
|
34
|
+
);
|
|
35
|
+
if (!dbConfig) {
|
|
36
|
+
const names = databases.map(d => `${d.binding}/${d.database_name}`).join(", ");
|
|
37
|
+
console.error(`Database "${dbName}" not found. Available: ${names || "(none)"}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const command = parseFlag(ctx.args, "--command");
|
|
42
|
+
const filePath = parseFlag(ctx.args, "--file");
|
|
43
|
+
if (!command && !filePath) {
|
|
44
|
+
console.error("Usage: bunflare d1 execute <database> --command 'SQL' | --file <path>");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sql = command ?? readFileSync(resolve(filePath!), "utf-8");
|
|
49
|
+
const d1 = openD1Database(ctx.dataDir(), dbConfig.database_name);
|
|
50
|
+
const result = await d1.exec(sql);
|
|
51
|
+
console.log(`Executed ${result.count} statement(s) in ${result.duration.toFixed(1)}ms`);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "migrations": {
|
|
55
|
+
const subAction = args[1];
|
|
56
|
+
if (subAction !== "apply") {
|
|
57
|
+
console.error("Usage: bunflare d1 migrations apply [database]");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const targetDb = args[2];
|
|
61
|
+
const config = await ctx.config();
|
|
62
|
+
await applyMigrations(config.d1_databases ?? [], ctx.dataDir(), targetDb);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
default:
|
|
66
|
+
console.error(`Usage: bunflare d1 <list|execute|migrations> [options]`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Apply D1 migrations. Extracted from runtime/d1-migrate.ts for CLI reuse.
|
|
73
|
+
*/
|
|
74
|
+
export async function applyMigrations(
|
|
75
|
+
databases: { binding: string; database_name: string; database_id: string; migrations_dir?: string }[],
|
|
76
|
+
dataDir: string,
|
|
77
|
+
targetDb?: string,
|
|
78
|
+
): Promise<number> {
|
|
79
|
+
const filtered = targetDb
|
|
80
|
+
? databases.filter(d => d.binding === targetDb || d.database_name === targetDb)
|
|
81
|
+
: databases;
|
|
82
|
+
|
|
83
|
+
if (filtered.length === 0) {
|
|
84
|
+
if (targetDb) {
|
|
85
|
+
console.error(`Database "${targetDb}" not found.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
console.log("No D1 databases configured.");
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const d1Dir = join(dataDir, "d1");
|
|
93
|
+
mkdirSync(d1Dir, { recursive: true });
|
|
94
|
+
const baseDir = process.cwd();
|
|
95
|
+
let totalApplied = 0;
|
|
96
|
+
|
|
97
|
+
for (const dbConfig of filtered) {
|
|
98
|
+
const { database_name, binding, migrations_dir } = dbConfig;
|
|
99
|
+
|
|
100
|
+
if (!migrations_dir) {
|
|
101
|
+
console.log(`${binding} (${database_name}): no migrations_dir, skipping`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const migrationsPath = resolve(baseDir, migrations_dir);
|
|
106
|
+
if (!existsSync(migrationsPath)) {
|
|
107
|
+
console.log(`${binding} (${database_name}): migrations_dir not found: ${migrationsPath}`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const dbPath = join(d1Dir, `${database_name}.sqlite`);
|
|
112
|
+
const db = new Database(dbPath, { create: true });
|
|
113
|
+
db.run("PRAGMA journal_mode=WAL");
|
|
114
|
+
|
|
115
|
+
db.run(`
|
|
116
|
+
CREATE TABLE IF NOT EXISTS d1_migrations (
|
|
117
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
118
|
+
name TEXT NOT NULL UNIQUE,
|
|
119
|
+
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
120
|
+
)
|
|
121
|
+
`);
|
|
122
|
+
|
|
123
|
+
const applied = new Set(
|
|
124
|
+
db.query<{ name: string }, []>("SELECT name FROM d1_migrations ORDER BY id").all().map(r => r.name),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const files = readdirSync(migrationsPath)
|
|
128
|
+
.filter(f => f.endsWith(".sql"))
|
|
129
|
+
.sort();
|
|
130
|
+
|
|
131
|
+
const pending = files.filter(f => !applied.has(f));
|
|
132
|
+
|
|
133
|
+
if (pending.length === 0) {
|
|
134
|
+
console.log(`${binding} (${database_name}): up to date (${applied.size} migrations)`);
|
|
135
|
+
db.close();
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(`${binding} (${database_name}): applying ${pending.length} migration(s)...`);
|
|
140
|
+
|
|
141
|
+
for (const file of pending) {
|
|
142
|
+
const sql = readFileSync(join(migrationsPath, file), "utf-8");
|
|
143
|
+
try {
|
|
144
|
+
db.run("BEGIN");
|
|
145
|
+
db.run(sql);
|
|
146
|
+
db.run("INSERT INTO d1_migrations (name) VALUES (?)", [file]);
|
|
147
|
+
db.run("COMMIT");
|
|
148
|
+
console.log(` + ${file}`);
|
|
149
|
+
totalApplied++;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
db.run("ROLLBACK");
|
|
152
|
+
console.error(` x ${file}: ${err}`);
|
|
153
|
+
db.close();
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
db.close();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`Done. Applied ${totalApplied} migration(s).`);
|
|
162
|
+
return totalApplied;
|
|
163
|
+
}
|