@cleverbrush/orm-cli 0.0.0-beta-20260424142030

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 ADDED
@@ -0,0 +1,166 @@
1
+ # `@cleverbrush/orm-cli`
2
+
3
+ A standalone CLI for managing PostgreSQL schema migrations for projects built
4
+ with [`@cleverbrush/orm`](https://github.com/cleverbrush/framework).
5
+
6
+ ```
7
+ cb-orm migrate generate [name] # diff DB → schema, emit TS migration file (name defaults to "migration")
8
+ cb-orm migrate run # apply pending migrations
9
+ cb-orm migrate rollback # roll back last batch
10
+ cb-orm migrate status # list applied/pending migrations
11
+ cb-orm db push # sync schema in-place (dev only)
12
+ ```
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ```sh
19
+ npm install --save-dev @cleverbrush/orm-cli
20
+ ```
21
+
22
+ `knex` is a peer dependency and must be installed in your project. `tsx` is a regular dependency and is installed automatically.
23
+
24
+ ---
25
+
26
+ ## Quick start
27
+
28
+ ### 1. Create `db.config.ts` in your project root
29
+
30
+ ```ts
31
+ // db.config.ts
32
+ import knex from 'knex';
33
+ import { defineConfig } from '@cleverbrush/orm-cli';
34
+ import { UserEntity, TodoEntity } from './src/db/schemas.js';
35
+
36
+ const db = knex({
37
+ client: 'pg',
38
+ connection: process.env.DATABASE_URL
39
+ });
40
+
41
+ export default defineConfig({
42
+ knex: db,
43
+ entities: { users: UserEntity, todos: TodoEntity },
44
+ migrations: {
45
+ directory: './migrations', // where .ts migration files are written
46
+ tableName: 'knex_migrations' // default; override if needed
47
+ }
48
+ });
49
+ ```
50
+
51
+ ### 2. Add convenience scripts to `package.json`
52
+
53
+ ```json
54
+ {
55
+ "scripts": {
56
+ "db:generate": "cb-orm migrate generate",
57
+ "db:run": "cb-orm migrate run",
58
+ "db:rollback": "cb-orm migrate rollback",
59
+ "db:status": "cb-orm migrate status",
60
+ "db:push": "cb-orm db push"
61
+ }
62
+ }
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Commands
68
+
69
+ ### `migrate generate <name>`
70
+
71
+ Compares every entity in `config.entities` against the live database and emits
72
+ a single timestamped TypeScript migration file when differences are found.
73
+
74
+ - New tables → `CREATE TABLE`.
75
+ - Existing tables with column/index/FK changes → `ALTER TABLE`.
76
+ - Multiple tables are FK-dependency-ordered (parents created first, dropped last).
77
+ - Polymorphic (CTI) entities include one entry per variant table.
78
+ - File written as `<dir>/YYYYMMDDHHmmss_<name>.ts`.
79
+
80
+ ```sh
81
+ npx cb-orm migrate generate add_role_column
82
+ # → migrations/20260423120000_add_role_column.ts
83
+ ```
84
+
85
+ ### `migrate run`
86
+
87
+ Applies all pending migrations via `knex.migrate.latest`.
88
+
89
+ ```sh
90
+ npx cb-orm migrate run
91
+ # Batch 1. Applied 2 migration(s):
92
+ # ✓ 20260423000001_init.ts
93
+ # ✓ 20260423120000_add_role_column.ts
94
+ ```
95
+
96
+ Use `--to <filename>` to migrate up to a specific file:
97
+
98
+ ```sh
99
+ npx cb-orm migrate run --to 20260423000001_init.ts
100
+ ```
101
+
102
+ ### `migrate rollback`
103
+
104
+ Rolls back the most recently applied batch.
105
+
106
+ ```sh
107
+ npx cb-orm migrate rollback
108
+ # Roll back all batches:
109
+ npx cb-orm migrate rollback --all
110
+ ```
111
+
112
+ ### `migrate status`
113
+
114
+ Lists applied and pending migrations.
115
+
116
+ ```sh
117
+ npx cb-orm migrate status
118
+
119
+ # Applied migrations:
120
+ # ✓ 20260423000001_init.ts
121
+ # Pending migrations:
122
+ # ○ 20260423120000_add_role_column.ts
123
+ ```
124
+
125
+ ### `db push`
126
+
127
+ Applies all schema changes directly to the database **without** writing a
128
+ migration file. Runs inside a single transaction.
129
+
130
+ > **Warning — dev only.** Requires `--yes` when `NODE_ENV=production`.
131
+
132
+ ```sh
133
+ npx cb-orm db push # asks for confirmation
134
+ npx cb-orm db push --yes # skip confirmation
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Options
140
+
141
+ | Flag | Commands | Description |
142
+ |------|----------|-------------|
143
+ | `--config <path>` | all | Path to config file (default: `db.config.ts` in cwd) |
144
+ | `--dir <path>` | all | Migrations directory (overrides `config.migrations.directory`) |
145
+ | `--to <name>` | `migrate run` | Apply up to a specific migration filename |
146
+ | `--all` | `migrate rollback` | Roll back all batches |
147
+ | `--yes` | `db push` | Skip the interactive confirmation prompt |
148
+
149
+ ---
150
+
151
+ ## How it works
152
+
153
+ The CLI delegates all schema intelligence to `@cleverbrush/knex-schema`:
154
+
155
+ | Step | Function |
156
+ |------|---------|
157
+ | Detect new tables | `tableExistsInDb(knex, tableName)` |
158
+ | Generate CREATE TABLE source | `generateCreateTableSource(schema)` |
159
+ | Introspect live table | `introspectDatabase(knex, tableName)` |
160
+ | Diff schema vs DB | `diffSchema(schema, dbState)` |
161
+ | Generate ALTER TABLE source | `generateMigration(diff, tableName)` |
162
+ | Apply diff without file | `applyDiff(knex, diff, tableName)` |
163
+ | Polymorphic variant tables | `getPolymorphicVariantSchemas(schema)` |
164
+
165
+ `tsx` is used to load `db.config.ts` at runtime by registering the
166
+ `tsx/esm/api` hook before any dynamic `import()` of the config file.
package/dist/bin.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin.ts
4
+ import { register } from "tsx/esm/api";
5
+ register();
6
+ var { run } = await import("./cli-XMDVRIZG.js");
7
+ await run(process.argv.slice(2));
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/run.ts
4
+ async function run(config, flags) {
5
+ const migrationConfig = buildMigrationConfig(config, flags);
6
+ const toMigration = flags["--to"];
7
+ if (toMigration) {
8
+ const [batchNo, applied] = await config.knex.migrate.up({
9
+ ...migrationConfig,
10
+ name: toMigration
11
+ });
12
+ if (applied.length === 0) {
13
+ console.log(`${toMigration} \u2014 already applied.`);
14
+ } else {
15
+ console.log(
16
+ `Batch ${batchNo}. Applied: ${applied.join(", ")}`
17
+ );
18
+ }
19
+ } else {
20
+ const [batchNo, applied] = await config.knex.migrate.latest(migrationConfig);
21
+ if (applied.length === 0) {
22
+ console.log("Already up to date.");
23
+ } else {
24
+ console.log(
25
+ `Batch ${batchNo}. Applied ${applied.length} migration(s):`
26
+ );
27
+ for (const m of applied) console.log(` \u2713 ${m}`);
28
+ }
29
+ }
30
+ }
31
+ function buildMigrationConfig(config, flags) {
32
+ return {
33
+ directory: flags["--dir"] ?? config.migrations.directory,
34
+ tableName: config.migrations.tableName ?? "knex_migrations",
35
+ loadExtensions: [".ts", ".js"]
36
+ };
37
+ }
38
+
39
+ export {
40
+ run,
41
+ buildMigrationConfig
42
+ };
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { createRequire } from "module";
5
+
6
+ // src/config.ts
7
+ import { existsSync } from "fs";
8
+ import path from "path";
9
+ import { pathToFileURL } from "url";
10
+ var CANDIDATE_NAMES = [
11
+ "db.config.ts",
12
+ "db.config.js",
13
+ "db.config.mjs"
14
+ ];
15
+ async function loadConfig(configPath) {
16
+ const resolved = configPath ? path.resolve(process.cwd(), configPath) : findConfigFile();
17
+ if (!existsSync(resolved)) {
18
+ throw new Error(`Config file not found: ${resolved}`);
19
+ }
20
+ const mod = await import(pathToFileURL(resolved).href);
21
+ const config = mod.default ?? mod;
22
+ assertConfig(config);
23
+ return config;
24
+ }
25
+ function findConfigFile() {
26
+ for (const name of CANDIDATE_NAMES) {
27
+ const candidate = path.resolve(process.cwd(), name);
28
+ if (existsSync(candidate)) return candidate;
29
+ }
30
+ throw new Error(
31
+ `No config file found. Create db.config.ts in your project root, or pass --config <path>.`
32
+ );
33
+ }
34
+ function assertConfig(config) {
35
+ if (!config || typeof config !== "object") {
36
+ throw new Error(
37
+ "Config must be an object (default export of db.config.ts)."
38
+ );
39
+ }
40
+ const c = config;
41
+ if (!c.knex || typeof c.knex.schema !== "object") {
42
+ throw new Error("config.knex must be a Knex instance.");
43
+ }
44
+ if (!c.entities || typeof c.entities !== "object") {
45
+ throw new Error(
46
+ "config.entities must be an object mapping names to Entity instances."
47
+ );
48
+ }
49
+ if (!c.migrations || typeof c.migrations.directory !== "string") {
50
+ throw new Error(
51
+ "config.migrations.directory must be a non-empty string path."
52
+ );
53
+ }
54
+ }
55
+
56
+ // src/cli.ts
57
+ var _require = createRequire(import.meta.url);
58
+ var _pkg = _require("../package.json");
59
+ async function run(argv) {
60
+ const [cmd, sub, ...rest] = argv;
61
+ if (!cmd || cmd === "--help" || cmd === "-h") {
62
+ printHelp();
63
+ return;
64
+ }
65
+ if (cmd === "--version" || cmd === "-v") {
66
+ console.log(_pkg.version ?? "unknown");
67
+ return;
68
+ }
69
+ const flags = parseFlags(rest);
70
+ const configPath = flags["--config"];
71
+ try {
72
+ if (cmd === "migrate") {
73
+ switch (sub) {
74
+ case "generate": {
75
+ let name = "migration";
76
+ for (let i = 0; i < rest.length; i++) {
77
+ if (rest[i].startsWith("-")) {
78
+ const next = rest[i + 1];
79
+ if (next !== void 0 && !next.startsWith("-"))
80
+ i++;
81
+ } else {
82
+ name = rest[i];
83
+ break;
84
+ }
85
+ }
86
+ const config = await loadConfig(configPath);
87
+ const { generate } = await import("./generate-CMLVA6DT.js");
88
+ await generate(name, config, flags);
89
+ break;
90
+ }
91
+ case "run": {
92
+ const config = await loadConfig(configPath);
93
+ const { run: runMigrations } = await import("./run-PSIPMF6V.js");
94
+ await runMigrations(config, flags);
95
+ break;
96
+ }
97
+ case "rollback": {
98
+ const config = await loadConfig(configPath);
99
+ const { rollback } = await import("./rollback-AVV6DTXF.js");
100
+ await rollback(config, flags);
101
+ break;
102
+ }
103
+ case "status": {
104
+ const config = await loadConfig(configPath);
105
+ const { status } = await import("./status-W3A6JWR3.js");
106
+ await status(config, flags);
107
+ break;
108
+ }
109
+ default:
110
+ fatal(
111
+ `Unknown migrate command: ${sub ?? "(none)"}. Run \`cb-orm --help\` for usage.`
112
+ );
113
+ }
114
+ } else if (cmd === "db") {
115
+ switch (sub) {
116
+ case "push": {
117
+ const config = await loadConfig(configPath);
118
+ const { push } = await import("./push-SNCDOATW.js");
119
+ await push(config, flags);
120
+ break;
121
+ }
122
+ default:
123
+ fatal(
124
+ `Unknown db command: ${sub ?? "(none)"}. Run \`cb-orm --help\` for usage.`
125
+ );
126
+ }
127
+ } else {
128
+ fatal(`Unknown command: ${cmd}. Run \`cb-orm --help\` for usage.`);
129
+ }
130
+ } catch (err) {
131
+ console.error(
132
+ `
133
+ Error: ${err instanceof Error ? err.message : String(err)}
134
+ `
135
+ );
136
+ process.exit(1);
137
+ }
138
+ }
139
+ function parseFlags(args) {
140
+ const flags = {};
141
+ for (let i = 0; i < args.length; i++) {
142
+ const arg = args[i];
143
+ if (arg.startsWith("--")) {
144
+ const next = args[i + 1];
145
+ if (next !== void 0 && !next.startsWith("-")) {
146
+ flags[arg] = next;
147
+ i++;
148
+ } else {
149
+ flags[arg] = true;
150
+ }
151
+ }
152
+ }
153
+ return flags;
154
+ }
155
+ function fatal(msg) {
156
+ console.error(`
157
+ Error: ${msg}
158
+ `);
159
+ process.exit(1);
160
+ }
161
+ function printHelp() {
162
+ console.log(`
163
+ cb-orm \u2014 Schema migration CLI for @cleverbrush/orm
164
+
165
+ USAGE
166
+ cb-orm <command> [options]
167
+
168
+ COMMANDS
169
+ migrate generate [name] Diff DB vs schema, emit a timestamped TS migration file (default name: "migration")
170
+ migrate run Apply pending migrations (knex.migrate.latest)
171
+ migrate rollback Roll back last batch (knex.migrate.rollback)
172
+ migrate status List applied and pending migrations
173
+ db push Sync schema to DB in-place (dev only \u2014 no migration file)
174
+
175
+ OPTIONS
176
+ --config <path> Path to db.config.ts (default: db.config.ts in cwd)
177
+ --dir <path> Migrations directory (overrides config.migrations.directory)
178
+ --to <name> (migrate run) Apply up to a specific migration by filename
179
+ --all (migrate rollback) Roll back all applied migrations
180
+ --yes (db push) Skip the confirmation prompt
181
+ --help, -h Show this help
182
+ --version, -v Show version
183
+ `);
184
+ }
185
+ export {
186
+ parseFlags,
187
+ run
188
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function run(argv: string[]): Promise<void>;
2
+ /** Parse `--flag` / `--flag value` pairs from an argv tail. */
3
+ export declare function parseFlags(args: string[]): Record<string, string | true>;
@@ -0,0 +1,11 @@
1
+ import type { OrmCliConfig } from '../types.js';
2
+ /**
3
+ * Diff the current entity schemas against the committed snapshot and write a
4
+ * single timestamped TypeScript migration file when changes are detected.
5
+ * Also updates the snapshot file so the next run starts from the new baseline.
6
+ *
7
+ * No live database connection is needed — the snapshot is the source of truth.
8
+ *
9
+ * File name pattern: `YYYYMMDDHHmmss_<name>.ts` (matches Knex defaults).
10
+ */
11
+ export declare function generate(name: string, config: OrmCliConfig, flags: Record<string, string | true>): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import type { OrmCliConfig } from '../types.js';
2
+ /**
3
+ * Sync every registered entity's schema to the live database without
4
+ * generating a migration file.
5
+ *
6
+ * - New tables are created via {@link generateCreateTable}.
7
+ * - Existing tables are altered via {@link applyDiff}.
8
+ * - All changes run inside a single transaction.
9
+ * - Aborted (no `--yes`) when `NODE_ENV=production`.
10
+ */
11
+ export declare function push(config: OrmCliConfig, flags: Record<string, string | true>): Promise<void>;
@@ -0,0 +1,5 @@
1
+ import type { OrmCliConfig } from '../types.js';
2
+ /**
3
+ * Roll back the last migration batch (or all batches with `--all`).
4
+ */
5
+ export declare function rollback(config: OrmCliConfig, flags: Record<string, string | true>): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import type { OrmCliConfig } from '../types.js';
2
+ /**
3
+ * Apply pending migrations via `knex.migrate.latest`.
4
+ * Pass `--to <migrationName>` to migrate up to a specific file.
5
+ */
6
+ export declare function run(config: OrmCliConfig, flags: Record<string, string | true>): Promise<void>;
7
+ export declare function buildMigrationConfig(config: OrmCliConfig, flags: Record<string, string | true>): {
8
+ directory: string;
9
+ tableName: string;
10
+ loadExtensions: string[];
11
+ };
@@ -0,0 +1,5 @@
1
+ import type { OrmCliConfig } from '../types.js';
2
+ /**
3
+ * List applied and pending migrations in a two-column display.
4
+ */
5
+ export declare function status(config: OrmCliConfig, flags: Record<string, string | true>): Promise<void>;
@@ -0,0 +1,13 @@
1
+ import type { OrmCliConfig } from './types.js';
2
+ /**
3
+ * Load and validate a CLI config file.
4
+ *
5
+ * Resolution order:
6
+ * 1. Explicit `configPath` argument (relative to `process.cwd()`).
7
+ * 2. `db.config.ts`, `db.config.js`, `db.config.mjs` in `process.cwd()`.
8
+ *
9
+ * The file must export a default {@link OrmCliConfig} object (or use the
10
+ * {@link defineConfig} helper). TypeScript files work because the tsx ESM
11
+ * hook was registered in `bin.ts` before this module is imported.
12
+ */
13
+ export declare function loadConfig(configPath?: string): Promise<OrmCliConfig>;
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/generate.ts
4
+ import { mkdirSync, writeFileSync } from "fs";
5
+ import path from "path";
6
+ import {
7
+ generateMigrationsForContext,
8
+ loadSnapshot,
9
+ writeSnapshot
10
+ } from "@cleverbrush/knex-schema";
11
+ async function generate(name, config, flags) {
12
+ const dir = flags["--dir"] ?? config.migrations.directory;
13
+ const absDir = path.resolve(process.cwd(), dir);
14
+ const snapshotPath = config.migrations.snapshot ?? path.join(absDir, "snapshot.json");
15
+ const absSnapshotPath = path.resolve(process.cwd(), snapshotPath);
16
+ const entities = Object.values(config.entities);
17
+ const prevSnapshot = loadSnapshot(absSnapshotPath);
18
+ const result = generateMigrationsForContext(entities, prevSnapshot);
19
+ if (result.isEmpty) {
20
+ console.log("No schema changes detected.");
21
+ return;
22
+ }
23
+ mkdirSync(absDir, { recursive: true });
24
+ const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
25
+ const safeName = name.replace(/[^a-z0-9_]/gi, "_");
26
+ const filename = `${timestamp}_${safeName}.ts`;
27
+ const filepath = path.join(absDir, filename);
28
+ writeFileSync(filepath, result.full, "utf-8");
29
+ console.log(`Created migration: ${filepath}`);
30
+ writeSnapshot(absSnapshotPath, result.nextSnapshot);
31
+ console.log(`Updated snapshot: ${absSnapshotPath}`);
32
+ }
33
+ function formatTimestamp(d) {
34
+ const p = (n) => String(n).padStart(2, "0");
35
+ return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
36
+ }
37
+ export {
38
+ generate
39
+ };
@@ -0,0 +1,2 @@
1
+ export type { MigrationsConfig, OrmCliConfig } from './types.js';
2
+ export { defineConfig } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // src/types.ts
2
+ function defineConfig(config) {
3
+ return config;
4
+ }
5
+ export {
6
+ defineConfig
7
+ };
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/push.ts
4
+ import path from "path";
5
+ import readline from "readline";
6
+ import {
7
+ applyDiff,
8
+ diffSchema,
9
+ entitiesToSnapshot,
10
+ generateCreateTable,
11
+ getPolymorphicVariantSchemas,
12
+ getTableName,
13
+ introspectDatabase,
14
+ isDiffEmpty,
15
+ tableExistsInDb,
16
+ writeSnapshot
17
+ } from "@cleverbrush/knex-schema";
18
+ async function push(config, flags) {
19
+ const isProduction = process.env.NODE_ENV === "production";
20
+ const confirmed = flags["--yes"] === true;
21
+ if (isProduction && !confirmed) {
22
+ console.error(
23
+ "\nError: `db push` is not allowed in NODE_ENV=production without the --yes flag.\n"
24
+ );
25
+ process.exit(1);
26
+ }
27
+ if (!confirmed) {
28
+ const answer = await prompt(
29
+ "\nThis will apply schema changes directly to the database (no migration file will be created).\nContinue? [y/N] "
30
+ );
31
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
32
+ console.log("Aborted.");
33
+ return;
34
+ }
35
+ }
36
+ const tableEntries = [];
37
+ for (const entity of Object.values(config.entities)) {
38
+ const schema = entity.schema;
39
+ const tableName = getTableName(schema);
40
+ tableEntries.push({ schema, tableName });
41
+ for (const vs of getPolymorphicVariantSchemas(schema)) {
42
+ const vt = vs.getExtension("tableName");
43
+ if (vt) tableEntries.push({ schema: vs, tableName: vt });
44
+ }
45
+ }
46
+ const seen = /* @__PURE__ */ new Set();
47
+ const unique = tableEntries.filter((e) => {
48
+ if (seen.has(e.tableName)) return false;
49
+ seen.add(e.tableName);
50
+ return true;
51
+ });
52
+ await config.knex.transaction(async (trx) => {
53
+ let changed = 0;
54
+ for (const { schema, tableName } of unique) {
55
+ const exists = await tableExistsInDb(trx, tableName);
56
+ if (!exists) {
57
+ await generateCreateTable(schema)(trx);
58
+ console.log(` + Created table: ${tableName}`);
59
+ changed++;
60
+ } else {
61
+ const dbState = await introspectDatabase(trx, tableName);
62
+ const diff = diffSchema(schema, dbState);
63
+ if (!isDiffEmpty(diff)) {
64
+ await applyDiff(trx, diff, tableName);
65
+ console.log(` ~ Altered table: ${tableName}`);
66
+ changed++;
67
+ }
68
+ }
69
+ }
70
+ if (changed === 0) {
71
+ console.log("No schema changes detected.");
72
+ } else {
73
+ console.log(`
74
+ Applied ${changed} table change(s).`);
75
+ }
76
+ });
77
+ const dir = config.migrations.directory;
78
+ const absDir = path.resolve(process.cwd(), dir);
79
+ const snapshotPath = config.migrations.snapshot ?? path.join(absDir, "snapshot.json");
80
+ const absSnapshotPath = path.resolve(process.cwd(), snapshotPath);
81
+ writeSnapshot(
82
+ absSnapshotPath,
83
+ entitiesToSnapshot(Object.values(config.entities))
84
+ );
85
+ console.log(`Updated snapshot: ${absSnapshotPath}`);
86
+ }
87
+ function prompt(question) {
88
+ const rl = readline.createInterface({
89
+ input: process.stdin,
90
+ output: process.stdout
91
+ });
92
+ return new Promise((resolve) => {
93
+ rl.question(question, (answer) => {
94
+ rl.close();
95
+ resolve(answer);
96
+ });
97
+ });
98
+ }
99
+ export {
100
+ push
101
+ };
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildMigrationConfig
4
+ } from "./chunk-E43XQHR3.js";
5
+
6
+ // src/commands/rollback.ts
7
+ async function rollback(config, flags) {
8
+ const migrationConfig = buildMigrationConfig(config, flags);
9
+ const all = flags["--all"] === true;
10
+ const [batchNo, reverted] = await config.knex.migrate.rollback(
11
+ migrationConfig,
12
+ all
13
+ );
14
+ if (reverted.length === 0) {
15
+ console.log("Nothing to roll back.");
16
+ } else {
17
+ console.log(
18
+ `Batch ${batchNo} rolled back. Reverted ${reverted.length} migration(s):`
19
+ );
20
+ for (const m of reverted) console.log(` \u2717 ${m}`);
21
+ }
22
+ }
23
+ export {
24
+ rollback
25
+ };
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildMigrationConfig,
4
+ run
5
+ } from "./chunk-E43XQHR3.js";
6
+ export {
7
+ buildMigrationConfig,
8
+ run
9
+ };
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildMigrationConfig
4
+ } from "./chunk-E43XQHR3.js";
5
+
6
+ // src/commands/status.ts
7
+ async function status(config, flags) {
8
+ const migrationConfig = buildMigrationConfig(config, flags);
9
+ const [completed, pending] = await config.knex.migrate.list(migrationConfig);
10
+ if (completed.length === 0 && pending.length === 0) {
11
+ console.log("No migrations found in the configured directory.");
12
+ return;
13
+ }
14
+ if (completed.length > 0) {
15
+ console.log("\nApplied migrations:");
16
+ for (const m of completed) {
17
+ const name = typeof m === "string" ? m : m.name ?? m.file ?? m;
18
+ console.log(` \u2713 ${name}`);
19
+ }
20
+ }
21
+ if (pending.length > 0) {
22
+ console.log("\nPending migrations:");
23
+ for (const m of pending) {
24
+ const name = typeof m === "string" ? m : m.file ?? m.name ?? m;
25
+ console.log(` \u25CB ${name}`);
26
+ }
27
+ } else {
28
+ console.log("\nDatabase is up to date.");
29
+ }
30
+ }
31
+ export {
32
+ status
33
+ };
@@ -0,0 +1,57 @@
1
+ import type { Entity } from '@cleverbrush/knex-schema';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Migration-runner configuration block inside {@link OrmCliConfig}.
5
+ */
6
+ export interface MigrationsConfig {
7
+ /** Directory where migration files are read from / written to. */
8
+ directory: string;
9
+ /**
10
+ * Knex migration tracking table name.
11
+ * @defaultValue `'knex_migrations'`
12
+ */
13
+ tableName?: string;
14
+ /**
15
+ * Path to the schema snapshot file. Must be committed to version control
16
+ * — it is the source of truth for `migrate generate`.
17
+ * @defaultValue `<directory>/snapshot.json`
18
+ */
19
+ snapshot?: string;
20
+ }
21
+ /**
22
+ * Shape of the default export expected in `db.config.ts`.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // db.config.ts
27
+ * import { defineConfig } from '@cleverbrush/orm-cli';
28
+ * import knex from './src/db/knex.js';
29
+ * import { UserEntity, TodoEntity } from './src/db/schemas.js';
30
+ *
31
+ * export default defineConfig({
32
+ * knex,
33
+ * entities: { users: UserEntity, todos: TodoEntity },
34
+ * migrations: { directory: './migrations' },
35
+ * });
36
+ * ```
37
+ */
38
+ export interface OrmCliConfig {
39
+ /** A live Knex instance connected to your database. */
40
+ knex: Knex;
41
+ /**
42
+ * Map of entity name to `Entity` definition. The same map you pass to
43
+ * `createDb()` from `@cleverbrush/orm`.
44
+ */
45
+ entities: Record<string, Entity<any, any>>;
46
+ /** Migration configuration. */
47
+ migrations: MigrationsConfig;
48
+ }
49
+ /**
50
+ * Identity helper that provides TypeScript autocompletion for `db.config.ts`.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * export default defineConfig({ knex, entities, migrations });
55
+ * ```
56
+ */
57
+ export declare function defineConfig(config: OrmCliConfig): OrmCliConfig;
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "author": "Andrew Zolotukhin <andrew_zol@cleverbrush.com>",
3
+ "bugs": {
4
+ "url": "https://github.com/cleverbrush/framework/issues",
5
+ "email": "andrew_zol@cleverbrush.com"
6
+ },
7
+ "bin": {
8
+ "cb-orm": "./dist/bin.js"
9
+ },
10
+ "dependencies": {
11
+ "@cleverbrush/knex-schema": "0.0.0-beta-20260424142030",
12
+ "tsx": "^4.19.0"
13
+ },
14
+ "peerDependencies": {
15
+ "knex": ">=3.1.0"
16
+ },
17
+ "description": "CLI for @cleverbrush/orm — migrate generate/run/rollback/status and db push",
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "homepage": "https://github.com/cleverbrush/framework/tree/master/libs/orm-cli#readme",
22
+ "keywords": [
23
+ "orm",
24
+ "knex",
25
+ "typescript",
26
+ "migrations",
27
+ "cli",
28
+ "cleverbrush"
29
+ ],
30
+ "license": "BSD 3-Clause",
31
+ "main": "./dist/index.js",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ }
37
+ },
38
+ "sideEffects": false,
39
+ "name": "@cleverbrush/orm-cli",
40
+ "readme": "https://github.com/cleverbrush/framework/tree/master/libs/orm-cli#readme",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "github:cleverbrush/framework"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup && tsc --project tsconfig.build.json --emitDeclarationOnly",
47
+ "clean": "rm -rf dist tsconfig.build.tsbuildinfo"
48
+ },
49
+ "type": "module",
50
+ "types": "./dist/index.d.ts",
51
+ "version": "0.0.0-beta-20260424142030"
52
+ }