@schemavaults/dbh 0.10.2 → 0.11.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/.claude/skills/database-migrations/SKILL.md +244 -0
- package/README.md +15 -1
- package/dist/utils/validateMigrationDirectory.d.ts +35 -0
- package/dist/utils/validateMigrationDirectory.js +157 -0
- package/dist/utils/validateMigrationDirectory.js.map +1 -0
- package/dist-cli/cli.js +21 -21
- package/package.json +2 -2
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: database-migrations
|
|
3
|
+
description: Authoring, building, validating, and running PostgreSQL database migrations with the @schemavaults/dbh package. Use when a project depends on @schemavaults/dbh and you are creating or editing Kysely migration files, setting up a migrations/ directory, or when the user mentions migrations, up()/down(), schema changes, or the dbh CLI's migrate / build-db-migrations / validate-migration-directory commands.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Database Migrations with @schemavaults/dbh
|
|
7
|
+
|
|
8
|
+
`@schemavaults/dbh` provides [Kysely](https://kysely.dev/) migrations for
|
|
9
|
+
PostgreSQL, applied through the `dbh` CLI. Migrations are opinionated: every file
|
|
10
|
+
is a numbered module that exports an `up()` and a `down()` function. TypeScript
|
|
11
|
+
source migrations are **built** to JavaScript first, then **applied** with the
|
|
12
|
+
CLI.
|
|
13
|
+
|
|
14
|
+
Invoke the CLI with your package runner. Use **`bunx @schemavaults/dbh`** for
|
|
15
|
+
**validating and building** migrations — `build-db-migrations` uses Bun's
|
|
16
|
+
bundler and requires Bun anyway. Use **`npx @schemavaults/dbh`** for **running /
|
|
17
|
+
applying** migrations (`migrate` and `reverse`): most PostgreSQL drivers are
|
|
18
|
+
built for Node.js rather than Bun, so apply migrations on the Node runtime.
|
|
19
|
+
|
|
20
|
+
## One-time setup (for consumers)
|
|
21
|
+
|
|
22
|
+
Migrations import the `sql` template tag from `@/sql` rather than directly from
|
|
23
|
+
the package. This indirection is required by the build step (see the note under
|
|
24
|
+
"Building migrations"), so configure it once:
|
|
25
|
+
|
|
26
|
+
1. **Create a local `sql` module** somewhere in your source tree, e.g.
|
|
27
|
+
`./src/db/sql.ts`, that re-exports the tag from the package:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// src/db/sql.ts
|
|
31
|
+
export { sql, sql as default } from "@schemavaults/dbh/sql";
|
|
32
|
+
export type * from "@schemavaults/dbh/sql";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. **Configure the `@/sql` path alias** in your `tsconfig.json` so migration
|
|
36
|
+
sources typecheck and resolve:
|
|
37
|
+
|
|
38
|
+
```jsonc
|
|
39
|
+
{
|
|
40
|
+
"compilerOptions": {
|
|
41
|
+
"baseUrl": ".",
|
|
42
|
+
"paths": {
|
|
43
|
+
"@/sql": ["./src/db/sql.ts"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. **Create a migrations directory**, e.g. `./src/db/migrations/`, and add your
|
|
50
|
+
numbered migration files there.
|
|
51
|
+
|
|
52
|
+
## Migration file format
|
|
53
|
+
|
|
54
|
+
Each migration is a single file in your migrations directory. The rules are:
|
|
55
|
+
|
|
56
|
+
1. **The directory is non-empty.**
|
|
57
|
+
2. **Each file name is prefixed with a 5-digit migration number**, followed by a
|
|
58
|
+
short kebab-case description, e.g. `00000-template-migration.ts`,
|
|
59
|
+
`00001-create-users-table.ts`. The number defines apply order.
|
|
60
|
+
3. **Each module exports an `up(db)` and a `down(db)` function.** `up()` applies
|
|
61
|
+
the change; `down()` must reverse it exactly so migrations can be rolled back.
|
|
62
|
+
4. **Migration numbers are unique** — never reuse a number. If two branches both
|
|
63
|
+
add `00040-*.ts`, that collision must be resolved by renumbering one of them
|
|
64
|
+
before merge.
|
|
65
|
+
|
|
66
|
+
Both `up` and `down` receive a `Kysely<any>` instance and return a `Promise`.
|
|
67
|
+
Import the `Kysely` type from the package: `import type { Kysely } from "@schemavaults/dbh"`.
|
|
68
|
+
|
|
69
|
+
### Example: using the `Kysely<any>` query builder
|
|
70
|
+
|
|
71
|
+
Prefer the typed query builder for schema operations:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// 00001-create-users-table.ts
|
|
75
|
+
import type { Kysely } from "@schemavaults/dbh";
|
|
76
|
+
|
|
77
|
+
export async function up(db: Kysely<any>): Promise<void> {
|
|
78
|
+
await db.schema
|
|
79
|
+
.createTable("users")
|
|
80
|
+
.addColumn("user_id", "uuid", (col) => col.primaryKey())
|
|
81
|
+
.addColumn("email", "text", (col) => col.notNull().unique())
|
|
82
|
+
.addColumn("created_at", "bigint", (col) => col.notNull())
|
|
83
|
+
.execute();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
87
|
+
await db.schema.dropTable("users").execute();
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Example: using the `sql` template tag
|
|
92
|
+
|
|
93
|
+
For statements the builder can't express (or raw DDL), import `sql` from
|
|
94
|
+
`@/sql` (your local module from setup, which re-exports Kysely's `sql` tag) and
|
|
95
|
+
call `.execute(db)`:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// 00002-create-squirrels-table.ts
|
|
99
|
+
import type { Kysely } from "@schemavaults/dbh";
|
|
100
|
+
import { sql } from "@/sql";
|
|
101
|
+
|
|
102
|
+
export async function up(db: Kysely<any>): Promise<void> {
|
|
103
|
+
await sql`
|
|
104
|
+
CREATE TABLE IF NOT EXISTS EXAMPLE_SQUIRRELS (
|
|
105
|
+
squirrel_id UUID PRIMARY KEY,
|
|
106
|
+
squirrel_name TEXT NOT NULL,
|
|
107
|
+
created_at BIGINT NOT NULL
|
|
108
|
+
);
|
|
109
|
+
`.execute(db);
|
|
110
|
+
|
|
111
|
+
// Always interpolate values via ${...}; the sql tag parameterizes them.
|
|
112
|
+
await sql`CREATE INDEX squirrels_name_idx ON EXAMPLE_SQUIRRELS (squirrel_name);`.execute(
|
|
113
|
+
db,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
118
|
+
await sql`DROP TABLE IF EXISTS EXAMPLE_SQUIRRELS;`.execute(db);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
> Important: migration files must **always** import `sql` from `@/sql`, never
|
|
123
|
+
> directly from `@schemavaults/dbh/sql`. The `build-db-migrations` step rewrites
|
|
124
|
+
> the literal `@/sql` import specifier to a relative path pointing at the built,
|
|
125
|
+
> standalone `sql.js`, so the import must be written exactly as `@/sql` for the
|
|
126
|
+
> build to work. (This is why the one-time setup configures the `@/sql` alias.)
|
|
127
|
+
|
|
128
|
+
### Empty template migration
|
|
129
|
+
|
|
130
|
+
A no-op migration is valid (useful as a starting template):
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// 00000-template-migration.ts
|
|
134
|
+
import type { Kysely } from "@schemavaults/dbh";
|
|
135
|
+
|
|
136
|
+
export async function up(
|
|
137
|
+
db: Kysely<any>, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
138
|
+
): Promise<void> {}
|
|
139
|
+
|
|
140
|
+
export async function down(
|
|
141
|
+
db: Kysely<any>, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
142
|
+
): Promise<void> {}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Validating migrations
|
|
146
|
+
|
|
147
|
+
Before building or applying, assert your source migrations directory is
|
|
148
|
+
well-formed. The `validate-migration-directory` command checks all four rules
|
|
149
|
+
above and exits `0` when valid, non-zero otherwise (good for CI / pre-commit):
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
bunx @schemavaults/dbh validate-migration-directory ./src/db/migrations
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
It reports each problem with an `[ERROR]`/`[WARN]` prefix:
|
|
156
|
+
- empty directory,
|
|
157
|
+
- a file missing the 5-digit prefix,
|
|
158
|
+
- a module missing `up()` or `down()`,
|
|
159
|
+
- duplicate migration numbers (branch collisions).
|
|
160
|
+
|
|
161
|
+
Treat duplicate numbers as warnings (non-fatal) with `--duplicates-as-warnings`.
|
|
162
|
+
|
|
163
|
+
## Building migrations
|
|
164
|
+
|
|
165
|
+
TypeScript migrations must be compiled to JavaScript before they're applied
|
|
166
|
+
(the `migrate` step runs on Node and imports `.js`). The `build-db-migrations`
|
|
167
|
+
command uses Bun's bundler and also builds the standalone `sql` module the
|
|
168
|
+
migrations depend on. Point `--sql-module` at the local `sql.ts` you created
|
|
169
|
+
during setup:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
bunx @schemavaults/dbh build-db-migrations ./src/db/migrations \
|
|
173
|
+
--outdir ./dist/migrations \
|
|
174
|
+
--sql-module ./src/db/sql.ts \
|
|
175
|
+
--sql-outdir ./dist
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Key options:
|
|
179
|
+
- `<migrations-src>` — directory of `.ts` migration sources (positional).
|
|
180
|
+
- `--outdir <dir>` — where compiled `.js` migrations are written (required).
|
|
181
|
+
- `--sql-module <path>` — path to your local `sql.ts` module to build alongside (required).
|
|
182
|
+
- `--sql-outdir <dir>` — where the built `sql.js` goes (defaults to the parent of `--outdir`).
|
|
183
|
+
- `--external <pkg...>` — packages to keep external (default: `@schemavaults/dbh`, `kysely`).
|
|
184
|
+
|
|
185
|
+
`build-db-migrations` requires `bun` to be installed and on the PATH.
|
|
186
|
+
|
|
187
|
+
## Running migrations
|
|
188
|
+
|
|
189
|
+
Apply built migrations with `migrate`, and roll back with `reverse`. Both take
|
|
190
|
+
the **built** migration folder and require an `--environment`; credentials come
|
|
191
|
+
from `process.env` (or an `--env-file`). Run these with **`npx`** (Node.js):
|
|
192
|
+
most PostgreSQL drivers target Node rather than Bun.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Apply all pending migrations (to latest):
|
|
196
|
+
npx @schemavaults/dbh migrate ./dist/migrations --environment production --env-file ./.env.production
|
|
197
|
+
|
|
198
|
+
# Apply up to a specific version (the migration name w/o extension):
|
|
199
|
+
npx @schemavaults/dbh migrate ./dist/migrations 00001-create-users-table --environment staging
|
|
200
|
+
|
|
201
|
+
# Roll back down to a target version:
|
|
202
|
+
npx @schemavaults/dbh reverse ./dist/migrations 00000-template-migration --environment staging
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Options for `migrate` / `reverse`:
|
|
206
|
+
- `<folder>` — path to the built migration folder (positional).
|
|
207
|
+
- `[version]` / `<version>` — target migration name; `migrate` defaults to latest, `reverse` requires it.
|
|
208
|
+
- `-e, --environment <env>` — `development | test | staging | production` (required).
|
|
209
|
+
- `--ws-proxy-url <url>` — custom Neon-compatible WebSocket proxy URL.
|
|
210
|
+
- `--env-file <path>` — load DB credentials from a `.env` file first.
|
|
211
|
+
|
|
212
|
+
Each result line prints as `[Up|Down] <migrationName>: <Success|Error|NotExecuted>`.
|
|
213
|
+
|
|
214
|
+
### Programmatic API
|
|
215
|
+
|
|
216
|
+
The same operations are available from `@schemavaults/dbh/migrate` for tests or
|
|
217
|
+
custom scripts, using the adapter's Kysely instance:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { migrate, reverse } from "@schemavaults/dbh/migrate";
|
|
221
|
+
|
|
222
|
+
await migrate({ db: adapter.db, migrationFolder, version /* optional */ });
|
|
223
|
+
await reverse({ db: adapter.db, migrationFolder, version });
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Typical end-to-end flow
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# 1. Validate the source migrations directory.
|
|
230
|
+
bunx @schemavaults/dbh validate-migration-directory ./src/db/migrations
|
|
231
|
+
|
|
232
|
+
# 2. Build .ts migrations (+ sql module) to .js.
|
|
233
|
+
bunx @schemavaults/dbh build-db-migrations ./src/db/migrations \
|
|
234
|
+
--outdir ./dist/migrations --sql-module ./src/db/sql.ts --sql-outdir ./dist
|
|
235
|
+
|
|
236
|
+
# 3. Apply the built migrations (npx / Node.js — pg drivers target Node).
|
|
237
|
+
npx @schemavaults/dbh migrate ./dist/migrations --environment production --env-file ./.env.production
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Required environment variables (for migrate/reverse)
|
|
241
|
+
|
|
242
|
+
`POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_URL`, `POSTGRES_HOST`,
|
|
243
|
+
`POSTGRES_PORT`, `POSTGRES_DATABASE` (and optional `POSTGRES_URL_NON_POOLING`).
|
|
244
|
+
Set `SCHEMAVAULTS_DBH_DEBUG=true` for verbose debug logging.
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Ensure that you have both `postgres` and a `postgres-ws-proxy` containers runnin
|
|
|
20
20
|
|
|
21
21
|
You'll likely want to replace the `build:` sections for the services in the e2e test example `.yml` file with `image:`. For example, use `image: postgres:17.7` for the `postgres` service. For the proxy, you can pull the docker image from `ghcr.io/schemavaults/dbh/postgres-ws-proxy`; use the version number equal to your `@schemavaults/dbh` npm package installation:
|
|
22
22
|
```md
|
|
23
|
-
# NPM Package: @schemavaults/dbh@0.
|
|
23
|
+
# NPM Package: @schemavaults/dbh@0.11.0 => ghcr.io/schemavaults/dbh/postgres-ws-proxy:0.11.0
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
### In your application server code
|
|
@@ -40,6 +40,20 @@ npx @schemavaults/dbh --help
|
|
|
40
40
|
# or `bun run cli --help` if you have the dbh source repository as your working directory
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
#### Validate the shape of a migrations directory
|
|
44
|
+
```bash
|
|
45
|
+
# assert the migrations directory is well-formed:
|
|
46
|
+
# - non-empty
|
|
47
|
+
# - every file is prefixed with a 5-digit migration number (e.g. 00000-my-migration.ts)
|
|
48
|
+
# - every module exports an up() and down() function
|
|
49
|
+
# - there are no duplicate migration numbers (branch collisions, e.g. 00040-a.ts and 00040-b.ts)
|
|
50
|
+
# exits 0 when the directory is valid, non-zero otherwise.
|
|
51
|
+
bunx @schemavaults/dbh validate-migration-directory ./src/db/migrations
|
|
52
|
+
|
|
53
|
+
# treat duplicate migration numbers as warnings instead of errors (still exits 0)
|
|
54
|
+
bunx @schemavaults/dbh validate-migration-directory ./src/db/migrations --duplicates-as-warnings
|
|
55
|
+
```
|
|
56
|
+
|
|
43
57
|
#### Build example database migrations with the CLI
|
|
44
58
|
```bash
|
|
45
59
|
mkdir ./tests/tmp
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type MigrationValidationLevel = "error" | "warning";
|
|
2
|
+
export interface MigrationValidationIssue {
|
|
3
|
+
level: MigrationValidationLevel;
|
|
4
|
+
message: string;
|
|
5
|
+
/** The offending file name (relative to the migration directory), if any. */
|
|
6
|
+
file?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ValidateMigrationDirectoryOptions {
|
|
9
|
+
/**
|
|
10
|
+
* When true, duplicate migration numbers are reported as warnings instead of
|
|
11
|
+
* errors (they will not, on their own, cause validation to fail).
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
duplicatesAsWarnings?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ValidateMigrationDirectoryResult {
|
|
17
|
+
/** True when there are no `error`-level issues. */
|
|
18
|
+
ok: boolean;
|
|
19
|
+
/** The absolute path that was validated. */
|
|
20
|
+
directory: string;
|
|
21
|
+
/** The migration file names that were considered (relative to `directory`). */
|
|
22
|
+
migrationFiles: string[];
|
|
23
|
+
issues: MigrationValidationIssue[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validates the shape of an opinionated Kysely migrations directory.
|
|
27
|
+
*
|
|
28
|
+
* Asserts that the directory:
|
|
29
|
+
* - exists and is non-empty,
|
|
30
|
+
* - contains only files prefixed with a 5-digit migration number,
|
|
31
|
+
* - has no duplicate migration numbers (branch collisions), and
|
|
32
|
+
* - exports an `up()` and `down()` function from every migration module.
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateMigrationDirectory(directory: string, options?: ValidateMigrationDirectoryOptions): Promise<ValidateMigrationDirectoryResult>;
|
|
35
|
+
export default validateMigrationDirectory;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { pathToFileURL } from "url";
|
|
4
|
+
/**
|
|
5
|
+
* Extensions that are treated as migration modules. `.d.ts` declaration files
|
|
6
|
+
* are explicitly ignored (handled separately below).
|
|
7
|
+
*/
|
|
8
|
+
const MIGRATION_FILE_EXTENSIONS = [
|
|
9
|
+
".ts",
|
|
10
|
+
".mts",
|
|
11
|
+
".cts",
|
|
12
|
+
".js",
|
|
13
|
+
".mjs",
|
|
14
|
+
".cjs",
|
|
15
|
+
];
|
|
16
|
+
/** Migration file names must begin with exactly 5 digits (e.g. 00000-foo.ts). */
|
|
17
|
+
const MIGRATION_PREFIX_REGEX = /^(\d{5})(?:\D|$)/;
|
|
18
|
+
function isMigrationFile(fileName) {
|
|
19
|
+
if (fileName.startsWith(".")) {
|
|
20
|
+
// Ignore hidden/dotfiles.
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (fileName.endsWith(".d.ts")) {
|
|
24
|
+
// Ignore TypeScript declaration files.
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return MIGRATION_FILE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validates the shape of an opinionated Kysely migrations directory.
|
|
31
|
+
*
|
|
32
|
+
* Asserts that the directory:
|
|
33
|
+
* - exists and is non-empty,
|
|
34
|
+
* - contains only files prefixed with a 5-digit migration number,
|
|
35
|
+
* - has no duplicate migration numbers (branch collisions), and
|
|
36
|
+
* - exports an `up()` and `down()` function from every migration module.
|
|
37
|
+
*/
|
|
38
|
+
export async function validateMigrationDirectory(directory, options = {}) {
|
|
39
|
+
const { duplicatesAsWarnings = false } = options;
|
|
40
|
+
const resolved = path.resolve(directory);
|
|
41
|
+
const issues = [];
|
|
42
|
+
if (!fs.existsSync(resolved)) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
directory: resolved,
|
|
46
|
+
migrationFiles: [],
|
|
47
|
+
issues: [
|
|
48
|
+
{
|
|
49
|
+
level: "error",
|
|
50
|
+
message: `Migration directory does not exist: ${resolved}`,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const stat = fs.statSync(resolved);
|
|
56
|
+
if (!stat.isDirectory()) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
directory: resolved,
|
|
60
|
+
migrationFiles: [],
|
|
61
|
+
issues: [
|
|
62
|
+
{
|
|
63
|
+
level: "error",
|
|
64
|
+
message: `Migration path is not a directory: ${resolved}`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const entries = fs.readdirSync(resolved, { withFileTypes: true });
|
|
70
|
+
const migrationFiles = entries
|
|
71
|
+
.filter((entry) => entry.isFile() && isMigrationFile(entry.name))
|
|
72
|
+
.map((entry) => entry.name)
|
|
73
|
+
.sort();
|
|
74
|
+
// 1) Directory must be non-empty.
|
|
75
|
+
if (migrationFiles.length === 0) {
|
|
76
|
+
issues.push({
|
|
77
|
+
level: "error",
|
|
78
|
+
message: `Migration directory is empty (no migration files found): ${resolved}`,
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
directory: resolved,
|
|
83
|
+
migrationFiles,
|
|
84
|
+
issues,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// 2) Every file must be prefixed with a 5-digit migration number.
|
|
88
|
+
// Track which numbers map to which files for duplicate detection.
|
|
89
|
+
const numberToFiles = new Map();
|
|
90
|
+
for (const file of migrationFiles) {
|
|
91
|
+
const match = MIGRATION_PREFIX_REGEX.exec(file);
|
|
92
|
+
if (!match) {
|
|
93
|
+
issues.push({
|
|
94
|
+
level: "error",
|
|
95
|
+
file,
|
|
96
|
+
message: `File is not prefixed with a 5-digit migration number (e.g. 00000-my-migration.ts): ${file}`,
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const number = match[1];
|
|
101
|
+
const existing = numberToFiles.get(number);
|
|
102
|
+
if (existing) {
|
|
103
|
+
existing.push(file);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
numberToFiles.set(number, [file]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 3) No duplicate migration numbers (collisions across branches).
|
|
110
|
+
for (const [number, files] of numberToFiles) {
|
|
111
|
+
if (files.length > 1) {
|
|
112
|
+
issues.push({
|
|
113
|
+
level: duplicatesAsWarnings ? "warning" : "error",
|
|
114
|
+
message: `Duplicate migration number '${number}' used by ${files.length} files: ${files.join(", ")}`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// 4) Every module must export an up() and down() function.
|
|
119
|
+
for (const file of migrationFiles) {
|
|
120
|
+
const fullPath = path.join(resolved, file);
|
|
121
|
+
let mod;
|
|
122
|
+
try {
|
|
123
|
+
mod = (await import(pathToFileURL(fullPath).href));
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
issues.push({
|
|
127
|
+
level: "error",
|
|
128
|
+
file,
|
|
129
|
+
message: `Failed to import migration module '${file}': ${error instanceof Error ? error.message : String(error)}`,
|
|
130
|
+
});
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (typeof mod.up !== "function") {
|
|
134
|
+
issues.push({
|
|
135
|
+
level: "error",
|
|
136
|
+
file,
|
|
137
|
+
message: `Migration '${file}' does not export an up() function`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (typeof mod.down !== "function") {
|
|
141
|
+
issues.push({
|
|
142
|
+
level: "error",
|
|
143
|
+
file,
|
|
144
|
+
message: `Migration '${file}' does not export a down() function`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const ok = !issues.some((issue) => issue.level === "error");
|
|
149
|
+
return {
|
|
150
|
+
ok,
|
|
151
|
+
directory: resolved,
|
|
152
|
+
migrationFiles,
|
|
153
|
+
issues,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export default validateMigrationDirectory;
|
|
157
|
+
//# sourceMappingURL=validateMigrationDirectory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateMigrationDirectory.js","sourceRoot":"","sources":["../../src/utils/validateMigrationDirectory.ts"],"names":[],"mappings":"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"}
|