@deterministic-code/migraters 0.0.1 → 0.0.2
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 +1 -0
- package/cli.yaml +264 -0
- package/dist/bin/migrate-create.js +0 -0
- package/dist/bin/migrate-down.js +0 -0
- package/dist/bin/migrate-init.js +0 -0
- package/dist/bin/migrate-setup.js +0 -0
- package/dist/bin/migrate-up.js +0 -0
- package/package.json +7 -6
- package/src/abstractions/migrater.ts +0 -13
- package/src/abstractions/sql-dialect-base.ts +0 -36
- package/src/abstractions/sql-dialect-factory.ts +0 -29
- package/src/abstractions/sql-dialect.ts +0 -17
- package/src/bin/migrate-create.ts +0 -80
- package/src/bin/migrate-down.ts +0 -163
- package/src/bin/migrate-init.ts +0 -88
- package/src/bin/migrate-setup.ts +0 -98
- package/src/bin/migrate-up.ts +0 -111
- package/src/cli-contract.ts +0 -25
- package/src/cli-spec.ts +0 -64
- package/src/cli.ts +0 -52
- package/src/create-sequence.ts +0 -17
- package/src/datasource-migrate.ts +0 -314
- package/src/dialects/mysql-dialect.test.ts +0 -93
- package/src/dialects/mysql-dialect.ts +0 -67
- package/src/dialects/oracle-dialect.test.ts +0 -118
- package/src/dialects/oracle-dialect.ts +0 -91
- package/src/dialects/postgres-dialect.test.ts +0 -80
- package/src/dialects/postgres-dialect.ts +0 -65
- package/src/dialects/sqlite-dialect.rollback.test.ts +0 -30
- package/src/dialects/sqlite-dialect.test.ts +0 -77
- package/src/dialects/sqlite-dialect.ts +0 -96
- package/src/dialects/sqlserver-dialect.test.ts +0 -99
- package/src/dialects/sqlserver-dialect.ts +0 -87
- package/src/generate-help.test.ts +0 -33
- package/src/generate-help.ts +0 -173
- package/src/help.test.ts +0 -48
- package/src/help.ts +0 -12
- package/src/index.ts +0 -23
- package/src/infrastructure/connection-resolver.ts +0 -18
- package/src/infrastructure/default-factory.test.ts +0 -18
- package/src/infrastructure/default-factory.ts +0 -17
- package/src/infrastructure/interop-default.test.ts +0 -9
- package/src/infrastructure/interop-default.ts +0 -2
- package/src/infrastructure/message-templates.ts +0 -30
- package/src/infrastructure/split-on-go.ts +0 -19
- package/src/infrastructure/sql-templates.ts +0 -10
- package/src/infrastructure/templates-root.ts +0 -13
- package/src/init-select.test.ts +0 -26
- package/src/init-select.ts +0 -73
- package/src/migrater.ts +0 -92
- package/src/migration-client.ts +0 -21
- package/src/path-exists.ts +0 -10
- package/src/setup-sql.test.ts +0 -16
- package/src/sql.ts +0 -17
- package/tsconfig.build.json +0 -19
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Migrate executables for deterministic-code.
|
package/cli.yaml
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Source of truth for migrate command names, argv layout, and --help copy.
|
|
2
|
+
# TypeScript, Rust, and C# parsers must match this file.
|
|
3
|
+
# IMigrater (setup, up, down, create) lives in api.yaml. SQL spelling is a provider detail.
|
|
4
|
+
# Help templates in shared/templates/help are generated:
|
|
5
|
+
# npm run generate:help
|
|
6
|
+
# Usage line for each verb: `Usage: migrate-<verb> ` + args in order.
|
|
7
|
+
|
|
8
|
+
command: migrate-{verb}
|
|
9
|
+
|
|
10
|
+
providers:
|
|
11
|
+
- sqlite
|
|
12
|
+
- postgres
|
|
13
|
+
- mysql
|
|
14
|
+
- sqlserver
|
|
15
|
+
- oracle
|
|
16
|
+
|
|
17
|
+
# Lookup: lowercase, strip spaces/underscores/hyphens, then match a provider or alias.
|
|
18
|
+
provider_aliases:
|
|
19
|
+
sqlite3: sqlite
|
|
20
|
+
mariadb: mysql
|
|
21
|
+
postgresql: postgres
|
|
22
|
+
pg: postgres
|
|
23
|
+
mssql: sqlserver
|
|
24
|
+
mssqlserver: sqlserver
|
|
25
|
+
ora: oracle
|
|
26
|
+
|
|
27
|
+
exit_codes:
|
|
28
|
+
success: 0
|
|
29
|
+
usage: 2
|
|
30
|
+
runtime: 1
|
|
31
|
+
|
|
32
|
+
help:
|
|
33
|
+
- -h
|
|
34
|
+
- --help
|
|
35
|
+
|
|
36
|
+
help_stream: stderr
|
|
37
|
+
|
|
38
|
+
# Canonical argv is `--flag value`. `--flag=value` is optional extra syntax.
|
|
39
|
+
aliases:
|
|
40
|
+
--migrate-path: --migrations-path
|
|
41
|
+
--migrate-root: --migrations-root
|
|
42
|
+
|
|
43
|
+
env_var_list_separator: " / "
|
|
44
|
+
|
|
45
|
+
migrations:
|
|
46
|
+
path_flag: --migrations-path
|
|
47
|
+
root_flag: --migrations-root
|
|
48
|
+
root_default: sql
|
|
49
|
+
layout: "<root>/<provider>/migrations"
|
|
50
|
+
discovery: top_level_up_down_pairs
|
|
51
|
+
missing_directory: errors/migrate-path-not-dir
|
|
52
|
+
|
|
53
|
+
create:
|
|
54
|
+
name_pattern: "^[a-z][a-z0-9_]*$"
|
|
55
|
+
sequence_digits: 4
|
|
56
|
+
|
|
57
|
+
checksum:
|
|
58
|
+
algorithm: sha256
|
|
59
|
+
drift_bypass: MIGRATE_ALLOW_CHECKSUM_DRIFT
|
|
60
|
+
|
|
61
|
+
# Documented when a command sets show_connection_env: true.
|
|
62
|
+
connection_environment:
|
|
63
|
+
sqlite: [SQLITE_PATH, DB_PATH]
|
|
64
|
+
postgres: [PG_CONNECTION_STRING, DATABASE_URL]
|
|
65
|
+
mysql: [MYSQL_URL, DATABASE_URL]
|
|
66
|
+
sqlserver: [MSSQL_URL, DATABASE_URL]
|
|
67
|
+
oracle: [ORACLE_CONNECT_STRING, DATABASE_URL]
|
|
68
|
+
|
|
69
|
+
commands:
|
|
70
|
+
- verb: init
|
|
71
|
+
summary: |
|
|
72
|
+
Selects the dialects this tree will compile and install. Repeat --dialect
|
|
73
|
+
once per provider. Re-running replaces the previous set. --all restores
|
|
74
|
+
every dialect. Does not take --connection.
|
|
75
|
+
examples: |
|
|
76
|
+
{{command}} --dialect sqlite --dialect postgres
|
|
77
|
+
{{command}} --dialect oracle
|
|
78
|
+
{{command}} --all
|
|
79
|
+
args:
|
|
80
|
+
- flag: --dialect
|
|
81
|
+
placeholder: providers
|
|
82
|
+
required: false
|
|
83
|
+
help: "Dialect to enable. Repeat for each dialect. Aliases from this file apply."
|
|
84
|
+
- flag: --all
|
|
85
|
+
required: false
|
|
86
|
+
help: "Enable every dialect. Restores the committed full-provider set."
|
|
87
|
+
|
|
88
|
+
- verb: setup
|
|
89
|
+
show_connection_env: true
|
|
90
|
+
summary: |
|
|
91
|
+
Creates the bookkeeping tables (migrates, migrate_logs) for the chosen dialect
|
|
92
|
+
and the migrations directory (mkdir -p). Idempotent — re-running against an
|
|
93
|
+
already-setup database is a no-op. For sqlite, also mkdir -p of the parent
|
|
94
|
+
directory of --connection so a fresh-tree path like ./.test/foo.sqlite works.
|
|
95
|
+
examples: |
|
|
96
|
+
# sqlite — bare path or sqlite:// URL; both create a file on disk.
|
|
97
|
+
{{command}} --provider sqlite --connection ./app.sqlite
|
|
98
|
+
{{command}} --provider sqlite --connection sqlite:///absolute/path/app.sqlite
|
|
99
|
+
|
|
100
|
+
# postgres — URL or keyword form.
|
|
101
|
+
{{command}} --provider postgres --connection postgresql://user:pass@host:5432/dbname
|
|
102
|
+
|
|
103
|
+
# mysql — URL form.
|
|
104
|
+
{{command}} --provider mysql --connection mysql://user:pass@host:3306/dbname
|
|
105
|
+
|
|
106
|
+
# sqlserver / oracle — pass a driver-native connection string.
|
|
107
|
+
{{command}} --provider sqlserver --connection "Server=host;Database=dbname;User Id=user;Password=pass;"
|
|
108
|
+
{{command}} --provider oracle --connection "user/pass@host:1521/service"
|
|
109
|
+
|
|
110
|
+
# env-var fallback when --connection is omitted:
|
|
111
|
+
SQLITE_PATH=./app.sqlite {{command}} --provider sqlite
|
|
112
|
+
DATABASE_URL=postgresql://... {{command}} --provider postgres
|
|
113
|
+
DATABASE_URL=mysql://... {{command}} --provider mysql
|
|
114
|
+
|
|
115
|
+
# explicit in-memory sqlite:
|
|
116
|
+
{{command}} --provider sqlite --connection :memory:
|
|
117
|
+
args:
|
|
118
|
+
- flag: --provider
|
|
119
|
+
placeholder: providers
|
|
120
|
+
required: true
|
|
121
|
+
help: "Database dialect. Required."
|
|
122
|
+
- flag: --connection
|
|
123
|
+
placeholder: url
|
|
124
|
+
required: true
|
|
125
|
+
help: "Connection string. Required (or set the per-dialect env var below)."
|
|
126
|
+
- flag: --migrations-path
|
|
127
|
+
placeholder: dir
|
|
128
|
+
required: false
|
|
129
|
+
help: "Migrations directory (default: ./sql/<dialect>/migrations)."
|
|
130
|
+
- flag: --and-up
|
|
131
|
+
required: false
|
|
132
|
+
help: "After setup, immediately invoke migrate-up so the chain is applied in one step."
|
|
133
|
+
|
|
134
|
+
- verb: up
|
|
135
|
+
show_connection_env: true
|
|
136
|
+
summary: |
|
|
137
|
+
Applies ALL pending migrations in sequence, each in its own transaction.
|
|
138
|
+
examples: |
|
|
139
|
+
# sqlite — bare path or sqlite:// URL; both create a file on disk.
|
|
140
|
+
{{command}} --provider sqlite --connection ./app.sqlite
|
|
141
|
+
{{command}} --provider sqlite --connection sqlite:///absolute/path/app.sqlite
|
|
142
|
+
|
|
143
|
+
# postgres — URL or keyword form.
|
|
144
|
+
{{command}} --provider postgres --connection postgresql://user:pass@host:5432/dbname
|
|
145
|
+
|
|
146
|
+
# mysql — URL form.
|
|
147
|
+
{{command}} --provider mysql --connection mysql://user:pass@host:3306/dbname
|
|
148
|
+
|
|
149
|
+
# sqlserver / oracle — pass a driver-native connection string.
|
|
150
|
+
{{command}} --provider sqlserver --connection "Server=host;Database=dbname;User Id=user;Password=pass;"
|
|
151
|
+
{{command}} --provider oracle --connection "user/pass@host:1521/service"
|
|
152
|
+
|
|
153
|
+
# env-var fallback when --connection is omitted:
|
|
154
|
+
SQLITE_PATH=./app.sqlite {{command}} --provider sqlite
|
|
155
|
+
DATABASE_URL=postgresql://... {{command}} --provider postgres
|
|
156
|
+
|
|
157
|
+
# custom migrations layout (default looks under sql/<dialect>/migrations):
|
|
158
|
+
{{command}} --provider sqlite --connection ./app.sqlite --migrations-path ./db/changes/sqlite
|
|
159
|
+
{{command}} --provider sqlite --connection ./app.sqlite --migrations-root ./db/changes
|
|
160
|
+
|
|
161
|
+
# one-shot: apply the next pending migration only, then exit.
|
|
162
|
+
{{command}} --provider sqlite --connection ./app.sqlite --one
|
|
163
|
+
args:
|
|
164
|
+
- flag: --provider
|
|
165
|
+
placeholder: providers
|
|
166
|
+
required: true
|
|
167
|
+
help: "Database dialect. Required."
|
|
168
|
+
- flag: --connection
|
|
169
|
+
placeholder: url
|
|
170
|
+
required: true
|
|
171
|
+
help: "Connection string. Required (or set the per-dialect env var below)."
|
|
172
|
+
- flag: --migrations-path
|
|
173
|
+
placeholder: dir
|
|
174
|
+
required: false
|
|
175
|
+
help: "Full path to the migrations directory (e.g. sql/sqlite/migrations)."
|
|
176
|
+
- flag: --migrations-root
|
|
177
|
+
placeholder: dir
|
|
178
|
+
required: false
|
|
179
|
+
help: |
|
|
180
|
+
Root prefix; the script then looks at <migrations-root>/<dialect>/migrations.
|
|
181
|
+
Defaults to 'sql'. Ignored when --migrations-path is also set.
|
|
182
|
+
- flag: --one
|
|
183
|
+
required: false
|
|
184
|
+
help: "Apply only the next pending migration, then exit. Default is to apply all pending migrations."
|
|
185
|
+
|
|
186
|
+
- verb: down
|
|
187
|
+
show_connection_env: true
|
|
188
|
+
summary: |
|
|
189
|
+
Rolls back the MOST RECENTLY applied migration (one step). DESTRUCTIVE: prints a
|
|
190
|
+
random 4-letter uppercase token on stderr and refuses to proceed unless the
|
|
191
|
+
operator types it back on stdin (or passes --confirm <TOKEN> matching it).
|
|
192
|
+
examples: |
|
|
193
|
+
# sqlite — bare path or sqlite:// URL.
|
|
194
|
+
{{command}} --provider sqlite --connection ./app.sqlite
|
|
195
|
+
{{command}} --provider sqlite --connection sqlite:///absolute/path/app.sqlite
|
|
196
|
+
|
|
197
|
+
# postgres — URL or keyword form.
|
|
198
|
+
{{command}} --provider postgres --connection postgresql://user:pass@host:5432/dbname
|
|
199
|
+
|
|
200
|
+
# mysql — URL form.
|
|
201
|
+
{{command}} --provider mysql --connection mysql://user:pass@host:3306/dbname
|
|
202
|
+
|
|
203
|
+
# sqlserver / oracle — pass a driver-native connection string.
|
|
204
|
+
{{command}} --provider sqlserver --connection "Server=host;Database=dbname;User Id=user;Password=pass;"
|
|
205
|
+
{{command}} --provider oracle --connection "user/pass@host:1521/service"
|
|
206
|
+
|
|
207
|
+
# env-var fallback when --connection is omitted:
|
|
208
|
+
SQLITE_PATH=./app.sqlite {{command}} --provider sqlite
|
|
209
|
+
DATABASE_URL=postgresql://... {{command}} --provider postgres
|
|
210
|
+
|
|
211
|
+
# custom migrations layout (default looks under sql/<dialect>/migrations):
|
|
212
|
+
{{command}} --provider sqlite --connection ./app.sqlite --migrations-path ./db/changes/sqlite
|
|
213
|
+
args:
|
|
214
|
+
- flag: --provider
|
|
215
|
+
placeholder: providers
|
|
216
|
+
required: true
|
|
217
|
+
help: "Database dialect. Required."
|
|
218
|
+
- flag: --connection
|
|
219
|
+
placeholder: url
|
|
220
|
+
required: true
|
|
221
|
+
help: "Connection string. Required (or set the per-dialect env var below)."
|
|
222
|
+
- flag: --migrations-path
|
|
223
|
+
placeholder: dir
|
|
224
|
+
required: false
|
|
225
|
+
help: "Full path to the migrations directory."
|
|
226
|
+
- flag: --migrations-root
|
|
227
|
+
placeholder: dir
|
|
228
|
+
required: false
|
|
229
|
+
help: |
|
|
230
|
+
Root prefix; the script then looks at <migrations-root>/<dialect>/migrations.
|
|
231
|
+
Defaults to 'sql'. Ignored when --migrations-path is also set.
|
|
232
|
+
- flag: --confirm
|
|
233
|
+
placeholder: TOKEN
|
|
234
|
+
required: false
|
|
235
|
+
help: |
|
|
236
|
+
Skip the interactive prompt. The TOKEN must match the value
|
|
237
|
+
printed on stderr at runtime — there is no fixed bypass value.
|
|
238
|
+
|
|
239
|
+
- verb: create
|
|
240
|
+
summary: |
|
|
241
|
+
Creates a new <NNNN>_<name>_up.sql + <NNNN>_<name>_down.sql pair scaffolded
|
|
242
|
+
with TODO placeholders. NNNN is one greater than the highest 1-4-digit
|
|
243
|
+
sequence prefix already present in the migrations directory (starts at 0001).
|
|
244
|
+
Legacy timestamp-prefixed filenames are ignored for numbering.
|
|
245
|
+
examples: |
|
|
246
|
+
{{command}} --provider sqlite --name add_users
|
|
247
|
+
{{command}} --provider postgres --name backfill_email_lower --migrations-path ./db/changes/postgres
|
|
248
|
+
args:
|
|
249
|
+
- flag: --provider
|
|
250
|
+
placeholder: providers
|
|
251
|
+
required: true
|
|
252
|
+
help: "Database dialect. Required."
|
|
253
|
+
- flag: --name
|
|
254
|
+
placeholder: snake_case_slug
|
|
255
|
+
required: true
|
|
256
|
+
help: |
|
|
257
|
+
snake_case slug for the new migration. Required. Must
|
|
258
|
+
match /^[a-z][a-z0-9_]*$/.
|
|
259
|
+
- flag: --migrations-path
|
|
260
|
+
placeholder: dir
|
|
261
|
+
required: false
|
|
262
|
+
help: |
|
|
263
|
+
Migrations directory (default: ./sql/<dialect>/migrations).
|
|
264
|
+
Created with mkdir -p if it does not exist.
|
|
File without changes
|
package/dist/bin/migrate-down.js
CHANGED
|
File without changes
|
package/dist/bin/migrate-init.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/bin/migrate-up.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deterministic-code/migraters",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "TypeScript migrate executables for deterministic-code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -19,14 +19,11 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
|
-
"src",
|
|
23
22
|
"cli.yaml",
|
|
24
|
-
"bundle.yaml"
|
|
25
|
-
"tsconfig.build.json"
|
|
23
|
+
"bundle.yaml"
|
|
26
24
|
],
|
|
27
25
|
"scripts": {
|
|
28
26
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp -R ../shared/templates dist/templates",
|
|
29
|
-
"prepare": "npm run build",
|
|
30
27
|
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
31
28
|
"test": "vitest run --coverage",
|
|
32
29
|
"test:proof": "vitest run --config vitest.proof.config.ts",
|
|
@@ -76,5 +73,9 @@
|
|
|
76
73
|
"prettier": "^3.9.6",
|
|
77
74
|
"typescript": "^5.8.0",
|
|
78
75
|
"vitest": "^3.2.4"
|
|
79
|
-
}
|
|
76
|
+
},
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public"
|
|
79
|
+
},
|
|
80
|
+
"license": "MIT"
|
|
80
81
|
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export type SetupArg = { connection: string; migrationsPath: string };
|
|
2
|
-
export type UpArg = { connection: string; migrationsPath: string; one?: boolean };
|
|
3
|
-
export type DownArg = { connection: string; migrationsPath: string };
|
|
4
|
-
export type CreateArg = { name: string; migrationsPath: string };
|
|
5
|
-
export type CreateResponse = { upName: string; downName: string };
|
|
6
|
-
|
|
7
|
-
export interface IMigrater {
|
|
8
|
-
readonly name: string;
|
|
9
|
-
setup(args: SetupArg): Promise<void>;
|
|
10
|
-
up(args: UpArg): Promise<string[]>;
|
|
11
|
-
down(args: DownArg): Promise<string | null>;
|
|
12
|
-
create(args: CreateArg): Promise<CreateResponse>;
|
|
13
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { SqlDialect } from '../sql.ts';
|
|
2
|
-
import type { ISqlDialect } from './sql-dialect.ts';
|
|
3
|
-
|
|
4
|
-
export abstract class SqlDialectBase implements ISqlDialect {
|
|
5
|
-
abstract readonly name: SqlDialect;
|
|
6
|
-
abstract readonly connectionEnvironmentVariables: readonly string[];
|
|
7
|
-
abstract readonly migratesDdl: string;
|
|
8
|
-
abstract readonly migrateLogsDdl: string;
|
|
9
|
-
readonly usesLastInsertRowid: boolean = false;
|
|
10
|
-
|
|
11
|
-
quoteIdent(ident: string): string {
|
|
12
|
-
return `"${ident}"`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
nowExpr(): string {
|
|
16
|
-
return 'CURRENT_TIMESTAMP';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
limitClause(n: number): string {
|
|
20
|
-
return `LIMIT ${n}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
translatePlaceholders(sql: string): string {
|
|
24
|
-
return sql;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async prerequisiteError(_connection: string): Promise<string | null> {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async prepareSetup(_connection: string): Promise<void> {}
|
|
32
|
-
|
|
33
|
-
abstract createClient(
|
|
34
|
-
connection: string,
|
|
35
|
-
): Promise<import('../migration-client.ts').MigrationClient>;
|
|
36
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { normalizeDialect, type SqlDialect } from '../sql.ts';
|
|
2
|
-
import type { ISqlDialect } from './sql-dialect.ts';
|
|
3
|
-
|
|
4
|
-
const VALID = 'sqlite, mysql, postgres, sqlserver, oracle';
|
|
5
|
-
|
|
6
|
-
export class SqlDialectFactory {
|
|
7
|
-
private readonly byName: Map<SqlDialect, ISqlDialect>;
|
|
8
|
-
|
|
9
|
-
constructor(dialects: readonly ISqlDialect[]) {
|
|
10
|
-
this.byName = new Map(dialects.map((d) => [d.name, d]));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
tryGet(raw: string): ISqlDialect | undefined {
|
|
14
|
-
const key = normalizeDialect(raw);
|
|
15
|
-
return key ? this.byName.get(key) : undefined;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
get(raw: string): ISqlDialect {
|
|
19
|
-
const dialect = this.tryGet(raw);
|
|
20
|
-
if (!dialect) {
|
|
21
|
-
throw new Error(`Unknown SQL dialect "${raw}". Valid: ${VALID}.`);
|
|
22
|
-
}
|
|
23
|
-
return dialect;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
all(): readonly ISqlDialect[] {
|
|
27
|
-
return [...this.byName.values()];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { MigrationClient } from '../migration-client.ts';
|
|
2
|
-
import type { SqlDialect } from '../sql.ts';
|
|
3
|
-
|
|
4
|
-
export interface ISqlDialect {
|
|
5
|
-
readonly name: SqlDialect;
|
|
6
|
-
readonly connectionEnvironmentVariables: readonly string[];
|
|
7
|
-
readonly migratesDdl: string;
|
|
8
|
-
readonly migrateLogsDdl: string;
|
|
9
|
-
readonly usesLastInsertRowid: boolean;
|
|
10
|
-
quoteIdent(ident: string): string;
|
|
11
|
-
nowExpr(): string;
|
|
12
|
-
limitClause(n: number): string;
|
|
13
|
-
translatePlaceholders(sql: string): string;
|
|
14
|
-
prerequisiteError(connection: string): Promise<string | null>;
|
|
15
|
-
prepareSetup(connection: string): Promise<void>;
|
|
16
|
-
createClient(connection: string): Promise<MigrationClient>;
|
|
17
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Scaffolds a new <NNNN>_<name>_{up,down}.sql pair in the migrations directory. Numbering is max(1-4-digit prefix in dir)+1; legacy timestamp prefixes are ignored.
|
|
3
|
-
|
|
4
|
-
import { missingProviderMsg, requireDialect, takeFlag } from '../cli.ts';
|
|
5
|
-
import { isMigrationName } from '../create-sequence.ts';
|
|
6
|
-
import { fillTemplate, loadMessage } from '../infrastructure/message-templates.ts';
|
|
7
|
-
import { loadHelp, Migrater } from '../index.ts';
|
|
8
|
-
|
|
9
|
-
const [HELP_TEXT, unknownArgTpl, missingNameMsg, invalidNameTpl, createdTpl] = await Promise.all([
|
|
10
|
-
loadHelp('migrate-create'),
|
|
11
|
-
loadMessage('errors/unknown-arg'),
|
|
12
|
-
loadMessage('errors/missing-name'),
|
|
13
|
-
loadMessage('errors/invalid-name'),
|
|
14
|
-
loadMessage('status/created'),
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
const parseArgs = (argv: string[]) => {
|
|
18
|
-
const args: {
|
|
19
|
-
provider: string | null;
|
|
20
|
-
name: string | null;
|
|
21
|
-
migrationsPath: string | null;
|
|
22
|
-
} = { provider: null, name: null, migrationsPath: null };
|
|
23
|
-
const rest = argv.slice(2);
|
|
24
|
-
for (let i = 0; i < rest.length; i++) {
|
|
25
|
-
const a = rest[i] as string;
|
|
26
|
-
if (a === '-h' || a === '--help') {
|
|
27
|
-
process.stderr.write(HELP_TEXT);
|
|
28
|
-
process.exit(0);
|
|
29
|
-
}
|
|
30
|
-
let taken;
|
|
31
|
-
if ((taken = takeFlag(rest, i, a, '--provider'))) {
|
|
32
|
-
args.provider = taken.value;
|
|
33
|
-
i = taken.next;
|
|
34
|
-
} else if ((taken = takeFlag(rest, i, a, '--name'))) {
|
|
35
|
-
args.name = taken.value;
|
|
36
|
-
i = taken.next;
|
|
37
|
-
} else if (
|
|
38
|
-
(taken = takeFlag(rest, i, a, '--migrations-path')) ||
|
|
39
|
-
(taken = takeFlag(rest, i, a, '--migrate-path'))
|
|
40
|
-
) {
|
|
41
|
-
args.migrationsPath = taken.value;
|
|
42
|
-
i = taken.next;
|
|
43
|
-
} else {
|
|
44
|
-
process.stderr.write(fillTemplate(unknownArgTpl, { arg: a }));
|
|
45
|
-
process.exit(2);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return args;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const main = async () => {
|
|
52
|
-
const args = parseArgs(process.argv);
|
|
53
|
-
if (!args.provider) {
|
|
54
|
-
process.stderr.write(missingProviderMsg);
|
|
55
|
-
process.exit(2);
|
|
56
|
-
}
|
|
57
|
-
const dialect = requireDialect(args.provider);
|
|
58
|
-
if (!args.name) {
|
|
59
|
-
process.stderr.write(missingNameMsg);
|
|
60
|
-
process.exit(2);
|
|
61
|
-
}
|
|
62
|
-
if (!isMigrationName(args.name)) {
|
|
63
|
-
process.stderr.write(fillTemplate(invalidNameTpl, { name: args.name }));
|
|
64
|
-
process.exit(2);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const migrationsPath = args.migrationsPath ?? `./sql/${dialect.name}/migrations`;
|
|
68
|
-
const { upName, downName } = await new Migrater(dialect).create({
|
|
69
|
-
name: args.name,
|
|
70
|
-
migrationsPath,
|
|
71
|
-
});
|
|
72
|
-
process.stdout.write(fillTemplate(createdTpl, { file: upName }));
|
|
73
|
-
process.stdout.write(fillTemplate(createdTpl, { file: downName }));
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
main().catch((e: unknown) => {
|
|
77
|
-
const err = e as Error;
|
|
78
|
-
process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
|
|
79
|
-
process.exit(1);
|
|
80
|
-
});
|
package/src/bin/migrate-down.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Rolls back the most recently applied migration (one step). DESTRUCTIVE: prints a random 4-letter uppercase confirmation token and refuses to proceed unless the user types it back on stdin. Pass --confirm <TOKEN> matching the printed token to bypass the interactive prompt (CI / scripted use).
|
|
3
|
-
|
|
4
|
-
import { resolve } from 'node:path';
|
|
5
|
-
import { createInterface } from 'node:readline/promises';
|
|
6
|
-
import { missingProviderMsg, requireConnection, requireDialect, takeFlag } from '../cli.ts';
|
|
7
|
-
import { fillTemplate, loadMessage } from '../infrastructure/message-templates.ts';
|
|
8
|
-
import { loadHelp, Migrater } from '../index.ts';
|
|
9
|
-
|
|
10
|
-
const [
|
|
11
|
-
HELP_TEXT,
|
|
12
|
-
unknownArgTpl,
|
|
13
|
-
destructiveHeaderTpl,
|
|
14
|
-
confirmMismatchTpl,
|
|
15
|
-
confirmTtyRequiredTpl,
|
|
16
|
-
confirmPromptTpl,
|
|
17
|
-
confirmTokenMismatchMsg,
|
|
18
|
-
noRollbackMsg,
|
|
19
|
-
rolledBackTpl,
|
|
20
|
-
] = await Promise.all([
|
|
21
|
-
loadHelp('migrate-down'),
|
|
22
|
-
loadMessage('errors/unknown-arg'),
|
|
23
|
-
loadMessage('down/destructive-header'),
|
|
24
|
-
loadMessage('down/confirm-mismatch'),
|
|
25
|
-
loadMessage('down/confirm-tty-required'),
|
|
26
|
-
loadMessage('down/confirm-prompt'),
|
|
27
|
-
loadMessage('down/confirm-token-mismatch'),
|
|
28
|
-
loadMessage('status/no-rollback'),
|
|
29
|
-
loadMessage('status/rolled-back'),
|
|
30
|
-
]);
|
|
31
|
-
|
|
32
|
-
const parseArgs = (argv: string[]) => {
|
|
33
|
-
const args: {
|
|
34
|
-
provider: string | null;
|
|
35
|
-
migratePath: string | null;
|
|
36
|
-
migrateRoot: string | null;
|
|
37
|
-
connection: string | null;
|
|
38
|
-
confirm: string | null;
|
|
39
|
-
} = {
|
|
40
|
-
provider: null,
|
|
41
|
-
migratePath: null,
|
|
42
|
-
migrateRoot: null,
|
|
43
|
-
connection: null,
|
|
44
|
-
confirm: null,
|
|
45
|
-
};
|
|
46
|
-
const rest = argv.slice(2);
|
|
47
|
-
for (let i = 0; i < rest.length; i++) {
|
|
48
|
-
const a = rest[i] as string;
|
|
49
|
-
if (a === '-h' || a === '--help') {
|
|
50
|
-
process.stderr.write(HELP_TEXT);
|
|
51
|
-
process.exit(0);
|
|
52
|
-
}
|
|
53
|
-
let taken;
|
|
54
|
-
if ((taken = takeFlag(rest, i, a, '--provider'))) {
|
|
55
|
-
args.provider = taken.value;
|
|
56
|
-
i = taken.next;
|
|
57
|
-
} else if (
|
|
58
|
-
(taken = takeFlag(rest, i, a, '--migrations-path')) ||
|
|
59
|
-
(taken = takeFlag(rest, i, a, '--migrate-path'))
|
|
60
|
-
) {
|
|
61
|
-
args.migratePath = taken.value;
|
|
62
|
-
i = taken.next;
|
|
63
|
-
} else if (
|
|
64
|
-
(taken = takeFlag(rest, i, a, '--migrations-root')) ||
|
|
65
|
-
(taken = takeFlag(rest, i, a, '--migrate-root'))
|
|
66
|
-
) {
|
|
67
|
-
args.migrateRoot = taken.value;
|
|
68
|
-
i = taken.next;
|
|
69
|
-
} else if ((taken = takeFlag(rest, i, a, '--connection'))) {
|
|
70
|
-
args.connection = taken.value;
|
|
71
|
-
i = taken.next;
|
|
72
|
-
} else if ((taken = takeFlag(rest, i, a, '--confirm'))) {
|
|
73
|
-
args.confirm = taken.value;
|
|
74
|
-
i = taken.next;
|
|
75
|
-
} else {
|
|
76
|
-
process.stderr.write(fillTemplate(unknownArgTpl, { arg: a }));
|
|
77
|
-
process.exit(2);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return args;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const resolveMigratePath = (
|
|
84
|
-
args: { migratePath: string | null; migrateRoot: string | null },
|
|
85
|
-
dialect: string,
|
|
86
|
-
): string => {
|
|
87
|
-
if (args.migratePath) return args.migratePath;
|
|
88
|
-
const root = args.migrateRoot ?? 'sql';
|
|
89
|
-
return `${root}/${dialect}/migrations`;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const randomToken = (): string => {
|
|
93
|
-
const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
|
|
94
|
-
const buf = new Uint8Array(4);
|
|
95
|
-
globalThis.crypto.getRandomValues(buf);
|
|
96
|
-
let out = '';
|
|
97
|
-
for (let i = 0; i < 4; i++) out += alphabet[(buf[i] as number) % alphabet.length];
|
|
98
|
-
return out;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const promptConfirmation = async (token: string): Promise<boolean> => {
|
|
102
|
-
if (!process.stdin.isTTY) {
|
|
103
|
-
process.stderr.write(fillTemplate(confirmTtyRequiredTpl, { token }));
|
|
104
|
-
process.exit(2);
|
|
105
|
-
}
|
|
106
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
107
|
-
try {
|
|
108
|
-
const answer = await rl.question(fillTemplate(confirmPromptTpl, { token }));
|
|
109
|
-
return answer.trim() === token;
|
|
110
|
-
} finally {
|
|
111
|
-
rl.close();
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const main = async () => {
|
|
116
|
-
const args = parseArgs(process.argv);
|
|
117
|
-
if (!args.provider) {
|
|
118
|
-
process.stderr.write(missingProviderMsg);
|
|
119
|
-
process.exit(2);
|
|
120
|
-
}
|
|
121
|
-
const dialect = requireDialect(args.provider);
|
|
122
|
-
const connection = requireConnection(dialect, args.connection);
|
|
123
|
-
|
|
124
|
-
const missing = await dialect.prerequisiteError(connection);
|
|
125
|
-
if (missing !== null) {
|
|
126
|
-
process.stderr.write(`${missing}\n`);
|
|
127
|
-
process.exit(2);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const token = randomToken();
|
|
131
|
-
process.stderr.write(
|
|
132
|
-
fillTemplate(destructiveHeaderTpl, { dialect: dialect.name, token }),
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
if (args.confirm !== null) {
|
|
136
|
-
if (args.confirm !== token) {
|
|
137
|
-
process.stderr.write(
|
|
138
|
-
fillTemplate(confirmMismatchTpl, { supplied: args.confirm, token }),
|
|
139
|
-
);
|
|
140
|
-
process.exit(2);
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
const ok = await promptConfirmation(token);
|
|
144
|
-
if (!ok) {
|
|
145
|
-
process.stderr.write(confirmTokenMismatchMsg);
|
|
146
|
-
process.exit(2);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const migratePath = resolve(resolveMigratePath(args, dialect.name));
|
|
151
|
-
const name = await new Migrater(dialect).down({
|
|
152
|
-
connection,
|
|
153
|
-
migrationsPath: migratePath,
|
|
154
|
-
});
|
|
155
|
-
if (name == null) process.stdout.write(noRollbackMsg);
|
|
156
|
-
else process.stdout.write(fillTemplate(rolledBackTpl, { name }));
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
main().catch((e: unknown) => {
|
|
160
|
-
const err = e as Error;
|
|
161
|
-
process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
|
|
162
|
-
process.exit(1);
|
|
163
|
-
});
|