forge-sql-orm-cli 1.0.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/README.md +202 -0
- package/dist-cli/actions/generate-models.d.ts +19 -0
- package/dist-cli/actions/migrations-create.d.ts +46 -0
- package/dist-cli/actions/migrations-drops.d.ts +6 -0
- package/dist-cli/actions/migrations-update.d.ts +6 -0
- package/dist-cli/cli.d.ts +3 -0
- package/dist-cli/cli.js +862 -0
- package/dist-cli/cli.js.map +1 -0
- package/dist-cli/cli.mjs +862 -0
- package/dist-cli/cli.mjs.map +1 -0
- package/package.json +64 -0
- package/src/.env +7 -0
- package/src/actions/generate-models.ts +267 -0
- package/src/actions/migrations-create.ts +213 -0
- package/src/actions/migrations-drops.ts +139 -0
- package/src/actions/migrations-update.ts +659 -0
- package/src/cli.ts +302 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for migration creation
|
|
9
|
+
*/
|
|
10
|
+
export interface CreateMigrationOptions {
|
|
11
|
+
output: string;
|
|
12
|
+
entitiesPath: string;
|
|
13
|
+
force?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Loads the current migration version from `migrationCount.ts`.
|
|
18
|
+
* @param migrationPath - Path to the migration folder.
|
|
19
|
+
* @returns The latest migration version.
|
|
20
|
+
*/
|
|
21
|
+
export const loadMigrationVersion = async (migrationPath: string): Promise<number> => {
|
|
22
|
+
try {
|
|
23
|
+
const migrationCountFilePath = path.resolve(path.join(migrationPath, "migrationCount.ts"));
|
|
24
|
+
if (!fs.existsSync(migrationCountFilePath)) {
|
|
25
|
+
console.log(`✅ Current migration version: 0`);
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { MIGRATION_VERSION } = await import(migrationCountFilePath);
|
|
30
|
+
console.log(`✅ Current migration version: ${MIGRATION_VERSION}`);
|
|
31
|
+
return MIGRATION_VERSION as number;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`❌ Error loading migrationCount:`, error);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Cleans SQL statements by removing unnecessary database options.
|
|
40
|
+
* @param sql - The raw SQL statement.
|
|
41
|
+
* @returns The cleaned SQL statement.
|
|
42
|
+
*/
|
|
43
|
+
export function cleanSQLStatement(sql: string): string {
|
|
44
|
+
// Add IF NOT EXISTS to CREATE TABLE statements
|
|
45
|
+
sql = sql.replace(/create\s+table\s+(\w+)/gi, "create table if not exists $1");
|
|
46
|
+
|
|
47
|
+
// Add IF NOT EXISTS to CREATE INDEX statements
|
|
48
|
+
sql = sql.replace(/create\s+index\s+(\w+)/gi, "create index if not exists $1");
|
|
49
|
+
|
|
50
|
+
// Add IF NOT EXISTS to ADD INDEX statements
|
|
51
|
+
sql = sql.replace(
|
|
52
|
+
/alter\s+table\s+(\w+)\s+add\s+index\s+(\w+)/gi,
|
|
53
|
+
"alter table $1 add index if not exists $2",
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Add IF NOT EXISTS to ADD CONSTRAINT statements
|
|
57
|
+
sql = sql.replace(
|
|
58
|
+
/alter\s+table\s+(\w+)\s+add\s+constraint\s+(\w+)/gi,
|
|
59
|
+
"alter table $1 add constraint if not exists $2",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Remove unnecessary database options
|
|
63
|
+
return sql.replace(/\s+default\s+character\s+set\s+utf8mb4\s+engine\s*=\s*InnoDB;?/gi, "").trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generates a migration file using the provided SQL statements.
|
|
68
|
+
* @param createStatements - Array of SQL statements.
|
|
69
|
+
* @param version - Migration version number.
|
|
70
|
+
* @returns TypeScript migration file content.
|
|
71
|
+
*/
|
|
72
|
+
export function generateMigrationFile(createStatements: string[], version: number): string {
|
|
73
|
+
const versionPrefix = `v${version}_MIGRATION`;
|
|
74
|
+
|
|
75
|
+
// Clean each SQL statement and generate migration lines with .enqueue()
|
|
76
|
+
const migrationLines = createStatements
|
|
77
|
+
.map(
|
|
78
|
+
(stmt, index) =>
|
|
79
|
+
` .enqueue("${versionPrefix}${index}", "${cleanSQLStatement(stmt).replace(/\s+/g, " ")}")`,
|
|
80
|
+
)
|
|
81
|
+
.join("\n");
|
|
82
|
+
|
|
83
|
+
// Migration template
|
|
84
|
+
return `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
85
|
+
|
|
86
|
+
export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
87
|
+
return migrationRunner
|
|
88
|
+
${migrationLines};
|
|
89
|
+
};`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Saves the generated migration file along with `migrationCount.ts` and `index.ts`.
|
|
94
|
+
* @param migrationCode - The migration code to be written to the file.
|
|
95
|
+
* @param version - Migration version number.
|
|
96
|
+
* @param outputDir - Directory where the migration files will be saved.
|
|
97
|
+
*/
|
|
98
|
+
export function saveMigrationFiles(migrationCode: string, version: number, outputDir: string) {
|
|
99
|
+
if (!fs.existsSync(outputDir)) {
|
|
100
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const migrationFilePath = path.join(outputDir, `migrationV${version}.ts`);
|
|
104
|
+
const migrationCountPath = path.join(outputDir, `migrationCount.ts`);
|
|
105
|
+
const indexFilePath = path.join(outputDir, `index.ts`);
|
|
106
|
+
|
|
107
|
+
// Write the migration file
|
|
108
|
+
fs.writeFileSync(migrationFilePath, migrationCode);
|
|
109
|
+
|
|
110
|
+
// Write the migration count file
|
|
111
|
+
fs.writeFileSync(migrationCountPath, `export const MIGRATION_VERSION = ${version};`);
|
|
112
|
+
|
|
113
|
+
// Generate the migration index file
|
|
114
|
+
const indexFileContent = `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
115
|
+
import { MIGRATION_VERSION } from "./migrationCount";
|
|
116
|
+
|
|
117
|
+
export type MigrationType = (
|
|
118
|
+
migrationRunner: MigrationRunner,
|
|
119
|
+
) => MigrationRunner;
|
|
120
|
+
|
|
121
|
+
export default async (
|
|
122
|
+
migrationRunner: MigrationRunner,
|
|
123
|
+
): Promise<MigrationRunner> => {
|
|
124
|
+
for (let i = 1; i <= MIGRATION_VERSION; i++) {
|
|
125
|
+
const migrations = (await import(\`./migrationV\${i}\`)) as {
|
|
126
|
+
default: MigrationType;
|
|
127
|
+
};
|
|
128
|
+
migrations.default(migrationRunner);
|
|
129
|
+
}
|
|
130
|
+
return migrationRunner;
|
|
131
|
+
};`;
|
|
132
|
+
|
|
133
|
+
fs.writeFileSync(indexFilePath, indexFileContent);
|
|
134
|
+
|
|
135
|
+
console.log(`✅ Migration file created: ${migrationFilePath}`);
|
|
136
|
+
console.log(`✅ Migration count file updated: ${migrationCountPath}`);
|
|
137
|
+
console.log(`✅ Migration index file created: ${indexFilePath}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Extracts only the relevant SQL statements for migration.
|
|
142
|
+
* @param schema - The full database schema as SQL.
|
|
143
|
+
* @returns Filtered list of SQL statements.
|
|
144
|
+
*/
|
|
145
|
+
export const extractCreateStatements = (schema: string): string[] => {
|
|
146
|
+
// Split by statement-breakpoint and semicolon
|
|
147
|
+
const statements = schema
|
|
148
|
+
.split(/--> statement-breakpoint|;/)
|
|
149
|
+
.map((s) => s.trim())
|
|
150
|
+
.filter((s) => s.length > 0);
|
|
151
|
+
|
|
152
|
+
return statements.filter(
|
|
153
|
+
(stmt) =>
|
|
154
|
+
stmt.toLowerCase().startsWith("create table") ||
|
|
155
|
+
stmt.toLowerCase().startsWith("alter table") ||
|
|
156
|
+
stmt.toLowerCase().includes("add index") ||
|
|
157
|
+
stmt.toLowerCase().includes("create index") ||
|
|
158
|
+
stmt.toLowerCase().includes("add unique index") ||
|
|
159
|
+
stmt.toLowerCase().includes("add constraint"),
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Creates a full database migration.
|
|
165
|
+
* @param options - Database connection settings and output paths.
|
|
166
|
+
*/
|
|
167
|
+
export const createMigration = async (options: CreateMigrationOptions) => {
|
|
168
|
+
try {
|
|
169
|
+
let version = await loadMigrationVersion(options.output);
|
|
170
|
+
|
|
171
|
+
if (version > 0) {
|
|
172
|
+
if (options.force) {
|
|
173
|
+
console.warn(
|
|
174
|
+
`⚠️ Warning: Migration already exists. Creating new migration with force flag...`,
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
console.error(
|
|
178
|
+
`❌ Error: Migration has already been created. Use --force flag to override.`,
|
|
179
|
+
);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Start from version 1 if no previous migrations exist
|
|
185
|
+
version = 1;
|
|
186
|
+
// Generate SQL using drizzle-kit
|
|
187
|
+
await execSync(
|
|
188
|
+
`npx drizzle-kit generate --name=init --dialect mysql --out ${options.output} --schema ${options.entitiesPath}`,
|
|
189
|
+
{ encoding: "utf-8" },
|
|
190
|
+
);
|
|
191
|
+
const initSqlFile = path.join(options.output, "0000_init.sql");
|
|
192
|
+
const sql = fs.readFileSync(initSqlFile, "utf-8");
|
|
193
|
+
|
|
194
|
+
// Extract and clean statements
|
|
195
|
+
const createStatements = extractCreateStatements(sql);
|
|
196
|
+
|
|
197
|
+
// Generate and save migration files
|
|
198
|
+
const migrationFile = generateMigrationFile(createStatements, 1);
|
|
199
|
+
saveMigrationFiles(migrationFile, 1, options.output);
|
|
200
|
+
|
|
201
|
+
fs.rmSync(initSqlFile, { force: true });
|
|
202
|
+
console.log(`✅ Removed SQL file: ${initSqlFile}`);
|
|
203
|
+
// Remove meta directory after processing
|
|
204
|
+
let metaDir = path.join(options.output, "meta");
|
|
205
|
+
fs.rmSync(metaDir, { recursive: true, force: true });
|
|
206
|
+
console.log(`✅ Removed: ${metaDir}`);
|
|
207
|
+
console.log(`✅ Migration successfully created!`);
|
|
208
|
+
process.exit(0);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error(`❌ Error during migration creation:`, error);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { MySqlTable, TableConfig } from "drizzle-orm/mysql-core";
|
|
5
|
+
import { getTableMetadata, generateDropTableStatements } from "forge-sql-orm";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generates a migration ID using current date
|
|
9
|
+
* @returns Migration ID string with current date
|
|
10
|
+
*/
|
|
11
|
+
function generateMigrationUUID(version: number): string {
|
|
12
|
+
const now = new Date();
|
|
13
|
+
const timestamp = now.getTime();
|
|
14
|
+
return `MIGRATION_V${version}_${timestamp}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generates a migration file using the provided SQL statements.
|
|
19
|
+
* @param createStatements - Array of SQL statements.
|
|
20
|
+
* @param version - Migration version number.
|
|
21
|
+
* @returns TypeScript migration file content.
|
|
22
|
+
*/
|
|
23
|
+
function generateMigrationFile(createStatements: string[], version: number): string {
|
|
24
|
+
const uniqId = generateMigrationUUID(version);
|
|
25
|
+
// Clean each SQL statement and generate migration lines with .enqueue()
|
|
26
|
+
const migrationLines = createStatements
|
|
27
|
+
.map(
|
|
28
|
+
(stmt, index) => ` .enqueue("${uniqId}_${index}", \"${stmt}\")`, // eslint-disable-line no-useless-escape
|
|
29
|
+
)
|
|
30
|
+
.join("\n");
|
|
31
|
+
|
|
32
|
+
// Migration template
|
|
33
|
+
return `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
34
|
+
|
|
35
|
+
export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
36
|
+
return migrationRunner
|
|
37
|
+
${migrationLines};
|
|
38
|
+
};`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Saves the generated migration file along with `migrationCount.ts` and `index.ts`.
|
|
43
|
+
* @param migrationCode - The migration code to be written to the file.
|
|
44
|
+
* @param version - Migration version number.
|
|
45
|
+
* @param outputDir - Directory where the migration files will be saved.
|
|
46
|
+
*/
|
|
47
|
+
function saveMigrationFiles(migrationCode: string, version: number, outputDir: string) {
|
|
48
|
+
if (!fs.existsSync(outputDir)) {
|
|
49
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const migrationFilePath = path.join(outputDir, `migrationV${version}.ts`);
|
|
53
|
+
const migrationCountPath = path.join(outputDir, `migrationCount.ts`);
|
|
54
|
+
const indexFilePath = path.join(outputDir, `index.ts`);
|
|
55
|
+
|
|
56
|
+
// Write the migration file
|
|
57
|
+
fs.writeFileSync(migrationFilePath, migrationCode);
|
|
58
|
+
|
|
59
|
+
// Write the migration count file
|
|
60
|
+
fs.writeFileSync(migrationCountPath, `export const MIGRATION_VERSION = ${version};`);
|
|
61
|
+
|
|
62
|
+
// Generate the migration index file
|
|
63
|
+
const indexFileContent = `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
64
|
+
import { MIGRATION_VERSION } from "./migrationCount";
|
|
65
|
+
|
|
66
|
+
export type MigrationType = (
|
|
67
|
+
migrationRunner: MigrationRunner,
|
|
68
|
+
) => MigrationRunner;
|
|
69
|
+
|
|
70
|
+
export default async (
|
|
71
|
+
migrationRunner: MigrationRunner,
|
|
72
|
+
): Promise<MigrationRunner> => {
|
|
73
|
+
for (let i = 1; i <= MIGRATION_VERSION; i++) {
|
|
74
|
+
const migrations = (await import(\`./migrationV\${i}\`)) as {
|
|
75
|
+
default: MigrationType;
|
|
76
|
+
};
|
|
77
|
+
migrations.default(migrationRunner);
|
|
78
|
+
}
|
|
79
|
+
return migrationRunner;
|
|
80
|
+
};`;
|
|
81
|
+
|
|
82
|
+
fs.writeFileSync(indexFilePath, indexFileContent);
|
|
83
|
+
|
|
84
|
+
console.log(`✅ Migration file created: ${migrationFilePath}`);
|
|
85
|
+
console.log(`✅ Migration count file updated: ${migrationCountPath}`);
|
|
86
|
+
console.log(`✅ Migration index file created: ${indexFilePath}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates a full database migration.
|
|
91
|
+
* @param options - Database connection settings and output paths.
|
|
92
|
+
*/
|
|
93
|
+
export const dropMigration = async (options: any) => {
|
|
94
|
+
try {
|
|
95
|
+
// Start from version 1 if no previous migrations exist
|
|
96
|
+
const version = 1;
|
|
97
|
+
|
|
98
|
+
// Import Drizzle schema using absolute path
|
|
99
|
+
const schemaPath = path.resolve(options.entitiesPath, "schema.ts");
|
|
100
|
+
if (!fs.existsSync(schemaPath)) {
|
|
101
|
+
throw new Error(`Schema file not found at: ${schemaPath}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const schemaModule = await import(schemaPath);
|
|
105
|
+
if (!schemaModule) {
|
|
106
|
+
throw new Error(`Invalid schema file at: ${schemaPath}. Schema must export tables.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Get all exports that are tables
|
|
110
|
+
const tables = Object.values(schemaModule) as MySqlTable<TableConfig>[];
|
|
111
|
+
|
|
112
|
+
if (tables.length === 0) {
|
|
113
|
+
throw new Error(`No valid tables found in schema at: ${schemaPath}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Get table names for logging
|
|
117
|
+
const tableNames = tables
|
|
118
|
+
.map((table) => {
|
|
119
|
+
const metadata = getTableMetadata(table);
|
|
120
|
+
return metadata.tableName;
|
|
121
|
+
})
|
|
122
|
+
.filter(Boolean);
|
|
123
|
+
|
|
124
|
+
console.log("Found tables:", tableNames);
|
|
125
|
+
|
|
126
|
+
// Generate drop statements
|
|
127
|
+
const dropStatements = generateDropTableStatements(tables);
|
|
128
|
+
|
|
129
|
+
// Generate and save migration files
|
|
130
|
+
const migrationFile = generateMigrationFile(dropStatements, version);
|
|
131
|
+
saveMigrationFiles(migrationFile, version, options.output);
|
|
132
|
+
|
|
133
|
+
console.log(`✅ Migration successfully created!`);
|
|
134
|
+
process.exit(0);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(`❌ Error during migration creation:`, error);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
};
|