@bungres/kit 0.3.0 → 0.4.0

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/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@bungres/kit",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI toolkit for @bungres/orm — migrate, push, generate, pull",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.0.0",
8
+ "bun": ">=1.3.0"
9
+ },
5
10
  "type": "module",
6
11
  "bin": {
7
12
  "bungres": "./dist/cli.js"
@@ -17,7 +22,6 @@
17
22
  },
18
23
  "files": [
19
24
  "dist",
20
- "src",
21
25
  "README.md",
22
26
  "LICENSE"
23
27
  ],
@@ -27,6 +31,18 @@
27
31
  "url": "https://github.com/bungres/bungres.git",
28
32
  "directory": "packages/@bungres/kit"
29
33
  },
34
+ "bugs": {
35
+ "url": "https://github.com/bungres/bungres/issues"
36
+ },
37
+ "homepage": "https://github.com/bungres/bungres#readme",
38
+ "keywords": [
39
+ "bun",
40
+ "postgres",
41
+ "orm",
42
+ "cli",
43
+ "migration",
44
+ "generator"
45
+ ],
30
46
  "scripts": {
31
47
  "build:types": "tsc --emitDeclarationOnly",
32
48
  "build": "bun build ./src/cli.ts ./src/index.ts --outdir ./dist --target bun --format esm && npm run build:types",
@@ -34,7 +50,7 @@
34
50
  "test": "bun test"
35
51
  },
36
52
  "dependencies": {
37
- "@bungres/orm": "0.2.0"
53
+ "@bungres/orm": "workspace:*"
38
54
  },
39
55
  "devDependencies": {
40
56
  "bun-types": "latest",
package/src/cli.ts DELETED
@@ -1,162 +0,0 @@
1
- #!/usr/bin/env bun
2
- // ---------------------------------------------------------------------------
3
- // @bungres/kit CLI — entry point
4
- // Usage: bungres <command> [options]
5
- // ---------------------------------------------------------------------------
6
-
7
- import { loadConfig } from "./config.js";
8
- import { runPush } from "./commands/push.js";
9
- import { runGenerate } from "./commands/generate.js";
10
- import { runMigrate } from "./commands/migrate.js";
11
- import { runPull } from "./commands/pull.js";
12
- import { runStatus } from "./commands/status.js";
13
- import { runDrop } from "./commands/drop.js";
14
- import { runFresh } from "./commands/fresh.js";
15
- import { runRefresh } from "./commands/refresh.js";
16
- import { runSeed } from "./commands/seed.js";
17
- import { runStudio } from "./commands/studio.js";
18
- import { runTusky } from "./commands/tusky.js";
19
- import { ensureDatabase } from "./ensure-db.js";
20
- import { colorize } from "./utils/colors.js";
21
-
22
- const VERSION = "0.0.1";
23
-
24
- const HELP = `
25
- ${colorize("🐘 Bungres ORM CLI", "cyan")} v${VERSION}
26
-
27
- ${colorize("Usage:", "yellow")}
28
- bungres <command> [options]
29
-
30
- ${colorize("Commands:", "yellow")}
31
- generate Generate SQL migration files from your schema definitions
32
- migrate Run pending migration files against the database
33
- push Apply schema directly to the DB (no migration files, dev mode)
34
- pull Introspect the database and generate TypeScript schema
35
- status Show applied vs. pending migrations
36
- fresh Drop all tables and re-run all migrations from scratch
37
- refresh Truncate all tables to quickly reset data without dropping schema
38
- seed Execute the seed script to populate the database
39
- studio Start a local web interface to browse database data
40
- tusky Boot up a Node REPL connected to the database with schema loaded
41
- drop Drop all tables defined in the schema (dev only)
42
-
43
- ${colorize("Options:", "yellow")}
44
- --config Path to config file (default: bungres.config.ts)
45
- --verbose Enable verbose SQL logging
46
- --force Skip confirmation prompts (use with drop)
47
- --version Show version
48
- --help Show this help
49
-
50
- ${colorize("Examples:", "yellow")}
51
- bungres generate
52
- bungres migrate
53
- bungres push
54
- bungres pull
55
- bungres status
56
- bungres fresh
57
- bungres refresh
58
- bungres seed
59
- bungres studio
60
- bungres tusky
61
- bungres drop --force
62
- `.trim();
63
-
64
- async function main() {
65
- const args = process.argv.slice(2);
66
-
67
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
68
- console.log(HELP);
69
- process.exit(0);
70
- }
71
-
72
- if (args.includes("--version") || args.includes("-v")) {
73
- console.log(`@bungres/kit v${VERSION}`);
74
- process.exit(0);
75
- }
76
-
77
- const command = args[0];
78
- const flags = parseFlags(args.slice(1));
79
-
80
- const config = await loadConfig(process.cwd());
81
-
82
- // Override verbose from flag
83
- if (flags.verbose) config.verbose = true;
84
-
85
- if (command && ["migrate", "push", "drop", "status", "fresh", "refresh"].includes(command)) {
86
- await ensureDatabase(config.dbUrl);
87
- }
88
-
89
- switch (command) {
90
- case "generate":
91
- await runGenerate(config, flags.name as string | undefined);
92
- break;
93
-
94
- case "migrate":
95
- await runMigrate(config);
96
- break;
97
-
98
- case "push":
99
- await runPush(config, { force: !!flags.force });
100
- break;
101
-
102
- case "pull":
103
- await runPull(config);
104
- break;
105
-
106
- case "status":
107
- await runStatus(config);
108
- break;
109
-
110
- case "drop":
111
- await runDrop(config, { force: !!flags.force });
112
- break;
113
-
114
- case "fresh":
115
- await runFresh(config);
116
- break;
117
-
118
- case "refresh":
119
- await runRefresh(config);
120
- break;
121
-
122
- case "seed":
123
- await runSeed(config);
124
- break;
125
-
126
- case "studio":
127
- await runStudio(config);
128
- break;
129
-
130
- case "tusky":
131
- await runTusky(config);
132
- break;
133
-
134
- default:
135
- console.error(`Unknown command: ${command}\n`);
136
- console.log(HELP);
137
- process.exit(1);
138
- }
139
- }
140
-
141
- function parseFlags(args: string[]): Record<string, boolean | string> {
142
- const flags: Record<string, boolean | string> = {};
143
- for (let i = 0; i < args.length; i++) {
144
- const arg = args[i];
145
- if (arg?.startsWith("--")) {
146
- const key = arg.slice(2);
147
- const next = args[i + 1];
148
- if (next && !next.startsWith("--")) {
149
- flags[key] = next;
150
- i++;
151
- } else {
152
- flags[key] = true;
153
- }
154
- }
155
- }
156
- return flags;
157
- }
158
-
159
- main().catch((err) => {
160
- console.error("@bungres/kit error:", err instanceof Error ? err.message : err);
161
- process.exit(1);
162
- });
@@ -1,92 +0,0 @@
1
- import { generateDropTable } from "@bungres/orm";
2
- import type { ResolvedConfig } from "../config.js";
3
- import { loadSchemas } from "../schema-loader.js";
4
- import { colorize } from "../utils/colors.js";
5
-
6
- // ---------------------------------------------------------------------------
7
- // drop — drop all tables defined in the schema (dev utility)
8
- // Always prompts for confirmation unless --force is passed
9
- // ---------------------------------------------------------------------------
10
-
11
- export async function runDrop(
12
- config: ResolvedConfig,
13
- opts: { force?: boolean } = {}
14
- ): Promise<void> {
15
- const schemas = await loadSchemas(config.schema);
16
-
17
- if (schemas.length === 0) {
18
- console.warn("No table definitions found in schema files.");
19
- return;
20
- }
21
-
22
- const sql = new Bun.SQL(config.dbUrl);
23
-
24
- try {
25
- // Check which tables actually exist in the database
26
- const existingTablesResult = await sql`
27
- SELECT table_name
28
- FROM information_schema.tables
29
- WHERE table_schema = 'public' OR table_schema = current_schema()
30
- `;
31
- const existingTableNames = new Set(
32
- existingTablesResult.map((row: any) => row.table_name)
33
- );
34
-
35
- // Also track the migration and push tables since they are dropped too
36
- const migrationTableExists = existingTableNames.has("__bungres_migrations");
37
- const pushTableExists = existingTableNames.has("__bungres_push");
38
-
39
- // Filter our schema list to only those that exist in the DB
40
- const tablesToDrop = schemas.filter((s) => existingTableNames.has(s.config.name));
41
-
42
- if (tablesToDrop.length === 0 && !migrationTableExists && !pushTableExists) {
43
- console.log("No tables to drop (they either don't exist or were already dropped).");
44
- return;
45
- }
46
-
47
- const tableNamesToPrint = tablesToDrop.map(
48
- (s) => (s.config.schema ? s.config.schema + "." : "") + s.config.name
49
- );
50
- if (migrationTableExists) {
51
- tableNamesToPrint.push("__bungres_migrations");
52
- }
53
- if (pushTableExists) {
54
- tableNamesToPrint.push("__bungres_push");
55
- }
56
-
57
- if (!opts.force) {
58
- console.warn(colorize("\n⚠️ WARNING: This will drop the following tables and ALL their data:\n", "red"));
59
- for (const name of tableNamesToPrint) console.log(colorize(` - ${name}`, "yellow"));
60
- process.stdout.write(colorize("\nAre you sure? Type YES to continue: ", "cyan"));
61
-
62
- // Read confirmation from stdin using Bun's native console iterator
63
- for await (const line of console) {
64
- if (line.trim().toLowerCase() !== "yes") {
65
- console.log("Aborted.");
66
- return;
67
- }
68
- break; // break loop after reading the first line
69
- }
70
- }
71
-
72
- for (const entry of tablesToDrop) {
73
- const ddl = `DROP TABLE IF EXISTS "${entry.config.name}" CASCADE;`;
74
- await sql.unsafe(ddl);
75
- console.log(colorize(` ✓ dropped ${entry.config.name}`, "green"));
76
- }
77
-
78
- if (migrationTableExists) {
79
- await sql.unsafe("DROP TABLE IF EXISTS public.__bungres_migrations CASCADE");
80
- console.log(colorize(` ✓ dropped __bungres_migrations`, "green"));
81
- }
82
-
83
- if (pushTableExists) {
84
- await sql.unsafe("DROP TABLE IF EXISTS public.__bungres_push CASCADE");
85
- console.log(colorize(` ✓ dropped __bungres_push`, "green"));
86
- }
87
-
88
- console.log(colorize("\nDrop complete.", "green"));
89
- } finally {
90
- await sql.end();
91
- }
92
- }
@@ -1,17 +0,0 @@
1
- import type { ResolvedConfig } from "../config.js";
2
- import { runDrop } from "./drop.js";
3
- import { runMigrate } from "./migrate.js";
4
-
5
- // ---------------------------------------------------------------------------
6
- // fresh — drop all tables and re-run all migrations from scratch
7
- // ---------------------------------------------------------------------------
8
-
9
- export async function runFresh(config: ResolvedConfig): Promise<void> {
10
- console.log("Dropping all tables...");
11
- await runDrop(config, { force: true });
12
-
13
- console.log("\nRe-running migrations...");
14
- await runMigrate(config);
15
-
16
- console.log("\nFresh complete.");
17
- }
@@ -1,151 +0,0 @@
1
- import { join, resolve } from "node:path";
2
- import { generateCreateTable } from "@bungres/orm";
3
- import type { TableConfig } from "@bungres/orm";
4
- import type { ResolvedConfig } from "../config.js";
5
- import { loadSchemas, type SchemaEntry } from "../schema-loader.js";
6
- import { diffSchemas, type SchemaSnapshot } from "../differ.js";
7
- import { colorize } from "../utils/colors.js";
8
-
9
- // ---------------------------------------------------------------------------
10
- // generate — diff-based SQL migration generator (drizzle-style numbering)
11
- //
12
- // First run: full CREATE TABLE for every table → 0001_init.sql
13
- // Later runs: only the changes (ADD COLUMN, CREATE TABLE, etc.) → 0002_*.sql
14
- //
15
- // Schema state is tracked in <migrationsDir>/.snapshot.json
16
- // ---------------------------------------------------------------------------
17
-
18
- const SNAPSHOT_FILE = ".snapshot.json";
19
-
20
- export async function runGenerate(
21
- config: ResolvedConfig,
22
- name?: string
23
- ): Promise<void> {
24
- console.log("@bungres/kit generate: loading schemas...");
25
-
26
- const schemas = await loadSchemas(config.schema);
27
-
28
- if (schemas.length === 0) {
29
- console.warn(colorize("No table definitions found. Check your schema glob pattern.", "yellow"));
30
- return;
31
- }
32
-
33
- const migrationsDir = resolve(config.migrationsDir);
34
- await Bun.$`mkdir -p ${migrationsDir}`.quiet();
35
-
36
- // ── Build current snapshot from loaded schemas ────────────────────────────
37
- const currentSnapshot: SchemaSnapshot = Object.fromEntries(
38
- schemas.map((s) => [s.config.name, s.config])
39
- );
40
-
41
- // ── Load previous snapshot (if any) ──────────────────────────────────────
42
- const snapshotPath = join(migrationsDir, SNAPSHOT_FILE);
43
- const snapshotFile = Bun.file(snapshotPath);
44
- const isFirstMigration = !(await snapshotFile.exists());
45
- const prevSnapshot: SchemaSnapshot = isFirstMigration
46
- ? {}
47
- : (JSON.parse(await snapshotFile.text()) as SchemaSnapshot);
48
-
49
- // ── Determine next sequence number ───────────────────────────────────────
50
- const now = new Date();
51
- const yyyy = now.getUTCFullYear();
52
- const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
53
- const dd = String(now.getUTCDate()).padStart(2, "0");
54
- const HH = String(now.getUTCHours()).padStart(2, "0");
55
- const MM = String(now.getUTCMinutes()).padStart(2, "0");
56
- const SS = String(now.getUTCSeconds()).padStart(2, "0");
57
-
58
- const prefix = `${yyyy}_${mm}_${dd}_${HH}${MM}${SS}`;
59
- const filename = name ? `${prefix}_${name}.sql` : `${prefix}.sql`;
60
- const outPath = join(migrationsDir, filename);
61
-
62
- // ── Diff ──────────────────────────────────────────────────────────────────
63
- let statements: string[];
64
- let summary: string[];
65
- let warnings: string[] = [];
66
-
67
- if (isFirstMigration) {
68
- // First migration — full schema sorted by FK dependency order
69
- const sorted = topoSort(schemas);
70
- statements = [
71
- `CREATE EXTENSION IF NOT EXISTS "pgcrypto";`,
72
- ``,
73
- ...sorted.flatMap((entry) => [
74
- `-- ${entry.exportName}`,
75
- generateCreateTable(entry.config, true),
76
- ``,
77
- ]),
78
- ];
79
- summary = sorted.map((s) => `CREATE TABLE ${s.config.name}`);
80
- } else {
81
- // Subsequent migrations — diff only
82
- const diff = diffSchemas(prevSnapshot, currentSnapshot);
83
-
84
- if (diff.statements.length === 0) {
85
- console.log(colorize("\nNo schema changes detected. Nothing to generate.", "yellow"));
86
- return;
87
- }
88
-
89
- statements = diff.statements;
90
- summary = diff.summary;
91
- warnings = diff.warnings;
92
- }
93
-
94
- // ── Write migration file ──────────────────────────────────────────────────
95
- const lines: string[] = [
96
- `-- Migration: ${filename}`,
97
- `-- Generated by @bungres/kit at ${new Date().toISOString()}`,
98
- `-- Changes: ${summary.join(", ")}`,
99
- ``,
100
- ...statements,
101
- ];
102
-
103
- await Bun.write(outPath, lines.join("\n"));
104
-
105
- // ── Save updated snapshot ─────────────────────────────────────────────────
106
- await Bun.write(snapshotPath, JSON.stringify(currentSnapshot, null, 2));
107
-
108
- console.log(colorize(`\nGenerated: ${outPath}`, "green"));
109
- console.log(` Changes:`);
110
- for (const s of summary) console.log(colorize(` + ${s}`, "cyan"));
111
-
112
- if (warnings.length > 0) {
113
- console.warn(colorize(`\n ⚠️ WARNING: Data Loss Detected!`, "red"));
114
- for (const w of warnings) console.warn(colorize(` ! ${w}`, "red"));
115
- console.warn(colorize(` Please review the generated migration carefully before applying it.\n`, "yellow"));
116
- }
117
-
118
- console.log(colorize(`\nRun \`bungres migrate\` to apply it.`, "cyan"));
119
- }
120
-
121
- // ---------------------------------------------------------------------------
122
- // Topological sort — referenced tables come before dependents
123
- // ---------------------------------------------------------------------------
124
-
125
- function topoSort(schemas: SchemaEntry[]): SchemaEntry[] {
126
- const byName = new Map<string, SchemaEntry>(
127
- schemas.map((s) => [s.config.name, s])
128
- );
129
-
130
- function deps(config: TableConfig): string[] {
131
- return Object.values(config.columns)
132
- .map((col) => col.references?.table)
133
- .filter((t): t is string => t !== undefined && byName.has(t) && t !== config.name);
134
- }
135
-
136
- const visited = new Set<string>();
137
- const result: SchemaEntry[] = [];
138
-
139
- function visit(name: string): void {
140
- if (visited.has(name)) return;
141
- visited.add(name);
142
- const entry = byName.get(name);
143
- if (!entry) return;
144
- for (const dep of deps(entry.config)) visit(dep);
145
- result.push(entry);
146
- }
147
-
148
- for (const schema of schemas) visit(schema.config.name);
149
-
150
- return result;
151
- }
@@ -1,84 +0,0 @@
1
- import { join, resolve } from "node:path";
2
- import type { ResolvedConfig } from "../config.js";
3
- import { colorize } from "../utils/colors.js";
4
-
5
- // ---------------------------------------------------------------------------
6
- // migrate — run pending .sql files, track applied in __bungres_migrations
7
- // ---------------------------------------------------------------------------
8
-
9
- const MIGRATIONS_TABLE = "__bungres_migrations";
10
-
11
- const CREATE_MIGRATIONS_TABLE = `
12
- CREATE TABLE IF NOT EXISTS "${MIGRATIONS_TABLE}" (
13
- id SERIAL PRIMARY KEY,
14
- name TEXT NOT NULL UNIQUE,
15
- applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
16
- );
17
- `.trim();
18
-
19
- export async function runMigrate(config: ResolvedConfig): Promise<void> {
20
- const migrationsDir = resolve(config.migrationsDir);
21
- const sql = new Bun.SQL(config.dbUrl);
22
-
23
- try {
24
- // Ensure tracking table exists
25
- await sql.unsafe(CREATE_MIGRATIONS_TABLE);
26
-
27
- // Discover migration files in order
28
- const glob = new Bun.Glob("*.sql");
29
- const files: string[] = [];
30
- for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
31
- files.push(file);
32
- }
33
- files.sort(); // 0001_ < 0002_ etc.
34
-
35
- if (files.length === 0) {
36
- console.log(colorize("No migration files found in " + migrationsDir, "yellow"));
37
- console.log(colorize("Run `bungres generate` first.", "yellow"));
38
- return;
39
- }
40
-
41
- // Fetch already applied
42
- const applied = await sql.unsafe(`SELECT name FROM "${MIGRATIONS_TABLE}"`);
43
- const appliedSet = new Set((applied as { name: string }[]).map((r) => r.name));
44
-
45
- const pending = files.filter((f) => !appliedSet.has(f));
46
-
47
- if (pending.length === 0) {
48
- console.log(colorize("Everything is up to date.", "green"));
49
- return;
50
- }
51
-
52
- console.log(colorize(`\nRunning ${pending.length} pending migration(s)...\n`, "cyan"));
53
-
54
- for (const file of pending) {
55
- const content = await Bun.file(join(migrationsDir, file)).text();
56
-
57
- if (config.verbose) {
58
- console.log(`-- ${file} --\n${content}\n`);
59
- }
60
-
61
- await sql.transaction(async (txSql: InstanceType<typeof Bun.SQL>) => {
62
- const statements = content
63
- .split(";")
64
- .map((s) => s.trim())
65
- .filter(Boolean);
66
-
67
- for (const stmt of statements) {
68
- await txSql.unsafe(stmt + ";");
69
- }
70
-
71
- await txSql.unsafe(
72
- `INSERT INTO "${MIGRATIONS_TABLE}" (name) VALUES ($1)`,
73
- [file]
74
- );
75
- });
76
-
77
- console.log(colorize(` ✓ ${file}`, "green"));
78
- }
79
-
80
- console.log(colorize("\nDone.", "green"));
81
- } finally {
82
- await sql.end();
83
- }
84
- }