@deterministic-code/migraters 0.0.0-stage → 0.0.1
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/bundle.yaml +262 -0
- package/dist/abstractions/migrater.d.ts +28 -0
- package/dist/abstractions/migrater.js +1 -0
- package/dist/abstractions/sql-dialect-base.d.ts +16 -0
- package/dist/abstractions/sql-dialect-base.js +19 -0
- package/dist/abstractions/sql-dialect-factory.d.ts +8 -0
- package/dist/abstractions/sql-dialect-factory.js +22 -0
- package/dist/abstractions/sql-dialect.d.ts +16 -0
- package/dist/abstractions/sql-dialect.js +1 -0
- package/dist/bin/migrate-create.d.ts +2 -0
- package/dist/bin/migrate-create.js +71 -0
- package/dist/bin/migrate-down.d.ts +2 -0
- package/dist/bin/migrate-down.js +135 -0
- package/dist/bin/migrate-init.d.ts +2 -0
- package/dist/bin/migrate-init.js +77 -0
- package/dist/bin/migrate-setup.d.ts +2 -0
- package/dist/bin/migrate-setup.js +87 -0
- package/dist/bin/migrate-up.d.ts +2 -0
- package/dist/bin/migrate-up.js +97 -0
- package/dist/cli-contract.d.ts +6 -0
- package/dist/cli-contract.js +19 -0
- package/dist/cli-spec.d.ts +24 -0
- package/dist/cli-spec.js +33 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +41 -0
- package/dist/create-sequence.d.ts +4 -0
- package/dist/create-sequence.js +16 -0
- package/dist/datasource-migrate.d.ts +31 -0
- package/dist/datasource-migrate.js +206 -0
- package/dist/dialects/mysql-dialect.d.ts +12 -0
- package/dist/dialects/mysql-dialect.js +56 -0
- package/dist/dialects/oracle-dialect.d.ts +18 -0
- package/dist/dialects/oracle-dialect.js +76 -0
- package/dist/dialects/postgres-dialect.d.ts +11 -0
- package/dist/dialects/postgres-dialect.js +58 -0
- package/dist/dialects/sqlite-dialect.d.ts +13 -0
- package/dist/dialects/sqlite-dialect.js +96 -0
- package/dist/dialects/sqlserver-dialect.d.ts +12 -0
- package/dist/dialects/sqlserver-dialect.js +73 -0
- package/dist/help.d.ts +2 -0
- package/dist/help.js +10 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/infrastructure/connection-resolver.d.ts +7 -0
- package/dist/infrastructure/connection-resolver.js +19 -0
- package/dist/infrastructure/default-factory.d.ts +4 -0
- package/dist/infrastructure/default-factory.js +15 -0
- package/dist/infrastructure/interop-default.d.ts +3 -0
- package/dist/infrastructure/interop-default.js +1 -0
- package/dist/infrastructure/message-templates.d.ts +3 -0
- package/dist/infrastructure/message-templates.js +18 -0
- package/dist/infrastructure/split-on-go.d.ts +2 -0
- package/dist/infrastructure/split-on-go.js +20 -0
- package/dist/infrastructure/sql-templates.d.ts +2 -0
- package/dist/infrastructure/sql-templates.js +4 -0
- package/dist/infrastructure/templates-root.d.ts +2 -0
- package/dist/infrastructure/templates-root.js +11 -0
- package/dist/init-select.d.ts +12 -0
- package/dist/init-select.js +52 -0
- package/dist/migrater.d.ts +12 -0
- package/dist/migrater.js +84 -0
- package/dist/migration-client.d.ts +21 -0
- package/dist/migration-client.js +1 -0
- package/dist/path-exists.d.ts +1 -0
- package/dist/path-exists.js +10 -0
- package/dist/sql.d.ts +3 -0
- package/dist/sql.js +15 -0
- package/dist/templates/help/create.txt +16 -0
- package/dist/templates/help/down.txt +42 -0
- package/dist/templates/help/init.txt +13 -0
- package/dist/templates/help/setup.txt +41 -0
- package/dist/templates/help/up.txt +43 -0
- package/dist/templates/messages/checksum-drift-line.txt +1 -0
- package/dist/templates/messages/checksum-drift.txt +11 -0
- package/dist/templates/messages/down/confirm-mismatch.txt +1 -0
- package/dist/templates/messages/down/confirm-prompt.txt +1 -0
- package/dist/templates/messages/down/confirm-stdin-failed.txt +1 -0
- package/dist/templates/messages/down/confirm-token-mismatch.txt +1 -0
- package/dist/templates/messages/down/confirm-tty-required.txt +1 -0
- package/dist/templates/messages/down/destructive-header.txt +3 -0
- package/dist/templates/messages/errors/invalid-name.txt +1 -0
- package/dist/templates/messages/errors/migrate-path-not-dir.txt +1 -0
- package/dist/templates/messages/errors/missing-connection.txt +1 -0
- package/dist/templates/messages/errors/missing-name-hint.txt +1 -0
- package/dist/templates/messages/errors/missing-name.txt +1 -0
- package/dist/templates/messages/errors/missing-provider-hint.txt +1 -0
- package/dist/templates/messages/errors/missing-provider.txt +1 -0
- package/dist/templates/messages/errors/missing-value-for-flag.txt +1 -0
- package/dist/templates/messages/errors/rollback-no-down-sibling.txt +1 -0
- package/dist/templates/messages/errors/rollback-no-rollback-sibling.txt +1 -0
- package/dist/templates/messages/errors/rollback-not-in-path.txt +1 -0
- package/dist/templates/messages/errors/sqlite-prerequisite.txt +1 -0
- package/dist/templates/messages/errors/unknown-arg.txt +1 -0
- package/dist/templates/messages/errors/unsupported-provider.txt +1 -0
- package/dist/templates/messages/setup-complete.txt +9 -0
- package/dist/templates/messages/status/applied.txt +1 -0
- package/dist/templates/messages/status/created.txt +1 -0
- package/dist/templates/messages/status/no-more-pending.txt +1 -0
- package/dist/templates/messages/status/no-pending.txt +1 -0
- package/dist/templates/messages/status/no-rollback.txt +1 -0
- package/dist/templates/messages/status/rolled-back.txt +1 -0
- package/dist/templates/scaffold/down.sql +1 -0
- package/dist/templates/scaffold/up.sql +1 -0
- package/dist/templates/sql/mysql/migrate_logs.sql +11 -0
- package/dist/templates/sql/mysql/migrates.sql +7 -0
- package/dist/templates/sql/oracle/migrate_logs.sql +14 -0
- package/dist/templates/sql/oracle/migrates.sql +10 -0
- package/dist/templates/sql/postgres/migrate_logs.sql +11 -0
- package/dist/templates/sql/postgres/migrates.sql +7 -0
- package/dist/templates/sql/sqlite/migrate_logs.sql +11 -0
- package/dist/templates/sql/sqlite/migrates.sql +7 -0
- package/dist/templates/sql/sqlserver/migrate_logs.sql +12 -0
- package/dist/templates/sql/sqlserver/migrates.sql +8 -0
- package/package.json +78 -4
- package/src/abstractions/migrater.ts +13 -0
- package/src/abstractions/sql-dialect-base.ts +36 -0
- package/src/abstractions/sql-dialect-factory.ts +29 -0
- package/src/abstractions/sql-dialect.ts +17 -0
- package/src/bin/migrate-create.ts +80 -0
- package/src/bin/migrate-down.ts +163 -0
- package/src/bin/migrate-init.ts +88 -0
- package/src/bin/migrate-setup.ts +98 -0
- package/src/bin/migrate-up.ts +111 -0
- package/src/cli-contract.ts +25 -0
- package/src/cli-spec.ts +64 -0
- package/src/cli.ts +52 -0
- package/src/create-sequence.ts +17 -0
- package/src/datasource-migrate.ts +314 -0
- package/src/dialects/mysql-dialect.test.ts +93 -0
- package/src/dialects/mysql-dialect.ts +67 -0
- package/src/dialects/oracle-dialect.test.ts +118 -0
- package/src/dialects/oracle-dialect.ts +91 -0
- package/src/dialects/postgres-dialect.test.ts +80 -0
- package/src/dialects/postgres-dialect.ts +65 -0
- package/src/dialects/sqlite-dialect.rollback.test.ts +30 -0
- package/src/dialects/sqlite-dialect.test.ts +77 -0
- package/src/dialects/sqlite-dialect.ts +96 -0
- package/src/dialects/sqlserver-dialect.test.ts +99 -0
- package/src/dialects/sqlserver-dialect.ts +87 -0
- package/src/generate-help.test.ts +33 -0
- package/src/generate-help.ts +173 -0
- package/src/help.test.ts +48 -0
- package/src/help.ts +12 -0
- package/src/index.ts +23 -0
- package/src/infrastructure/connection-resolver.ts +18 -0
- package/src/infrastructure/default-factory.test.ts +18 -0
- package/src/infrastructure/default-factory.ts +17 -0
- package/src/infrastructure/interop-default.test.ts +9 -0
- package/src/infrastructure/interop-default.ts +2 -0
- package/src/infrastructure/message-templates.ts +30 -0
- package/src/infrastructure/split-on-go.ts +19 -0
- package/src/infrastructure/sql-templates.ts +10 -0
- package/src/infrastructure/templates-root.ts +13 -0
- package/src/init-select.test.ts +26 -0
- package/src/init-select.ts +73 -0
- package/src/migrater.ts +92 -0
- package/src/migration-client.ts +21 -0
- package/src/path-exists.ts +10 -0
- package/src/setup-sql.test.ts +16 -0
- package/src/sql.ts +17 -0
- package/tsconfig.build.json +19 -0
- package/README.md +0 -3
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { loadHelp } from '../help.ts';
|
|
4
|
+
import { loadSelectedPackages, resolveInitDialects } from '../init-select.ts';
|
|
5
|
+
|
|
6
|
+
const HELP_TEXT = await loadHelp('migrate-init');
|
|
7
|
+
|
|
8
|
+
const takeFlag = (
|
|
9
|
+
rest: string[],
|
|
10
|
+
i: number,
|
|
11
|
+
arg: string,
|
|
12
|
+
flag: string,
|
|
13
|
+
): { value: string; next: number } | undefined => {
|
|
14
|
+
const eq = `${flag}=`;
|
|
15
|
+
if (arg === flag) {
|
|
16
|
+
const value = rest[i + 1];
|
|
17
|
+
if (value === undefined) return undefined;
|
|
18
|
+
return { value, next: i + 1 };
|
|
19
|
+
}
|
|
20
|
+
if (arg.startsWith(eq)) return { value: arg.slice(eq.length), next: i };
|
|
21
|
+
return undefined;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const parseArgs = (argv: string[]) => {
|
|
25
|
+
const dialects: string[] = [];
|
|
26
|
+
let all = false;
|
|
27
|
+
const rest = argv.slice(2);
|
|
28
|
+
for (let i = 0; i < rest.length; i++) {
|
|
29
|
+
const a = rest[i] as string;
|
|
30
|
+
if (a === '-h' || a === '--help') {
|
|
31
|
+
process.stderr.write(HELP_TEXT);
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
if (a === '--all') {
|
|
35
|
+
all = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const taken = takeFlag(rest, i, a, '--dialect');
|
|
39
|
+
if (taken) {
|
|
40
|
+
dialects.push(taken.value);
|
|
41
|
+
i = taken.next;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
process.stderr.write(`unknown arg: ${a}\n`);
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
return { dialects, all };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const npmFlags = ['--no-audit', '--no-fund', '--loglevel=error'];
|
|
51
|
+
|
|
52
|
+
const npm = (args: string[]): void => {
|
|
53
|
+
const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
54
|
+
const result = spawnSync(cmd, args, { stdio: 'inherit', cwd: process.cwd() });
|
|
55
|
+
if (result.status !== 0) {
|
|
56
|
+
process.exit(result.status ?? 1);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const main = async () => {
|
|
61
|
+
const args = parseArgs(process.argv);
|
|
62
|
+
let names: string[];
|
|
63
|
+
try {
|
|
64
|
+
names = resolveInitDialects(args.dialects, args.all);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
const err = e as Error;
|
|
67
|
+
process.stderr.write(`${err.message}\n`);
|
|
68
|
+
process.exit(2);
|
|
69
|
+
}
|
|
70
|
+
const selected = await loadSelectedPackages(names);
|
|
71
|
+
const specs = selected.packages.map((p) => `${p.name}@${p.version}`);
|
|
72
|
+
if (specs.length > 0) {
|
|
73
|
+
npm(['install', ...npmFlags, ...specs]);
|
|
74
|
+
}
|
|
75
|
+
const remove = selected.allDriverPackages.filter(
|
|
76
|
+
(name) => !selected.packages.some((p) => p.name === name),
|
|
77
|
+
);
|
|
78
|
+
if (remove.length > 0) {
|
|
79
|
+
npm(['uninstall', ...npmFlags, ...remove]);
|
|
80
|
+
}
|
|
81
|
+
process.stdout.write(`Enabled dialects: ${selected.names.join(', ')}\n`);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
main().catch((e: unknown) => {
|
|
85
|
+
const err = e as Error;
|
|
86
|
+
process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Idempotent migrate-setup — creates the migrates + migrate_logs tracking tables per dialect.
|
|
3
|
+
|
|
4
|
+
import { missingProviderMsg, requireConnection, requireDialect, takeFlag } from '../cli.ts';
|
|
5
|
+
import { fillTemplate, loadMessage } from '../infrastructure/message-templates.ts';
|
|
6
|
+
import { loadHelp, Migrater } from '../index.ts';
|
|
7
|
+
|
|
8
|
+
const [HELP_TEXT, unknownArgTpl, setupCompleteTpl] = await Promise.all([
|
|
9
|
+
loadHelp('migrate-setup'),
|
|
10
|
+
loadMessage('errors/unknown-arg'),
|
|
11
|
+
loadMessage('setup-complete'),
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const parseArgs = (argv: string[]) => {
|
|
15
|
+
const args: {
|
|
16
|
+
provider: string | null;
|
|
17
|
+
connection: string | null;
|
|
18
|
+
migrationsPath: string | null;
|
|
19
|
+
andUp: boolean;
|
|
20
|
+
} = {
|
|
21
|
+
provider: null,
|
|
22
|
+
connection: null,
|
|
23
|
+
migrationsPath: null,
|
|
24
|
+
andUp: false,
|
|
25
|
+
};
|
|
26
|
+
const rest = argv.slice(2);
|
|
27
|
+
for (let i = 0; i < rest.length; i++) {
|
|
28
|
+
const a = rest[i] as string;
|
|
29
|
+
if (a === '-h' || a === '--help') {
|
|
30
|
+
process.stderr.write(HELP_TEXT);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
if (a === '--and-up') {
|
|
34
|
+
args.andUp = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
let taken;
|
|
38
|
+
if ((taken = takeFlag(rest, i, a, '--provider'))) {
|
|
39
|
+
args.provider = taken.value;
|
|
40
|
+
i = taken.next;
|
|
41
|
+
} else if ((taken = takeFlag(rest, i, a, '--connection'))) {
|
|
42
|
+
args.connection = taken.value;
|
|
43
|
+
i = taken.next;
|
|
44
|
+
} else if (
|
|
45
|
+
(taken = takeFlag(rest, i, a, '--migrations-path')) ||
|
|
46
|
+
(taken = takeFlag(rest, i, a, '--migrate-path'))
|
|
47
|
+
) {
|
|
48
|
+
args.migrationsPath = taken.value;
|
|
49
|
+
i = taken.next;
|
|
50
|
+
} else {
|
|
51
|
+
process.stderr.write(fillTemplate(unknownArgTpl, { arg: a }));
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return args;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const main = async () => {
|
|
59
|
+
const args = parseArgs(process.argv);
|
|
60
|
+
if (!args.provider) {
|
|
61
|
+
process.stderr.write(missingProviderMsg);
|
|
62
|
+
process.exit(2);
|
|
63
|
+
}
|
|
64
|
+
const dialect = requireDialect(args.provider);
|
|
65
|
+
const connection = requireConnection(dialect, args.connection);
|
|
66
|
+
|
|
67
|
+
const migrationsPath = args.migrationsPath ?? `./sql/${dialect.name}/migrations`;
|
|
68
|
+
const migrater = new Migrater(dialect);
|
|
69
|
+
await migrater.setup({ connection, migrationsPath });
|
|
70
|
+
process.stdout.write(
|
|
71
|
+
fillTemplate(setupCompleteTpl, {
|
|
72
|
+
dialect: dialect.name,
|
|
73
|
+
migrationsPath,
|
|
74
|
+
createExample: ` migrate-create --provider ${dialect.name} --name add_users`,
|
|
75
|
+
upExample: ` migrate-up --provider ${dialect.name} --connection ${connection}`,
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (args.andUp) {
|
|
80
|
+
const names = await migrater.up({ connection, migrationsPath });
|
|
81
|
+
const [noPendingMsg, noMorePendingMsg, appliedTpl] = await Promise.all([
|
|
82
|
+
loadMessage('status/no-pending'),
|
|
83
|
+
loadMessage('status/no-more-pending'),
|
|
84
|
+
loadMessage('status/applied'),
|
|
85
|
+
]);
|
|
86
|
+
if (names.length === 0) process.stdout.write(noPendingMsg);
|
|
87
|
+
else {
|
|
88
|
+
for (const name of names) process.stdout.write(fillTemplate(appliedTpl, { name }));
|
|
89
|
+
process.stdout.write(noMorePendingMsg);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
main().catch((e: unknown) => {
|
|
95
|
+
const err = e as Error;
|
|
96
|
+
process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Default: applies ALL pending migrations in sequence, each in its own transaction. Pass --one to apply only the next pending migration (legacy one-shot behavior; useful when wrapping in an external loop or when you want to inspect state between steps).
|
|
3
|
+
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { missingProviderMsg, requireConnection, requireDialect, takeFlag } from '../cli.ts';
|
|
6
|
+
import { fillTemplate, loadMessage } from '../infrastructure/message-templates.ts';
|
|
7
|
+
import { loadHelp, Migrater } from '../index.ts';
|
|
8
|
+
|
|
9
|
+
const [HELP_TEXT, unknownArgTpl, noPendingMsg, noMorePendingMsg, appliedTpl] = await Promise.all([
|
|
10
|
+
loadHelp('migrate-up'),
|
|
11
|
+
loadMessage('errors/unknown-arg'),
|
|
12
|
+
loadMessage('status/no-pending'),
|
|
13
|
+
loadMessage('status/no-more-pending'),
|
|
14
|
+
loadMessage('status/applied'),
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const parseArgs = (argv: string[]) => {
|
|
18
|
+
const args: {
|
|
19
|
+
provider: string | null;
|
|
20
|
+
migratePath: string | null;
|
|
21
|
+
migrateRoot: string | null;
|
|
22
|
+
connection: string | null;
|
|
23
|
+
one: boolean;
|
|
24
|
+
} = {
|
|
25
|
+
provider: null,
|
|
26
|
+
migratePath: null,
|
|
27
|
+
migrateRoot: null,
|
|
28
|
+
connection: null,
|
|
29
|
+
one: false,
|
|
30
|
+
};
|
|
31
|
+
const rest = argv.slice(2);
|
|
32
|
+
for (let i = 0; i < rest.length; i++) {
|
|
33
|
+
const a = rest[i] as string;
|
|
34
|
+
if (a === '-h' || a === '--help') {
|
|
35
|
+
process.stderr.write(HELP_TEXT);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
if (a === '--one') {
|
|
39
|
+
args.one = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
let taken;
|
|
43
|
+
if ((taken = takeFlag(rest, i, a, '--provider'))) {
|
|
44
|
+
args.provider = taken.value;
|
|
45
|
+
i = taken.next;
|
|
46
|
+
} else if (
|
|
47
|
+
(taken = takeFlag(rest, i, a, '--migrations-path')) ||
|
|
48
|
+
(taken = takeFlag(rest, i, a, '--migrate-path'))
|
|
49
|
+
) {
|
|
50
|
+
args.migratePath = taken.value;
|
|
51
|
+
i = taken.next;
|
|
52
|
+
} else if (
|
|
53
|
+
(taken = takeFlag(rest, i, a, '--migrations-root')) ||
|
|
54
|
+
(taken = takeFlag(rest, i, a, '--migrate-root'))
|
|
55
|
+
) {
|
|
56
|
+
args.migrateRoot = taken.value;
|
|
57
|
+
i = taken.next;
|
|
58
|
+
} else if ((taken = takeFlag(rest, i, a, '--connection'))) {
|
|
59
|
+
args.connection = taken.value;
|
|
60
|
+
i = taken.next;
|
|
61
|
+
} else {
|
|
62
|
+
process.stderr.write(fillTemplate(unknownArgTpl, { arg: a }));
|
|
63
|
+
process.exit(2);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return args;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const resolveMigratePath = (
|
|
70
|
+
args: { migratePath: string | null; migrateRoot: string | null },
|
|
71
|
+
dialect: string,
|
|
72
|
+
): string => {
|
|
73
|
+
if (args.migratePath) return args.migratePath;
|
|
74
|
+
const root = args.migrateRoot ?? 'sql';
|
|
75
|
+
return `${root}/${dialect}/migrations`;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const main = async () => {
|
|
79
|
+
const args = parseArgs(process.argv);
|
|
80
|
+
if (!args.provider) {
|
|
81
|
+
process.stderr.write(missingProviderMsg);
|
|
82
|
+
process.exit(2);
|
|
83
|
+
}
|
|
84
|
+
const dialect = requireDialect(args.provider);
|
|
85
|
+
const connection = requireConnection(dialect, args.connection);
|
|
86
|
+
|
|
87
|
+
const missing = await dialect.prerequisiteError(connection);
|
|
88
|
+
if (missing !== null) {
|
|
89
|
+
process.stderr.write(`${missing}\n`);
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const migratePath = resolve(resolveMigratePath(args, dialect.name));
|
|
94
|
+
const names = await new Migrater(dialect).up({
|
|
95
|
+
connection,
|
|
96
|
+
migrationsPath: migratePath,
|
|
97
|
+
one: args.one,
|
|
98
|
+
});
|
|
99
|
+
if (names.length === 0) {
|
|
100
|
+
process.stdout.write(noPendingMsg);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const name of names) process.stdout.write(fillTemplate(appliedTpl, { name }));
|
|
104
|
+
if (!args.one) process.stdout.write(noMorePendingMsg);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
main().catch((e: unknown) => {
|
|
108
|
+
const err = e as Error;
|
|
109
|
+
process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type { MigrateVerb } from './cli-spec.ts';
|
|
2
|
+
export { migrateCommand, usageLine, programUsageText } from './cli-spec.ts';
|
|
3
|
+
import { usageLine, type MigrateVerb } from './cli-spec.ts';
|
|
4
|
+
import { fillTemplate } from './infrastructure/message-templates.ts';
|
|
5
|
+
|
|
6
|
+
export const MIGRATE_VERBS = ['setup', 'up', 'down', 'create'] as const;
|
|
7
|
+
|
|
8
|
+
export const verbFromCommand = (command: string): MigrateVerb => {
|
|
9
|
+
const verb = command.replace(/^migrate-/, '');
|
|
10
|
+
if (
|
|
11
|
+
verb !== 'init' &&
|
|
12
|
+
verb !== 'setup' &&
|
|
13
|
+
verb !== 'up' &&
|
|
14
|
+
verb !== 'down' &&
|
|
15
|
+
verb !== 'create'
|
|
16
|
+
) {
|
|
17
|
+
throw new Error(`Unknown migrate command "${command}"`);
|
|
18
|
+
}
|
|
19
|
+
return verb;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const fillHelpTemplate = (text: string, command: string): string => {
|
|
23
|
+
const usage = usageLine(verbFromCommand(command));
|
|
24
|
+
return fillTemplate(text, { command, usage });
|
|
25
|
+
};
|