@deterministic-code/migraters 0.0.0-stage → 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.
Files changed (116) hide show
  1. package/README.md +1 -3
  2. package/bundle.yaml +262 -0
  3. package/cli.yaml +264 -0
  4. package/dist/abstractions/migrater.d.ts +28 -0
  5. package/dist/abstractions/migrater.js +1 -0
  6. package/dist/abstractions/sql-dialect-base.d.ts +16 -0
  7. package/dist/abstractions/sql-dialect-base.js +19 -0
  8. package/dist/abstractions/sql-dialect-factory.d.ts +8 -0
  9. package/dist/abstractions/sql-dialect-factory.js +22 -0
  10. package/dist/abstractions/sql-dialect.d.ts +16 -0
  11. package/dist/abstractions/sql-dialect.js +1 -0
  12. package/dist/bin/migrate-create.d.ts +2 -0
  13. package/dist/bin/migrate-create.js +71 -0
  14. package/dist/bin/migrate-down.d.ts +2 -0
  15. package/dist/bin/migrate-down.js +135 -0
  16. package/dist/bin/migrate-init.d.ts +2 -0
  17. package/dist/bin/migrate-init.js +77 -0
  18. package/dist/bin/migrate-setup.d.ts +2 -0
  19. package/dist/bin/migrate-setup.js +87 -0
  20. package/dist/bin/migrate-up.d.ts +2 -0
  21. package/dist/bin/migrate-up.js +97 -0
  22. package/dist/cli-contract.d.ts +6 -0
  23. package/dist/cli-contract.js +19 -0
  24. package/dist/cli-spec.d.ts +24 -0
  25. package/dist/cli-spec.js +33 -0
  26. package/dist/cli.d.ts +9 -0
  27. package/dist/cli.js +41 -0
  28. package/dist/create-sequence.d.ts +4 -0
  29. package/dist/create-sequence.js +16 -0
  30. package/dist/datasource-migrate.d.ts +31 -0
  31. package/dist/datasource-migrate.js +206 -0
  32. package/dist/dialects/mysql-dialect.d.ts +12 -0
  33. package/dist/dialects/mysql-dialect.js +56 -0
  34. package/dist/dialects/oracle-dialect.d.ts +18 -0
  35. package/dist/dialects/oracle-dialect.js +76 -0
  36. package/dist/dialects/postgres-dialect.d.ts +11 -0
  37. package/dist/dialects/postgres-dialect.js +58 -0
  38. package/dist/dialects/sqlite-dialect.d.ts +13 -0
  39. package/dist/dialects/sqlite-dialect.js +96 -0
  40. package/dist/dialects/sqlserver-dialect.d.ts +12 -0
  41. package/dist/dialects/sqlserver-dialect.js +73 -0
  42. package/dist/help.d.ts +2 -0
  43. package/dist/help.js +10 -0
  44. package/dist/index.d.ts +7 -0
  45. package/dist/index.js +6 -0
  46. package/dist/infrastructure/connection-resolver.d.ts +7 -0
  47. package/dist/infrastructure/connection-resolver.js +19 -0
  48. package/dist/infrastructure/default-factory.d.ts +4 -0
  49. package/dist/infrastructure/default-factory.js +15 -0
  50. package/dist/infrastructure/interop-default.d.ts +3 -0
  51. package/dist/infrastructure/interop-default.js +1 -0
  52. package/dist/infrastructure/message-templates.d.ts +3 -0
  53. package/dist/infrastructure/message-templates.js +18 -0
  54. package/dist/infrastructure/split-on-go.d.ts +2 -0
  55. package/dist/infrastructure/split-on-go.js +20 -0
  56. package/dist/infrastructure/sql-templates.d.ts +2 -0
  57. package/dist/infrastructure/sql-templates.js +4 -0
  58. package/dist/infrastructure/templates-root.d.ts +2 -0
  59. package/dist/infrastructure/templates-root.js +11 -0
  60. package/dist/init-select.d.ts +12 -0
  61. package/dist/init-select.js +52 -0
  62. package/dist/migrater.d.ts +12 -0
  63. package/dist/migrater.js +84 -0
  64. package/dist/migration-client.d.ts +21 -0
  65. package/dist/migration-client.js +1 -0
  66. package/dist/path-exists.d.ts +1 -0
  67. package/dist/path-exists.js +10 -0
  68. package/dist/sql.d.ts +3 -0
  69. package/dist/sql.js +15 -0
  70. package/dist/templates/help/create.txt +16 -0
  71. package/dist/templates/help/down.txt +42 -0
  72. package/dist/templates/help/init.txt +13 -0
  73. package/dist/templates/help/setup.txt +41 -0
  74. package/dist/templates/help/up.txt +43 -0
  75. package/dist/templates/messages/checksum-drift-line.txt +1 -0
  76. package/dist/templates/messages/checksum-drift.txt +11 -0
  77. package/dist/templates/messages/down/confirm-mismatch.txt +1 -0
  78. package/dist/templates/messages/down/confirm-prompt.txt +1 -0
  79. package/dist/templates/messages/down/confirm-stdin-failed.txt +1 -0
  80. package/dist/templates/messages/down/confirm-token-mismatch.txt +1 -0
  81. package/dist/templates/messages/down/confirm-tty-required.txt +1 -0
  82. package/dist/templates/messages/down/destructive-header.txt +3 -0
  83. package/dist/templates/messages/errors/invalid-name.txt +1 -0
  84. package/dist/templates/messages/errors/migrate-path-not-dir.txt +1 -0
  85. package/dist/templates/messages/errors/missing-connection.txt +1 -0
  86. package/dist/templates/messages/errors/missing-name-hint.txt +1 -0
  87. package/dist/templates/messages/errors/missing-name.txt +1 -0
  88. package/dist/templates/messages/errors/missing-provider-hint.txt +1 -0
  89. package/dist/templates/messages/errors/missing-provider.txt +1 -0
  90. package/dist/templates/messages/errors/missing-value-for-flag.txt +1 -0
  91. package/dist/templates/messages/errors/rollback-no-down-sibling.txt +1 -0
  92. package/dist/templates/messages/errors/rollback-no-rollback-sibling.txt +1 -0
  93. package/dist/templates/messages/errors/rollback-not-in-path.txt +1 -0
  94. package/dist/templates/messages/errors/sqlite-prerequisite.txt +1 -0
  95. package/dist/templates/messages/errors/unknown-arg.txt +1 -0
  96. package/dist/templates/messages/errors/unsupported-provider.txt +1 -0
  97. package/dist/templates/messages/setup-complete.txt +9 -0
  98. package/dist/templates/messages/status/applied.txt +1 -0
  99. package/dist/templates/messages/status/created.txt +1 -0
  100. package/dist/templates/messages/status/no-more-pending.txt +1 -0
  101. package/dist/templates/messages/status/no-pending.txt +1 -0
  102. package/dist/templates/messages/status/no-rollback.txt +1 -0
  103. package/dist/templates/messages/status/rolled-back.txt +1 -0
  104. package/dist/templates/scaffold/down.sql +1 -0
  105. package/dist/templates/scaffold/up.sql +1 -0
  106. package/dist/templates/sql/mysql/migrate_logs.sql +11 -0
  107. package/dist/templates/sql/mysql/migrates.sql +7 -0
  108. package/dist/templates/sql/oracle/migrate_logs.sql +14 -0
  109. package/dist/templates/sql/oracle/migrates.sql +10 -0
  110. package/dist/templates/sql/postgres/migrate_logs.sql +11 -0
  111. package/dist/templates/sql/postgres/migrates.sql +7 -0
  112. package/dist/templates/sql/sqlite/migrate_logs.sql +11 -0
  113. package/dist/templates/sql/sqlite/migrates.sql +7 -0
  114. package/dist/templates/sql/sqlserver/migrate_logs.sql +12 -0
  115. package/dist/templates/sql/sqlserver/migrates.sql +8 -0
  116. package/package.json +79 -4
@@ -0,0 +1,71 @@
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
+ import { missingProviderMsg, requireDialect, takeFlag } from "../cli.js";
4
+ import { isMigrationName } from "../create-sequence.js";
5
+ import { fillTemplate, loadMessage } from "../infrastructure/message-templates.js";
6
+ import { loadHelp, Migrater } from "../index.js";
7
+ const [HELP_TEXT, unknownArgTpl, missingNameMsg, invalidNameTpl, createdTpl] = await Promise.all([
8
+ loadHelp('migrate-create'),
9
+ loadMessage('errors/unknown-arg'),
10
+ loadMessage('errors/missing-name'),
11
+ loadMessage('errors/invalid-name'),
12
+ loadMessage('status/created'),
13
+ ]);
14
+ const parseArgs = (argv) => {
15
+ const args = { provider: null, name: null, migrationsPath: null };
16
+ const rest = argv.slice(2);
17
+ for (let i = 0; i < rest.length; i++) {
18
+ const a = rest[i];
19
+ if (a === '-h' || a === '--help') {
20
+ process.stderr.write(HELP_TEXT);
21
+ process.exit(0);
22
+ }
23
+ let taken;
24
+ if ((taken = takeFlag(rest, i, a, '--provider'))) {
25
+ args.provider = taken.value;
26
+ i = taken.next;
27
+ }
28
+ else if ((taken = takeFlag(rest, i, a, '--name'))) {
29
+ args.name = taken.value;
30
+ i = taken.next;
31
+ }
32
+ else if ((taken = takeFlag(rest, i, a, '--migrations-path')) ||
33
+ (taken = takeFlag(rest, i, a, '--migrate-path'))) {
34
+ args.migrationsPath = taken.value;
35
+ i = taken.next;
36
+ }
37
+ else {
38
+ process.stderr.write(fillTemplate(unknownArgTpl, { arg: a }));
39
+ process.exit(2);
40
+ }
41
+ }
42
+ return args;
43
+ };
44
+ const main = async () => {
45
+ const args = parseArgs(process.argv);
46
+ if (!args.provider) {
47
+ process.stderr.write(missingProviderMsg);
48
+ process.exit(2);
49
+ }
50
+ const dialect = requireDialect(args.provider);
51
+ if (!args.name) {
52
+ process.stderr.write(missingNameMsg);
53
+ process.exit(2);
54
+ }
55
+ if (!isMigrationName(args.name)) {
56
+ process.stderr.write(fillTemplate(invalidNameTpl, { name: args.name }));
57
+ process.exit(2);
58
+ }
59
+ const migrationsPath = args.migrationsPath ?? `./sql/${dialect.name}/migrations`;
60
+ const { upName, downName } = await new Migrater(dialect).create({
61
+ name: args.name,
62
+ migrationsPath,
63
+ });
64
+ process.stdout.write(fillTemplate(createdTpl, { file: upName }));
65
+ process.stdout.write(fillTemplate(createdTpl, { file: downName }));
66
+ };
67
+ main().catch((e) => {
68
+ const err = e;
69
+ process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
70
+ process.exit(1);
71
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,135 @@
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
+ import { resolve } from 'node:path';
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { missingProviderMsg, requireConnection, requireDialect, takeFlag } from "../cli.js";
6
+ import { fillTemplate, loadMessage } from "../infrastructure/message-templates.js";
7
+ import { loadHelp, Migrater } from "../index.js";
8
+ const [HELP_TEXT, unknownArgTpl, destructiveHeaderTpl, confirmMismatchTpl, confirmTtyRequiredTpl, confirmPromptTpl, confirmTokenMismatchMsg, noRollbackMsg, rolledBackTpl,] = await Promise.all([
9
+ loadHelp('migrate-down'),
10
+ loadMessage('errors/unknown-arg'),
11
+ loadMessage('down/destructive-header'),
12
+ loadMessage('down/confirm-mismatch'),
13
+ loadMessage('down/confirm-tty-required'),
14
+ loadMessage('down/confirm-prompt'),
15
+ loadMessage('down/confirm-token-mismatch'),
16
+ loadMessage('status/no-rollback'),
17
+ loadMessage('status/rolled-back'),
18
+ ]);
19
+ const parseArgs = (argv) => {
20
+ const args = {
21
+ provider: null,
22
+ migratePath: null,
23
+ migrateRoot: null,
24
+ connection: null,
25
+ confirm: null,
26
+ };
27
+ const rest = argv.slice(2);
28
+ for (let i = 0; i < rest.length; i++) {
29
+ const a = rest[i];
30
+ if (a === '-h' || a === '--help') {
31
+ process.stderr.write(HELP_TEXT);
32
+ process.exit(0);
33
+ }
34
+ let taken;
35
+ if ((taken = takeFlag(rest, i, a, '--provider'))) {
36
+ args.provider = taken.value;
37
+ i = taken.next;
38
+ }
39
+ else if ((taken = takeFlag(rest, i, a, '--migrations-path')) ||
40
+ (taken = takeFlag(rest, i, a, '--migrate-path'))) {
41
+ args.migratePath = taken.value;
42
+ i = taken.next;
43
+ }
44
+ else if ((taken = takeFlag(rest, i, a, '--migrations-root')) ||
45
+ (taken = takeFlag(rest, i, a, '--migrate-root'))) {
46
+ args.migrateRoot = taken.value;
47
+ i = taken.next;
48
+ }
49
+ else if ((taken = takeFlag(rest, i, a, '--connection'))) {
50
+ args.connection = taken.value;
51
+ i = taken.next;
52
+ }
53
+ else if ((taken = takeFlag(rest, i, a, '--confirm'))) {
54
+ args.confirm = taken.value;
55
+ i = taken.next;
56
+ }
57
+ else {
58
+ process.stderr.write(fillTemplate(unknownArgTpl, { arg: a }));
59
+ process.exit(2);
60
+ }
61
+ }
62
+ return args;
63
+ };
64
+ const resolveMigratePath = (args, dialect) => {
65
+ if (args.migratePath)
66
+ return args.migratePath;
67
+ const root = args.migrateRoot ?? 'sql';
68
+ return `${root}/${dialect}/migrations`;
69
+ };
70
+ const randomToken = () => {
71
+ const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
72
+ const buf = new Uint8Array(4);
73
+ globalThis.crypto.getRandomValues(buf);
74
+ let out = '';
75
+ for (let i = 0; i < 4; i++)
76
+ out += alphabet[buf[i] % alphabet.length];
77
+ return out;
78
+ };
79
+ const promptConfirmation = async (token) => {
80
+ if (!process.stdin.isTTY) {
81
+ process.stderr.write(fillTemplate(confirmTtyRequiredTpl, { token }));
82
+ process.exit(2);
83
+ }
84
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
85
+ try {
86
+ const answer = await rl.question(fillTemplate(confirmPromptTpl, { token }));
87
+ return answer.trim() === token;
88
+ }
89
+ finally {
90
+ rl.close();
91
+ }
92
+ };
93
+ const main = async () => {
94
+ const args = parseArgs(process.argv);
95
+ if (!args.provider) {
96
+ process.stderr.write(missingProviderMsg);
97
+ process.exit(2);
98
+ }
99
+ const dialect = requireDialect(args.provider);
100
+ const connection = requireConnection(dialect, args.connection);
101
+ const missing = await dialect.prerequisiteError(connection);
102
+ if (missing !== null) {
103
+ process.stderr.write(`${missing}\n`);
104
+ process.exit(2);
105
+ }
106
+ const token = randomToken();
107
+ process.stderr.write(fillTemplate(destructiveHeaderTpl, { dialect: dialect.name, token }));
108
+ if (args.confirm !== null) {
109
+ if (args.confirm !== token) {
110
+ process.stderr.write(fillTemplate(confirmMismatchTpl, { supplied: args.confirm, token }));
111
+ process.exit(2);
112
+ }
113
+ }
114
+ else {
115
+ const ok = await promptConfirmation(token);
116
+ if (!ok) {
117
+ process.stderr.write(confirmTokenMismatchMsg);
118
+ process.exit(2);
119
+ }
120
+ }
121
+ const migratePath = resolve(resolveMigratePath(args, dialect.name));
122
+ const name = await new Migrater(dialect).down({
123
+ connection,
124
+ migrationsPath: migratePath,
125
+ });
126
+ if (name == null)
127
+ process.stdout.write(noRollbackMsg);
128
+ else
129
+ process.stdout.write(fillTemplate(rolledBackTpl, { name }));
130
+ };
131
+ main().catch((e) => {
132
+ const err = e;
133
+ process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
134
+ process.exit(1);
135
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import { loadHelp } from "../help.js";
4
+ import { loadSelectedPackages, resolveInitDialects } from "../init-select.js";
5
+ const HELP_TEXT = await loadHelp('migrate-init');
6
+ const takeFlag = (rest, i, arg, flag) => {
7
+ const eq = `${flag}=`;
8
+ if (arg === flag) {
9
+ const value = rest[i + 1];
10
+ if (value === undefined)
11
+ return undefined;
12
+ return { value, next: i + 1 };
13
+ }
14
+ if (arg.startsWith(eq))
15
+ return { value: arg.slice(eq.length), next: i };
16
+ return undefined;
17
+ };
18
+ const parseArgs = (argv) => {
19
+ const dialects = [];
20
+ let all = false;
21
+ const rest = argv.slice(2);
22
+ for (let i = 0; i < rest.length; i++) {
23
+ const a = rest[i];
24
+ if (a === '-h' || a === '--help') {
25
+ process.stderr.write(HELP_TEXT);
26
+ process.exit(0);
27
+ }
28
+ if (a === '--all') {
29
+ all = true;
30
+ continue;
31
+ }
32
+ const taken = takeFlag(rest, i, a, '--dialect');
33
+ if (taken) {
34
+ dialects.push(taken.value);
35
+ i = taken.next;
36
+ continue;
37
+ }
38
+ process.stderr.write(`unknown arg: ${a}\n`);
39
+ process.exit(2);
40
+ }
41
+ return { dialects, all };
42
+ };
43
+ const npmFlags = ['--no-audit', '--no-fund', '--loglevel=error'];
44
+ const npm = (args) => {
45
+ const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
46
+ const result = spawnSync(cmd, args, { stdio: 'inherit', cwd: process.cwd() });
47
+ if (result.status !== 0) {
48
+ process.exit(result.status ?? 1);
49
+ }
50
+ };
51
+ const main = async () => {
52
+ const args = parseArgs(process.argv);
53
+ let names;
54
+ try {
55
+ names = resolveInitDialects(args.dialects, args.all);
56
+ }
57
+ catch (e) {
58
+ const err = e;
59
+ process.stderr.write(`${err.message}\n`);
60
+ process.exit(2);
61
+ }
62
+ const selected = await loadSelectedPackages(names);
63
+ const specs = selected.packages.map((p) => `${p.name}@${p.version}`);
64
+ if (specs.length > 0) {
65
+ npm(['install', ...npmFlags, ...specs]);
66
+ }
67
+ const remove = selected.allDriverPackages.filter((name) => !selected.packages.some((p) => p.name === name));
68
+ if (remove.length > 0) {
69
+ npm(['uninstall', ...npmFlags, ...remove]);
70
+ }
71
+ process.stdout.write(`Enabled dialects: ${selected.names.join(', ')}\n`);
72
+ };
73
+ main().catch((e) => {
74
+ const err = e;
75
+ process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
76
+ process.exit(1);
77
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ // Idempotent migrate-setup — creates the migrates + migrate_logs tracking tables per dialect.
3
+ import { missingProviderMsg, requireConnection, requireDialect, takeFlag } from "../cli.js";
4
+ import { fillTemplate, loadMessage } from "../infrastructure/message-templates.js";
5
+ import { loadHelp, Migrater } from "../index.js";
6
+ const [HELP_TEXT, unknownArgTpl, setupCompleteTpl] = await Promise.all([
7
+ loadHelp('migrate-setup'),
8
+ loadMessage('errors/unknown-arg'),
9
+ loadMessage('setup-complete'),
10
+ ]);
11
+ const parseArgs = (argv) => {
12
+ const args = {
13
+ provider: null,
14
+ connection: null,
15
+ migrationsPath: null,
16
+ andUp: false,
17
+ };
18
+ const rest = argv.slice(2);
19
+ for (let i = 0; i < rest.length; i++) {
20
+ const a = rest[i];
21
+ if (a === '-h' || a === '--help') {
22
+ process.stderr.write(HELP_TEXT);
23
+ process.exit(0);
24
+ }
25
+ if (a === '--and-up') {
26
+ args.andUp = true;
27
+ continue;
28
+ }
29
+ let taken;
30
+ if ((taken = takeFlag(rest, i, a, '--provider'))) {
31
+ args.provider = taken.value;
32
+ i = taken.next;
33
+ }
34
+ else if ((taken = takeFlag(rest, i, a, '--connection'))) {
35
+ args.connection = taken.value;
36
+ i = taken.next;
37
+ }
38
+ else if ((taken = takeFlag(rest, i, a, '--migrations-path')) ||
39
+ (taken = takeFlag(rest, i, a, '--migrate-path'))) {
40
+ args.migrationsPath = taken.value;
41
+ i = taken.next;
42
+ }
43
+ else {
44
+ process.stderr.write(fillTemplate(unknownArgTpl, { arg: a }));
45
+ process.exit(2);
46
+ }
47
+ }
48
+ return args;
49
+ };
50
+ const main = async () => {
51
+ const args = parseArgs(process.argv);
52
+ if (!args.provider) {
53
+ process.stderr.write(missingProviderMsg);
54
+ process.exit(2);
55
+ }
56
+ const dialect = requireDialect(args.provider);
57
+ const connection = requireConnection(dialect, args.connection);
58
+ const migrationsPath = args.migrationsPath ?? `./sql/${dialect.name}/migrations`;
59
+ const migrater = new Migrater(dialect);
60
+ await migrater.setup({ connection, migrationsPath });
61
+ process.stdout.write(fillTemplate(setupCompleteTpl, {
62
+ dialect: dialect.name,
63
+ migrationsPath,
64
+ createExample: ` migrate-create --provider ${dialect.name} --name add_users`,
65
+ upExample: ` migrate-up --provider ${dialect.name} --connection ${connection}`,
66
+ }));
67
+ if (args.andUp) {
68
+ const names = await migrater.up({ connection, migrationsPath });
69
+ const [noPendingMsg, noMorePendingMsg, appliedTpl] = await Promise.all([
70
+ loadMessage('status/no-pending'),
71
+ loadMessage('status/no-more-pending'),
72
+ loadMessage('status/applied'),
73
+ ]);
74
+ if (names.length === 0)
75
+ process.stdout.write(noPendingMsg);
76
+ else {
77
+ for (const name of names)
78
+ process.stdout.write(fillTemplate(appliedTpl, { name }));
79
+ process.stdout.write(noMorePendingMsg);
80
+ }
81
+ }
82
+ };
83
+ main().catch((e) => {
84
+ const err = e;
85
+ process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
86
+ process.exit(1);
87
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,97 @@
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
+ import { resolve } from 'node:path';
4
+ import { missingProviderMsg, requireConnection, requireDialect, takeFlag } from "../cli.js";
5
+ import { fillTemplate, loadMessage } from "../infrastructure/message-templates.js";
6
+ import { loadHelp, Migrater } from "../index.js";
7
+ const [HELP_TEXT, unknownArgTpl, noPendingMsg, noMorePendingMsg, appliedTpl] = await Promise.all([
8
+ loadHelp('migrate-up'),
9
+ loadMessage('errors/unknown-arg'),
10
+ loadMessage('status/no-pending'),
11
+ loadMessage('status/no-more-pending'),
12
+ loadMessage('status/applied'),
13
+ ]);
14
+ const parseArgs = (argv) => {
15
+ const args = {
16
+ provider: null,
17
+ migratePath: null,
18
+ migrateRoot: null,
19
+ connection: null,
20
+ one: false,
21
+ };
22
+ const rest = argv.slice(2);
23
+ for (let i = 0; i < rest.length; i++) {
24
+ const a = rest[i];
25
+ if (a === '-h' || a === '--help') {
26
+ process.stderr.write(HELP_TEXT);
27
+ process.exit(0);
28
+ }
29
+ if (a === '--one') {
30
+ args.one = true;
31
+ continue;
32
+ }
33
+ let taken;
34
+ if ((taken = takeFlag(rest, i, a, '--provider'))) {
35
+ args.provider = taken.value;
36
+ i = taken.next;
37
+ }
38
+ else if ((taken = takeFlag(rest, i, a, '--migrations-path')) ||
39
+ (taken = takeFlag(rest, i, a, '--migrate-path'))) {
40
+ args.migratePath = taken.value;
41
+ i = taken.next;
42
+ }
43
+ else if ((taken = takeFlag(rest, i, a, '--migrations-root')) ||
44
+ (taken = takeFlag(rest, i, a, '--migrate-root'))) {
45
+ args.migrateRoot = taken.value;
46
+ i = taken.next;
47
+ }
48
+ else if ((taken = takeFlag(rest, i, a, '--connection'))) {
49
+ args.connection = taken.value;
50
+ i = taken.next;
51
+ }
52
+ else {
53
+ process.stderr.write(fillTemplate(unknownArgTpl, { arg: a }));
54
+ process.exit(2);
55
+ }
56
+ }
57
+ return args;
58
+ };
59
+ const resolveMigratePath = (args, dialect) => {
60
+ if (args.migratePath)
61
+ return args.migratePath;
62
+ const root = args.migrateRoot ?? 'sql';
63
+ return `${root}/${dialect}/migrations`;
64
+ };
65
+ const main = async () => {
66
+ const args = parseArgs(process.argv);
67
+ if (!args.provider) {
68
+ process.stderr.write(missingProviderMsg);
69
+ process.exit(2);
70
+ }
71
+ const dialect = requireDialect(args.provider);
72
+ const connection = requireConnection(dialect, args.connection);
73
+ const missing = await dialect.prerequisiteError(connection);
74
+ if (missing !== null) {
75
+ process.stderr.write(`${missing}\n`);
76
+ process.exit(2);
77
+ }
78
+ const migratePath = resolve(resolveMigratePath(args, dialect.name));
79
+ const names = await new Migrater(dialect).up({
80
+ connection,
81
+ migrationsPath: migratePath,
82
+ one: args.one,
83
+ });
84
+ if (names.length === 0) {
85
+ process.stdout.write(noPendingMsg);
86
+ return;
87
+ }
88
+ for (const name of names)
89
+ process.stdout.write(fillTemplate(appliedTpl, { name }));
90
+ if (!args.one)
91
+ process.stdout.write(noMorePendingMsg);
92
+ };
93
+ main().catch((e) => {
94
+ const err = e;
95
+ process.stderr.write(`${err?.stack ?? err?.message ?? String(e)}\n`);
96
+ process.exit(1);
97
+ });
@@ -0,0 +1,6 @@
1
+ export type { MigrateVerb } from './cli-spec.ts';
2
+ export { migrateCommand, usageLine, programUsageText } from './cli-spec.ts';
3
+ import { type MigrateVerb } from './cli-spec.ts';
4
+ export declare const MIGRATE_VERBS: readonly ["setup", "up", "down", "create"];
5
+ export declare const verbFromCommand: (command: string) => MigrateVerb;
6
+ export declare const fillHelpTemplate: (text: string, command: string) => string;
@@ -0,0 +1,19 @@
1
+ export { migrateCommand, usageLine, programUsageText } from "./cli-spec.js";
2
+ import { usageLine } from "./cli-spec.js";
3
+ import { fillTemplate } from "./infrastructure/message-templates.js";
4
+ export const MIGRATE_VERBS = ['setup', 'up', 'down', 'create'];
5
+ export const verbFromCommand = (command) => {
6
+ const verb = command.replace(/^migrate-/, '');
7
+ if (verb !== 'init' &&
8
+ verb !== 'setup' &&
9
+ verb !== 'up' &&
10
+ verb !== 'down' &&
11
+ verb !== 'create') {
12
+ throw new Error(`Unknown migrate command "${command}"`);
13
+ }
14
+ return verb;
15
+ };
16
+ export const fillHelpTemplate = (text, command) => {
17
+ const usage = usageLine(verbFromCommand(command));
18
+ return fillTemplate(text, { command, usage });
19
+ };
@@ -0,0 +1,24 @@
1
+ export type MigrateVerb = 'init' | 'setup' | 'up' | 'down' | 'create';
2
+ export type CliArg = {
3
+ flag: string;
4
+ placeholder?: string;
5
+ required: boolean;
6
+ };
7
+ export type CliCommand = {
8
+ verb: MigrateVerb;
9
+ args: CliArg[];
10
+ };
11
+ export type CliSpec = {
12
+ command: string;
13
+ providers: string[];
14
+ provider_aliases: Record<string, string>;
15
+ aliases: Record<string, string>;
16
+ env_var_list_separator: string;
17
+ connection_environment: Record<string, string[]>;
18
+ commands: CliCommand[];
19
+ };
20
+ export declare const cliYamlPath: () => string;
21
+ export declare const cliSpec: CliSpec;
22
+ export declare const migrateCommand: (verb: MigrateVerb) => string;
23
+ export declare const usageLine: (verb: MigrateVerb) => string;
24
+ export declare const programUsageText: () => string;
@@ -0,0 +1,33 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { parse } from 'yaml';
6
+ export const cliYamlPath = () => {
7
+ const here = dirname(fileURLToPath(import.meta.url));
8
+ const candidates = [
9
+ join(here, '..', 'cli.yaml'),
10
+ join(here, '..', '..', 'cli.yaml'),
11
+ join(process.cwd(), 'cli.yaml'),
12
+ ];
13
+ const found = candidates.find((candidate) => existsSync(candidate));
14
+ if (!found) {
15
+ throw new Error('cli.yaml not found');
16
+ }
17
+ return found;
18
+ };
19
+ export const cliSpec = parse(await readFile(cliYamlPath(), 'utf8'));
20
+ export const migrateCommand = (verb) => cliSpec.command.replace('{verb}', verb);
21
+ const providers = cliSpec.providers.join('|');
22
+ const argUsage = (arg) => {
23
+ const ph = arg.placeholder === 'providers' ? providers : arg.placeholder;
24
+ const body = ph ? `${arg.flag} <${ph}>` : arg.flag;
25
+ return arg.required ? body : `[${body}]`;
26
+ };
27
+ export const usageLine = (verb) => {
28
+ const command = cliSpec.commands.find((c) => c.verb === verb);
29
+ if (!command)
30
+ throw new Error(`cli.yaml: missing command ${verb}`);
31
+ return `Usage: ${migrateCommand(verb)} ${command.args.map(argUsage).join(' ')}`;
32
+ };
33
+ export const programUsageText = () => `${cliSpec.commands.map((c) => usageLine(c.verb)).join('\n')}\n`;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { ISqlDialect } from './abstractions/sql-dialect.ts';
2
+ declare const missingProviderMsg: string;
3
+ export { missingProviderMsg };
4
+ export declare const requireDialect: (provider: string | null) => ISqlDialect;
5
+ export declare const requireConnection: (dialect: ISqlDialect, fromFlag: string | null) => string;
6
+ export declare const takeFlag: (rest: string[], i: number, arg: string, flag: string) => {
7
+ value: string;
8
+ next: number;
9
+ } | undefined;
package/dist/cli.js ADDED
@@ -0,0 +1,41 @@
1
+ import { connectionResolver, defaultDialectFactory } from "./infrastructure/default-factory.js";
2
+ import { cliSpec } from "./cli-spec.js";
3
+ import { fillTemplate, loadMessage } from "./infrastructure/message-templates.js";
4
+ const missingProviderMsg = await loadMessage('errors/missing-provider');
5
+ const unsupportedProviderTpl = await loadMessage('errors/unsupported-provider');
6
+ const missingConnectionTpl = await loadMessage('errors/missing-connection');
7
+ export { missingProviderMsg };
8
+ export const requireDialect = (provider) => {
9
+ if (!provider) {
10
+ process.stderr.write(missingProviderMsg);
11
+ process.exit(2);
12
+ }
13
+ const dialect = defaultDialectFactory.tryGet(provider);
14
+ if (!dialect) {
15
+ process.stderr.write(fillTemplate(unsupportedProviderTpl, { provider }));
16
+ process.exit(2);
17
+ }
18
+ return dialect;
19
+ };
20
+ export const requireConnection = (dialect, fromFlag) => {
21
+ const connection = connectionResolver.resolve(dialect, fromFlag);
22
+ if (!connection) {
23
+ process.stderr.write(fillTemplate(missingConnectionTpl, {
24
+ envVars: dialect.connectionEnvironmentVariables.join(cliSpec.env_var_list_separator),
25
+ }));
26
+ process.exit(2);
27
+ }
28
+ return connection;
29
+ };
30
+ export const takeFlag = (rest, i, arg, flag) => {
31
+ const eq = `${flag}=`;
32
+ if (arg === flag) {
33
+ const value = rest[i + 1];
34
+ if (value === undefined)
35
+ return undefined;
36
+ return { value, next: i + 1 };
37
+ }
38
+ if (arg.startsWith(eq))
39
+ return { value: arg.slice(eq.length), next: i };
40
+ return undefined;
41
+ };
@@ -0,0 +1,4 @@
1
+ export declare const MIGRATION_NAME_RE: RegExp;
2
+ export declare const isMigrationName: (name: string) => boolean;
3
+ export declare const nextSequence: (filenames: Iterable<string>) => number;
4
+ export declare const pad4: (n: number) => string;
@@ -0,0 +1,16 @@
1
+ export const MIGRATION_NAME_RE = /^[a-z][a-z0-9_]*$/;
2
+ const SEQ_RE = /^(\d{1,4})_.*_up\.sql$/;
3
+ export const isMigrationName = (name) => MIGRATION_NAME_RE.test(name);
4
+ export const nextSequence = (filenames) => {
5
+ let max = 0;
6
+ for (const name of filenames) {
7
+ const m = SEQ_RE.exec(name);
8
+ if (!m)
9
+ continue;
10
+ const n = Number.parseInt(m[1], 10);
11
+ if (n > max)
12
+ max = n;
13
+ }
14
+ return max + 1;
15
+ };
16
+ export const pad4 = (n) => String(n).padStart(4, '0');
@@ -0,0 +1,31 @@
1
+ import type { MigrationClient } from './migration-client.ts';
2
+ export { normalizeDialect } from './sql.ts';
3
+ export type { MigrationClient, MigrationRow, MigrationSqlParam, SqliteRawDatabase, } from './migration-client.ts';
4
+ export { applyMysqlDdlViaTextProtocol } from './dialects/mysql-dialect.ts';
5
+ /** One discovered migration: its up script and the rollback sibling when present. */
6
+ export interface MigrationDescriptor {
7
+ name: string;
8
+ upPath: string;
9
+ downPath: string | null;
10
+ }
11
+ export declare function discoverMigrations({ migratePath, dialect, }: {
12
+ migratePath: string;
13
+ dialect: string;
14
+ }): Promise<MigrationDescriptor[]>;
15
+ export declare function checksum(text: string): string;
16
+ export declare function applyOneUp({ client, migrations, env, }: {
17
+ client: MigrationClient;
18
+ migrations: MigrationDescriptor[];
19
+ env?: NodeJS.ProcessEnv;
20
+ }): Promise<{
21
+ applied: boolean;
22
+ name: string | null;
23
+ }>;
24
+ export declare function applyOneDown({ client, migrations, env, }: {
25
+ client: MigrationClient;
26
+ migrations: MigrationDescriptor[];
27
+ env?: NodeJS.ProcessEnv;
28
+ }): Promise<{
29
+ rolledBack: boolean;
30
+ name: string | null;
31
+ }>;