@ftschopp/dynatable-migrations 1.2.6 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +12 -0
- package/dist/cli.js +21 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/create.js +1 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/down.d.ts +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +61 -1
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/unlock.d.ts +8 -0
- package/dist/commands/unlock.d.ts.map +1 -0
- package/dist/commands/unlock.js +87 -0
- package/dist/commands/unlock.js.map +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +14 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/loader.d.ts +7 -1
- package/dist/core/loader.d.ts.map +1 -1
- package/dist/core/loader.js +35 -6
- package/dist/core/loader.js.map +1 -1
- package/dist/core/tracker.d.ts.map +1 -1
- package/dist/core/tracker.js +23 -12
- package/dist/core/tracker.js.map +1 -1
- package/package.json +35 -4
- package/.gitkeep +0 -0
- package/jest.config.js +0 -8
- package/migrations/.gitkeep +0 -0
- package/src/cli-utils.test.ts +0 -45
- package/src/cli-utils.ts +0 -18
- package/src/cli.ts +0 -110
- package/src/commands/create.ts +0 -111
- package/src/commands/down.ts +0 -26
- package/src/commands/init.ts +0 -37
- package/src/commands/status.ts +0 -75
- package/src/commands/up.ts +0 -26
- package/src/core/client.ts +0 -24
- package/src/core/config.ts +0 -140
- package/src/core/errors.ts +0 -55
- package/src/core/loader.ts +0 -256
- package/src/core/lock-heartbeat.test.ts +0 -78
- package/src/core/lock-heartbeat.ts +0 -48
- package/src/core/runner.test.ts +0 -51
- package/src/core/runner.ts +0 -315
- package/src/core/semver.test.ts +0 -33
- package/src/core/semver.ts +0 -26
- package/src/core/tracker.test.ts +0 -281
- package/src/core/tracker.ts +0 -559
- package/src/index.ts +0 -25
- package/src/templates/migration.ts +0 -92
- package/src/types/index.ts +0 -220
- package/tsconfig.json +0 -19
package/package.json
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ftschopp/dynatable-migrations",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "DynamoDB migration tool for single table design with schema versioning",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
7
14
|
"bin": {
|
|
8
15
|
"dynatable-migrate": "./bin/dynatable-migrate.js"
|
|
9
16
|
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"bin",
|
|
20
|
+
"README.md",
|
|
21
|
+
"USAGE.md",
|
|
22
|
+
"CHANGELOG.md"
|
|
23
|
+
],
|
|
10
24
|
"publishConfig": {
|
|
11
25
|
"access": "public"
|
|
12
26
|
},
|
|
@@ -14,7 +28,9 @@
|
|
|
14
28
|
"build": "tsc",
|
|
15
29
|
"dev": "tsc --watch",
|
|
16
30
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
17
|
-
"
|
|
31
|
+
"lint": "eslint src",
|
|
32
|
+
"test": "jest",
|
|
33
|
+
"prepublishOnly": "yarn build"
|
|
18
34
|
},
|
|
19
35
|
"keywords": [
|
|
20
36
|
"dynamodb",
|
|
@@ -29,6 +45,7 @@
|
|
|
29
45
|
"@types/jest": "^30.0.0",
|
|
30
46
|
"@types/node": "^25.0.3",
|
|
31
47
|
"aws-sdk-client-mock": "^4.1.0",
|
|
48
|
+
"eslint": "^9.39.2",
|
|
32
49
|
"jest": "^30.2.0",
|
|
33
50
|
"ts-jest": "^29.4.6",
|
|
34
51
|
"typescript": "5.9.3"
|
|
@@ -36,7 +53,21 @@
|
|
|
36
53
|
"dependencies": {
|
|
37
54
|
"@aws-sdk/client-dynamodb": "^3.965.0",
|
|
38
55
|
"@aws-sdk/lib-dynamodb": "^3.965.0",
|
|
39
|
-
"commander": "^14.0.2"
|
|
40
|
-
|
|
56
|
+
"commander": "^14.0.2"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"ts-node": "^10.9.0",
|
|
60
|
+
"typescript": ">=4.5"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"ts-node": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"typescript": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=22"
|
|
41
72
|
}
|
|
42
73
|
}
|
package/.gitkeep
DELETED
|
File without changes
|
package/jest.config.js
DELETED
package/migrations/.gitkeep
DELETED
|
File without changes
|
package/src/cli-utils.test.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { parsePositiveInt } from './cli-utils';
|
|
2
|
-
|
|
3
|
-
describe('parsePositiveInt', () => {
|
|
4
|
-
const parser = parsePositiveInt('limit');
|
|
5
|
-
|
|
6
|
-
test('accepts positive integers', () => {
|
|
7
|
-
expect(parser('1')).toBe(1);
|
|
8
|
-
expect(parser('42')).toBe(42);
|
|
9
|
-
expect(parser('1000')).toBe(1000);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test('trims surrounding whitespace before parsing', () => {
|
|
13
|
-
expect(parser(' 5 ')).toBe(5);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('rejects 0', () => {
|
|
17
|
-
expect(() => parser('0')).toThrow(/--limit must be a positive integer/);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('rejects negative numbers', () => {
|
|
21
|
-
expect(() => parser('-5')).toThrow(/--limit must be a positive integer/);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('rejects non-numeric input', () => {
|
|
25
|
-
expect(() => parser('abc')).toThrow(/--limit must be a positive integer/);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('rejects floats (1.5 would parseInt to 1, but the trailing decimals are still wrong)', () => {
|
|
29
|
-
expect(() => parser('1.5')).toThrow(/--limit must be a positive integer/);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('rejects empty string', () => {
|
|
33
|
-
expect(() => parser('')).toThrow(/--limit must be a positive integer/);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('rejects strings with trailing junk that parseInt would silently swallow', () => {
|
|
37
|
-
// parseInt('5abc', 10) === 5, but we should refuse it because the input
|
|
38
|
-
// is clearly not a positive integer.
|
|
39
|
-
expect(() => parser('5abc')).toThrow(/--limit must be a positive integer/);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('error mentions the flag name passed to the factory', () => {
|
|
43
|
-
expect(() => parsePositiveInt('steps')('-1')).toThrow(/--steps/);
|
|
44
|
-
});
|
|
45
|
-
});
|
package/src/cli-utils.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { InvalidArgumentError } from 'commander';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Commander value parser that accepts only positive integers. Used for
|
|
5
|
-
* `--limit` and `--steps` so `0`, negative numbers, and non-numeric input
|
|
6
|
-
* fail at parse time with a clean usage error instead of silently feeding
|
|
7
|
-
* `Array.prototype.slice` and producing surprising rollbacks.
|
|
8
|
-
*/
|
|
9
|
-
export function parsePositiveInt(name: string): (raw: string) => number {
|
|
10
|
-
return (raw: string) => {
|
|
11
|
-
const trimmed = raw.trim();
|
|
12
|
-
const n = Number.parseInt(trimmed, 10);
|
|
13
|
-
if (!Number.isInteger(n) || n <= 0 || String(n) !== trimmed) {
|
|
14
|
-
throw new InvalidArgumentError(`--${name} must be a positive integer (got "${raw}").`);
|
|
15
|
-
}
|
|
16
|
-
return n;
|
|
17
|
-
};
|
|
18
|
-
}
|
package/src/cli.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { loadConfig } from './core/config';
|
|
5
|
-
import { createMigration } from './commands/create';
|
|
6
|
-
import { runMigrations } from './commands/up';
|
|
7
|
-
import { rollbackMigrations } from './commands/down';
|
|
8
|
-
import { showStatus } from './commands/status';
|
|
9
|
-
import { initProject } from './commands/init';
|
|
10
|
-
import { parsePositiveInt } from './cli-utils';
|
|
11
|
-
|
|
12
|
-
// Read version from package.json
|
|
13
|
-
const packageJson = require('../package.json');
|
|
14
|
-
|
|
15
|
-
const program = new Command();
|
|
16
|
-
|
|
17
|
-
program
|
|
18
|
-
.name('dynatable-migrate')
|
|
19
|
-
.description('DynamoDB migration tool for single table design')
|
|
20
|
-
.version(packageJson.version);
|
|
21
|
-
|
|
22
|
-
// Init command
|
|
23
|
-
program
|
|
24
|
-
.command('init')
|
|
25
|
-
.description('Initialize migrations in current project')
|
|
26
|
-
.action(async () => {
|
|
27
|
-
try {
|
|
28
|
-
await initProject();
|
|
29
|
-
} catch (error: any) {
|
|
30
|
-
console.error(`Error: ${error.message}`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Create command
|
|
36
|
-
program
|
|
37
|
-
.command('create <name>')
|
|
38
|
-
.description('Create a new migration file')
|
|
39
|
-
.option('-c, --config <path>', 'Path to config file')
|
|
40
|
-
.option('-t, --type <type>', 'Version bump type: major, minor, or patch (default: patch)')
|
|
41
|
-
.option('-e, --explicit <version>', 'Explicit version (e.g., 2.0.0)')
|
|
42
|
-
.action(async (name: string, options) => {
|
|
43
|
-
try {
|
|
44
|
-
let migrationsDir = './migrations';
|
|
45
|
-
|
|
46
|
-
// Try to load config to get migrations directory
|
|
47
|
-
try {
|
|
48
|
-
const config = await loadConfig(options.config);
|
|
49
|
-
migrationsDir = config.migrationsDir || './migrations';
|
|
50
|
-
} catch {
|
|
51
|
-
// If config doesn't exist, use default
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await createMigration(name, migrationsDir, options.type, options.explicit);
|
|
55
|
-
} catch (error: any) {
|
|
56
|
-
console.error(`Error: ${error.message}`);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Up command
|
|
62
|
-
program
|
|
63
|
-
.command('up')
|
|
64
|
-
.description('Run pending migrations')
|
|
65
|
-
.option('-c, --config <path>', 'Path to config file')
|
|
66
|
-
.option('-l, --limit <number>', 'Limit number of migrations to run', parsePositiveInt('limit'))
|
|
67
|
-
.option('-d, --dry-run', 'Show what would be done without making changes')
|
|
68
|
-
.action(async (options) => {
|
|
69
|
-
try {
|
|
70
|
-
const config = await loadConfig(options.config);
|
|
71
|
-
await runMigrations(config, options.limit, options.dryRun);
|
|
72
|
-
} catch (error: any) {
|
|
73
|
-
console.error(`Error: ${error.message}`);
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Down command
|
|
79
|
-
program
|
|
80
|
-
.command('down')
|
|
81
|
-
.description('Rollback migrations')
|
|
82
|
-
.option('-c, --config <path>', 'Path to config file')
|
|
83
|
-
.option('-s, --steps <number>', 'Number of migrations to rollback', parsePositiveInt('steps'), 1)
|
|
84
|
-
.option('-d, --dry-run', 'Show what would be done without making changes')
|
|
85
|
-
.action(async (options) => {
|
|
86
|
-
try {
|
|
87
|
-
const config = await loadConfig(options.config);
|
|
88
|
-
await rollbackMigrations(config, options.steps, options.dryRun);
|
|
89
|
-
} catch (error: any) {
|
|
90
|
-
console.error(`Error: ${error.message}`);
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// Status command
|
|
96
|
-
program
|
|
97
|
-
.command('status')
|
|
98
|
-
.description('Show migration status')
|
|
99
|
-
.option('-c, --config <path>', 'Path to config file')
|
|
100
|
-
.action(async (options) => {
|
|
101
|
-
try {
|
|
102
|
-
const config = await loadConfig(options.config);
|
|
103
|
-
await showStatus(config);
|
|
104
|
-
} catch (error: any) {
|
|
105
|
-
console.error(`Error: ${error.message}`);
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
program.parse(process.argv);
|
package/src/commands/create.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { MigrationLoader } from '../core/loader';
|
|
4
|
-
import { generateMigrationTemplate } from '../templates/migration';
|
|
5
|
-
|
|
6
|
-
type VersionBumpType = 'major' | 'minor' | 'patch';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Get next version based on bump type
|
|
10
|
-
*/
|
|
11
|
-
async function getNextVersion(loader: MigrationLoader, bumpType: VersionBumpType): Promise<string> {
|
|
12
|
-
const migrations = await loader['loadMigrations']();
|
|
13
|
-
|
|
14
|
-
const lastMigration = migrations.length > 0 ? migrations[migrations.length - 1] : null;
|
|
15
|
-
const lastVersion = lastMigration?.version;
|
|
16
|
-
const parts = lastVersion?.split('.');
|
|
17
|
-
|
|
18
|
-
if (!parts || parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) {
|
|
19
|
-
switch (bumpType) {
|
|
20
|
-
case 'major':
|
|
21
|
-
return '1.0.0';
|
|
22
|
-
case 'minor':
|
|
23
|
-
return '0.1.0';
|
|
24
|
-
case 'patch':
|
|
25
|
-
return '0.0.1';
|
|
26
|
-
default:
|
|
27
|
-
return '0.1.0';
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const major = parseInt(parts[0], 10);
|
|
32
|
-
const minor = parseInt(parts[1], 10);
|
|
33
|
-
const patch = parseInt(parts[2], 10);
|
|
34
|
-
|
|
35
|
-
switch (bumpType) {
|
|
36
|
-
case 'major':
|
|
37
|
-
return `${major + 1}.0.0`;
|
|
38
|
-
case 'minor':
|
|
39
|
-
return `${major}.${minor + 1}.0`;
|
|
40
|
-
case 'patch':
|
|
41
|
-
return `${major}.${minor}.${patch + 1}`;
|
|
42
|
-
default:
|
|
43
|
-
return `${major}.${minor}.${patch + 1}`;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function createMigration(
|
|
48
|
-
name: string,
|
|
49
|
-
migrationsDir: string = './migrations',
|
|
50
|
-
bumpType?: string,
|
|
51
|
-
explicitVersion?: string
|
|
52
|
-
): Promise<void> {
|
|
53
|
-
// Ensure migrations directory exists
|
|
54
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
55
|
-
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
56
|
-
console.log(`📁 Created migrations directory: ${migrationsDir}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Generate version
|
|
60
|
-
const loader = new MigrationLoader(migrationsDir);
|
|
61
|
-
let version: string;
|
|
62
|
-
|
|
63
|
-
if (explicitVersion) {
|
|
64
|
-
// Validate explicit version is semver
|
|
65
|
-
if (!/^\d+\.\d+\.\d+$/.test(explicitVersion)) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
`Invalid version format: ${explicitVersion}. Expected semver format (e.g., 1.0.0)`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
version = explicitVersion;
|
|
71
|
-
} else if (bumpType) {
|
|
72
|
-
// Validate bump type
|
|
73
|
-
const validTypes: VersionBumpType[] = ['major', 'minor', 'patch'];
|
|
74
|
-
if (!validTypes.includes(bumpType as VersionBumpType)) {
|
|
75
|
-
throw new Error(`Invalid bump type: ${bumpType}. Expected: major, minor, or patch`);
|
|
76
|
-
}
|
|
77
|
-
version = await getNextVersion(loader, bumpType as VersionBumpType);
|
|
78
|
-
} else {
|
|
79
|
-
// Default: increment patch
|
|
80
|
-
version = await getNextVersion(loader, 'patch');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Sanitize name (replace spaces and special chars with underscores)
|
|
84
|
-
const sanitizedName = name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
|
|
85
|
-
|
|
86
|
-
// Generate file name
|
|
87
|
-
const fileName = `${version}_${sanitizedName}.ts`;
|
|
88
|
-
const filePath = path.join(migrationsDir, fileName);
|
|
89
|
-
|
|
90
|
-
// Check if file already exists
|
|
91
|
-
if (fs.existsSync(filePath)) {
|
|
92
|
-
throw new Error(`Migration file already exists: ${filePath}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Generate migration content
|
|
96
|
-
const content = generateMigrationTemplate(version, sanitizedName);
|
|
97
|
-
|
|
98
|
-
// Write file
|
|
99
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
100
|
-
|
|
101
|
-
// Determine version type for informational message
|
|
102
|
-
const versionType = bumpType ? bumpType.toUpperCase() : explicitVersion ? 'EXPLICIT' : 'PATCH';
|
|
103
|
-
|
|
104
|
-
console.log(`\n✅ Created migration: ${fileName}`);
|
|
105
|
-
console.log(` Path: ${filePath}`);
|
|
106
|
-
console.log(` Version: ${version} (${versionType})\n`);
|
|
107
|
-
console.log(`Next steps:`);
|
|
108
|
-
console.log(` 1. Edit ${fileName}`);
|
|
109
|
-
console.log(` 2. Implement up() and down() functions`);
|
|
110
|
-
console.log(` 3. Run: dynatable-migrate up\n`);
|
|
111
|
-
}
|
package/src/commands/down.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { MigrationRunner } from '../core/runner';
|
|
2
|
-
import { createDynamoDBClient } from '../core/client';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
export async function rollbackMigrations(
|
|
6
|
-
config: MigrationConfig,
|
|
7
|
-
steps: number = 1,
|
|
8
|
-
dryRun: boolean = false
|
|
9
|
-
): Promise<void> {
|
|
10
|
-
const client = createDynamoDBClient(config);
|
|
11
|
-
const runner = new MigrationRunner(client, config);
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const rolledBack = await runner.down(steps, dryRun);
|
|
15
|
-
|
|
16
|
-
if (!dryRun && rolledBack.length > 0) {
|
|
17
|
-
console.log(`\n🎉 Successfully rolled back ${rolledBack.length} migration(s)`);
|
|
18
|
-
|
|
19
|
-
const currentVersion = await runner.getCurrentVersion();
|
|
20
|
-
console.log(`📌 Current version: ${currentVersion}\n`);
|
|
21
|
-
}
|
|
22
|
-
} catch (error: any) {
|
|
23
|
-
console.error(`\n❌ Rollback failed: ${error.message}\n`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { ConfigLoader } from '../core/config';
|
|
4
|
-
|
|
5
|
-
export async function initProject(): Promise<void> {
|
|
6
|
-
const cwd = process.cwd();
|
|
7
|
-
|
|
8
|
-
console.log('\n🚀 Initializing DynamoDB migrations\n');
|
|
9
|
-
|
|
10
|
-
// Create migrations directory
|
|
11
|
-
const migrationsDir = path.join(cwd, 'migrations');
|
|
12
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
13
|
-
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
14
|
-
console.log(`✅ Created: ${migrationsDir}/`);
|
|
15
|
-
|
|
16
|
-
// Create .gitkeep
|
|
17
|
-
fs.writeFileSync(path.join(migrationsDir, '.gitkeep'), '');
|
|
18
|
-
} else {
|
|
19
|
-
console.log(`⚠️ Directory already exists: ${migrationsDir}/`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Create config file
|
|
23
|
-
const configPath = path.join(cwd, 'dynatable.config.js');
|
|
24
|
-
if (!fs.existsSync(configPath)) {
|
|
25
|
-
ConfigLoader.createDefaultConfig(configPath);
|
|
26
|
-
} else {
|
|
27
|
-
console.log(`⚠️ Config already exists: ${configPath}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
console.log('\n✅ Initialization complete!\n');
|
|
31
|
-
console.log('Next steps:');
|
|
32
|
-
console.log(' 1. Edit dynatable.config.js with your DynamoDB settings');
|
|
33
|
-
console.log(' 2. Create your first migration:');
|
|
34
|
-
console.log(' dynatable-migrate create add_user_email');
|
|
35
|
-
console.log(' 3. Run migrations:');
|
|
36
|
-
console.log(' dynatable-migrate up\n');
|
|
37
|
-
}
|
package/src/commands/status.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { MigrationRunner } from '../core/runner';
|
|
2
|
-
import { createDynamoDBClient } from '../core/client';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
export async function showStatus(config: MigrationConfig): Promise<void> {
|
|
6
|
-
const client = createDynamoDBClient(config);
|
|
7
|
-
const runner = new MigrationRunner(client, config);
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const statuses = await runner.status();
|
|
11
|
-
const currentVersion = await runner.getCurrentVersion();
|
|
12
|
-
|
|
13
|
-
console.log(`\n📊 Migration Status\n`);
|
|
14
|
-
console.log(`Table: ${config.tableName}`);
|
|
15
|
-
console.log(`Current version: ${currentVersion || 'v0000 (no migrations)'}`);
|
|
16
|
-
console.log(`Migrations directory: ${config.migrationsDir || './migrations'}\n`);
|
|
17
|
-
|
|
18
|
-
if (statuses.length === 0) {
|
|
19
|
-
console.log('No migrations found.\n');
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Group by status
|
|
24
|
-
const pending = statuses.filter((s) => s.status === 'pending');
|
|
25
|
-
const applied = statuses.filter((s) => s.status === 'applied');
|
|
26
|
-
const failed = statuses.filter((s) => s.status === 'failed');
|
|
27
|
-
const rolledBack = statuses.filter((s) => s.status === 'rolled_back');
|
|
28
|
-
|
|
29
|
-
// Show applied migrations
|
|
30
|
-
if (applied.length > 0) {
|
|
31
|
-
console.log(`✅ Applied (${applied.length}):`);
|
|
32
|
-
for (const status of applied) {
|
|
33
|
-
const date = status.appliedAt ? new Date(status.appliedAt).toLocaleString() : '';
|
|
34
|
-
console.log(` ${status.version} - ${status.name} (${date})`);
|
|
35
|
-
}
|
|
36
|
-
console.log();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Show pending migrations
|
|
40
|
-
if (pending.length > 0) {
|
|
41
|
-
console.log(`⏳ Pending (${pending.length}):`);
|
|
42
|
-
for (const status of pending) {
|
|
43
|
-
console.log(` ${status.version} - ${status.name}`);
|
|
44
|
-
}
|
|
45
|
-
console.log();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Show rolled back migrations
|
|
49
|
-
if (rolledBack.length > 0) {
|
|
50
|
-
console.log(`⬅️ Rolled Back (${rolledBack.length}):`);
|
|
51
|
-
for (const status of rolledBack) {
|
|
52
|
-
console.log(` ${status.version} - ${status.name}`);
|
|
53
|
-
}
|
|
54
|
-
console.log();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Show failed migrations
|
|
58
|
-
if (failed.length > 0) {
|
|
59
|
-
console.log(`❌ Failed (${failed.length}):`);
|
|
60
|
-
for (const status of failed) {
|
|
61
|
-
console.log(` ${status.version} - ${status.name}`);
|
|
62
|
-
if (status.error) {
|
|
63
|
-
console.log(` Error: ${status.error}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
console.log();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Summary
|
|
70
|
-
console.log(`Total: ${statuses.length} migration(s)\n`);
|
|
71
|
-
} catch (error: any) {
|
|
72
|
-
console.error(`\n❌ Failed to get status: ${error.message}\n`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/commands/up.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { MigrationRunner } from '../core/runner';
|
|
2
|
-
import { createDynamoDBClient } from '../core/client';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
export async function runMigrations(
|
|
6
|
-
config: MigrationConfig,
|
|
7
|
-
limit?: number,
|
|
8
|
-
dryRun: boolean = false
|
|
9
|
-
): Promise<void> {
|
|
10
|
-
const client = createDynamoDBClient(config);
|
|
11
|
-
const runner = new MigrationRunner(client, config);
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const executed = await runner.up({ limit, dryRun });
|
|
15
|
-
|
|
16
|
-
if (!dryRun && executed.length > 0) {
|
|
17
|
-
console.log(`\n🎉 Successfully applied ${executed.length} migration(s)`);
|
|
18
|
-
|
|
19
|
-
const currentVersion = await runner.getCurrentVersion();
|
|
20
|
-
console.log(`📌 Current version: ${currentVersion}\n`);
|
|
21
|
-
}
|
|
22
|
-
} catch (error: any) {
|
|
23
|
-
console.error(`\n❌ Migration failed: ${error.message}\n`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
}
|
package/src/core/client.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
-
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Create a DynamoDB Document Client from config
|
|
7
|
-
*/
|
|
8
|
-
export function createDynamoDBClient(config: MigrationConfig): DynamoDBDocumentClient {
|
|
9
|
-
const ddbClient = new DynamoDBClient({
|
|
10
|
-
region: config.client.region,
|
|
11
|
-
endpoint: config.client.endpoint,
|
|
12
|
-
credentials: config.client.credentials,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
return DynamoDBDocumentClient.from(ddbClient, {
|
|
16
|
-
marshallOptions: {
|
|
17
|
-
removeUndefinedValues: true,
|
|
18
|
-
convertClassInstanceToMap: true,
|
|
19
|
-
},
|
|
20
|
-
unmarshallOptions: {
|
|
21
|
-
wrapNumbers: false,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
}
|