migratex 1.0.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/README.md +188 -0
- package/bin/migratex.js +54 -0
- package/install.js +88 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# migratex
|
|
2
|
+
|
|
3
|
+
a schema diff and migration engine that sits on top of your ORM. it treats database changes like a graph — not a numbered list — enabling deterministic migrations, drift detection, and safe CI/CD workflows.
|
|
4
|
+
|
|
5
|
+
works with **drizzle**, **prisma**, and **typeorm**. supports **postgres** (mysql coming soon).
|
|
6
|
+
|
|
7
|
+
## why
|
|
8
|
+
|
|
9
|
+
every ORM handles migrations the same way: numbered files in a folder. `001_create_users.sql`, `002_add_posts.sql`, and so on. works fine solo. put a team on it and two devs on different branches will both create `004_*.sql`, someone has to manually rename theirs, and you pray the SQL doesn't conflict. at scale this is a constant source of broken deploys, merge pain, and wasted time.
|
|
10
|
+
|
|
11
|
+
migratex fixes this by replacing linear migrations with a DAG (like git uses for commits) and auto-generating the SQL from your ORM schema.
|
|
12
|
+
|
|
13
|
+
## features
|
|
14
|
+
|
|
15
|
+
### auto-generated migrations
|
|
16
|
+
stop writing SQL by hand. migratex reads your ORM schema, introspects the database, diffs them, and generates `up.sql` and `down.sql` automatically. change your schema file, run one command, done.
|
|
17
|
+
|
|
18
|
+
> **the problem this solves:** hand-written migrations drift from the ORM schema over time. someone forgets to add an index in the migration that they added in the schema. or the migration SQL has a typo that doesn't match what the ORM expects. migratex eliminates this entire class of bugs.
|
|
19
|
+
|
|
20
|
+
### DAG-based migration graph
|
|
21
|
+
migrations are nodes in a graph with content-addressed IDs (SHA-256 hashes), not numbered files. two branches can create migrations independently and merge cleanly — no renaming, no ordering conflicts.
|
|
22
|
+
|
|
23
|
+
> **the problem this solves:** on any team with parallel feature branches, linear migrations constantly collide. devs waste time renaming files, resolving fake conflicts, and coordinating who gets the next number. the DAG makes this a non-issue.
|
|
24
|
+
|
|
25
|
+
### conflict detection
|
|
26
|
+
migratex understands the schema semantically. if two branches add a `status` column to the same table with different types, it tells you. if they touch different tables, it merges cleanly. real conflicts get caught, false positives don't.
|
|
27
|
+
|
|
28
|
+
> **the problem this solves:** with linear migrations, you only find out about real schema conflicts when the migration runs (or worse, in production). migratex catches them at merge time, before anything touches the database.
|
|
29
|
+
|
|
30
|
+
### drift detection
|
|
31
|
+
compares your ORM schema against the actual database. catches out-of-band changes — someone ran `ALTER TABLE` directly on prod at 2am, or a migration was applied manually and never committed.
|
|
32
|
+
|
|
33
|
+
> **the problem this solves:** shadow changes to the database that nobody knows about until something breaks. "it works on my machine" but prod has an extra column nobody added through migrations.
|
|
34
|
+
|
|
35
|
+
### CI-friendly
|
|
36
|
+
`migratex check` validates the migration graph and detects drift. drop it in your pipeline — it exits non-zero if anything is wrong.
|
|
37
|
+
|
|
38
|
+
> **the problem this solves:** broken migrations making it to production because there's no automated gate. migratex check catches graph issues, missing parents, cycles, and schema drift before deploy.
|
|
39
|
+
|
|
40
|
+
### advisory locking
|
|
41
|
+
`migratex apply` uses database advisory locks. safe to run from multiple instances — no double-applying, no race conditions.
|
|
42
|
+
|
|
43
|
+
### up and down migrations
|
|
44
|
+
every migration automatically gets a reverse. rollbacks are generated, not hand-written.
|
|
45
|
+
|
|
46
|
+
## install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install migratex
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## quickstart
|
|
53
|
+
|
|
54
|
+
### init
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
$ migratex init
|
|
58
|
+
|
|
59
|
+
? ORM: drizzle
|
|
60
|
+
? Dialect: pg
|
|
61
|
+
? Schema path: ./src/schema.ts
|
|
62
|
+
? Connection: postgresql://user:pass@localhost:5432/mydb
|
|
63
|
+
? Migrations dir: ./migrations
|
|
64
|
+
|
|
65
|
+
Created migratex.config.yaml
|
|
66
|
+
Created migrations/
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### generate
|
|
70
|
+
|
|
71
|
+
change your ORM schema, then:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
$ migratex generate -m "add users table"
|
|
75
|
+
|
|
76
|
+
Reading ORM schema...
|
|
77
|
+
Introspecting database...
|
|
78
|
+
Computing diff...
|
|
79
|
+
|
|
80
|
+
Found 2 change(s):
|
|
81
|
+
create_table users
|
|
82
|
+
create_index users_email_idx
|
|
83
|
+
|
|
84
|
+
Generated migration: a1b2c3d4e5f6
|
|
85
|
+
migrations/a1b2c3d4e5f6/up.sql
|
|
86
|
+
migrations/a1b2c3d4e5f6/down.sql
|
|
87
|
+
Description: add users table
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### apply
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
$ migratex apply
|
|
94
|
+
|
|
95
|
+
Loading migration graph...
|
|
96
|
+
Acquiring lock...
|
|
97
|
+
Applying a1b2c3d4e5f6 — add users table... done
|
|
98
|
+
Releasing lock...
|
|
99
|
+
|
|
100
|
+
1 migration applied.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### check
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
$ migratex check
|
|
107
|
+
|
|
108
|
+
Validating migration graph... ok
|
|
109
|
+
Checking for drift... ok
|
|
110
|
+
No issues found.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
or when something is wrong:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
$ migratex check
|
|
117
|
+
|
|
118
|
+
Validating migration graph... ok
|
|
119
|
+
Checking for drift... DRIFT DETECTED
|
|
120
|
+
|
|
121
|
+
Table "users":
|
|
122
|
+
- Column "phone" exists in database but not in ORM schema
|
|
123
|
+
- Column "email" has type "text" in database but "varchar(255)" in ORM schema
|
|
124
|
+
|
|
125
|
+
1 table has drifted from the expected schema.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### status
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
$ migratex status
|
|
132
|
+
|
|
133
|
+
Migration graph:
|
|
134
|
+
a1b2c3d4 — create users table [applied]
|
|
135
|
+
d4e5f6a7 — add posts table [applied]
|
|
136
|
+
g7h8i9j0 — add sessions table [pending]
|
|
137
|
+
|
|
138
|
+
Heads: g7h8i9j0
|
|
139
|
+
Applied: 2 | Pending: 1
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## how it works
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
your ORM schema (TypeScript)
|
|
146
|
+
|
|
|
147
|
+
v
|
|
148
|
+
sidecar extracts it as JSON
|
|
149
|
+
|
|
|
150
|
+
v
|
|
151
|
+
go core diffs it against the actual DB
|
|
152
|
+
|
|
|
153
|
+
v
|
|
154
|
+
generates migration SQL (up + down)
|
|
155
|
+
|
|
|
156
|
+
v
|
|
157
|
+
stores it as a DAG node in migrations/
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
migrations/
|
|
162
|
+
graph.json # lightweight index (heads, metadata)
|
|
163
|
+
a1b2c3d4e5f6/
|
|
164
|
+
migration.json # full node (parents, operations, checksums)
|
|
165
|
+
up.sql # apply
|
|
166
|
+
down.sql # revert
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
each migration node has:
|
|
170
|
+
- **content-addressed ID** — SHA-256 of (parent IDs + operations), truncated to 12 hex chars
|
|
171
|
+
- **parent pointers** — like git commits, can have multiple parents for merges
|
|
172
|
+
- **checksum** — SHA-256 of the up SQL, detects tampering
|
|
173
|
+
- **operations** — structured diff, so conflicts can be detected semantically
|
|
174
|
+
|
|
175
|
+
## config
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
# migratex.config.yaml
|
|
179
|
+
orm: drizzle # drizzle | prisma | typeorm
|
|
180
|
+
dialect: pg # pg | mysql
|
|
181
|
+
connection: postgresql://user:pass@localhost:5432/mydb
|
|
182
|
+
schemaPath: ./src/schema.ts
|
|
183
|
+
migrationsDir: ./migrations
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## license
|
|
187
|
+
|
|
188
|
+
MIT
|
package/bin/migratex.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
|
|
8
|
+
function getBinaryPath() {
|
|
9
|
+
const platform = os.platform();
|
|
10
|
+
const arch = os.arch();
|
|
11
|
+
|
|
12
|
+
const platformMap = {
|
|
13
|
+
darwin: "darwin",
|
|
14
|
+
linux: "linux",
|
|
15
|
+
win32: "windows",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const archMap = {
|
|
19
|
+
x64: "amd64",
|
|
20
|
+
arm64: "arm64",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const goPlatform = platformMap[platform];
|
|
24
|
+
const goArch = archMap[arch];
|
|
25
|
+
|
|
26
|
+
if (!goPlatform || !goArch) {
|
|
27
|
+
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ext = platform === "win32" ? ".exe" : "";
|
|
32
|
+
const binaryName = `migratex-${goPlatform}-${goArch}${ext}`;
|
|
33
|
+
const binaryPath = path.join(__dirname, "..", "bin", binaryName);
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(binaryPath)) {
|
|
36
|
+
console.error(
|
|
37
|
+
`Binary not found: ${binaryPath}\nRun "npm run postinstall" or reinstall the package.`
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return binaryPath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const binary = getBinaryPath();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
execFileSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (err.status !== undefined) {
|
|
51
|
+
process.exit(err.status);
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
package/install.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const https = require("https");
|
|
2
|
+
const http = require("http");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const platformMap = {
|
|
8
|
+
darwin: "darwin",
|
|
9
|
+
linux: "linux",
|
|
10
|
+
win32: "windows",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const archMap = {
|
|
14
|
+
x64: "amd64",
|
|
15
|
+
arm64: "arm64",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const platform = platformMap[os.platform()];
|
|
19
|
+
const arch = archMap[os.arch()];
|
|
20
|
+
|
|
21
|
+
if (!platform || !arch) {
|
|
22
|
+
console.error(`Unsupported platform: ${os.platform()}-${os.arch()}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const pkg = require("./package.json");
|
|
27
|
+
const version = pkg.version;
|
|
28
|
+
const ext = os.platform() === "win32" ? ".exe" : "";
|
|
29
|
+
const binaryName = `migratex-${platform}-${arch}${ext}`;
|
|
30
|
+
|
|
31
|
+
const repo = "Vswaroop04/migrion";
|
|
32
|
+
const url = `https://github.com/${repo}/releases/download/v${version}/${binaryName}`;
|
|
33
|
+
|
|
34
|
+
const dest = path.join(__dirname, "bin", binaryName);
|
|
35
|
+
|
|
36
|
+
// Skip download if binary already exists (e.g. CI pre-packed it)
|
|
37
|
+
if (fs.existsSync(dest)) {
|
|
38
|
+
console.log(`migratex binary already exists at ${dest}`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`Downloading migratex v${version} for ${platform}-${arch}...`);
|
|
43
|
+
console.log(` ${url}`);
|
|
44
|
+
|
|
45
|
+
function download(url, dest, redirects = 0) {
|
|
46
|
+
if (redirects > 5) {
|
|
47
|
+
console.error("Too many redirects");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const client = url.startsWith("https") ? https : http;
|
|
52
|
+
|
|
53
|
+
client
|
|
54
|
+
.get(url, (res) => {
|
|
55
|
+
// Follow redirects (GitHub releases redirect to S3)
|
|
56
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
57
|
+
return download(res.headers.location, dest, redirects + 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (res.statusCode !== 200) {
|
|
61
|
+
console.error(`Failed to download: HTTP ${res.statusCode}`);
|
|
62
|
+
console.error(`URL: ${url}`);
|
|
63
|
+
console.error(
|
|
64
|
+
`\nMake sure a GitHub release exists for v${version} with the binary "${binaryName}".`
|
|
65
|
+
);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
70
|
+
const file = fs.createWriteStream(dest);
|
|
71
|
+
res.pipe(file);
|
|
72
|
+
|
|
73
|
+
file.on("finish", () => {
|
|
74
|
+
file.close();
|
|
75
|
+
// Make binary executable on unix
|
|
76
|
+
if (os.platform() !== "win32") {
|
|
77
|
+
fs.chmodSync(dest, 0o755);
|
|
78
|
+
}
|
|
79
|
+
console.log(`migratex installed successfully.`);
|
|
80
|
+
});
|
|
81
|
+
})
|
|
82
|
+
.on("error", (err) => {
|
|
83
|
+
console.error(`Download failed: ${err.message}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
download(url, dest);
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "migratex",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Schema diff and migration engine for ORMs (Drizzle, Prisma, TypeORM)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Vswaroop04/migrion"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"migratex": "bin/migratex.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"postinstall": "node install.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin/migratex.js",
|
|
18
|
+
"install.js"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"migrations",
|
|
22
|
+
"schema",
|
|
23
|
+
"database",
|
|
24
|
+
"orm",
|
|
25
|
+
"drizzle",
|
|
26
|
+
"prisma",
|
|
27
|
+
"typeorm",
|
|
28
|
+
"postgresql",
|
|
29
|
+
"mysql",
|
|
30
|
+
"dag"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
}
|
|
35
|
+
}
|