@conorroberts/utils 0.0.99 → 0.0.102
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/dist/db/migrate.d.mts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
+
import { AnyMySqlTable } from "drizzle-orm/mysql-core";
|
|
2
|
+
|
|
1
3
|
//#region src/db/migrate.d.ts
|
|
2
|
-
|
|
4
|
+
type DrizzleSchema = Record<string, AnyMySqlTable>;
|
|
5
|
+
type SchemaTableName<TSchema extends DrizzleSchema> = TSchema[keyof TSchema]["_"]["name"];
|
|
6
|
+
interface MigrateConnectionOptions {
|
|
3
7
|
migrationsFolder: string;
|
|
4
8
|
host: string;
|
|
5
9
|
username: string;
|
|
6
10
|
password: string;
|
|
7
11
|
}
|
|
8
|
-
|
|
12
|
+
interface MigrateSharedOptions<TSchema extends DrizzleSchema> extends MigrateConnectionOptions {
|
|
13
|
+
deleteOrder?: SchemaTableName<TSchema>[];
|
|
14
|
+
}
|
|
15
|
+
type CleanMigrateOptions<TSchema extends DrizzleSchema> = MigrateSharedOptions<TSchema> & {
|
|
16
|
+
clean: true;
|
|
17
|
+
schema: TSchema;
|
|
18
|
+
};
|
|
19
|
+
type StandardMigrateOptions<TSchema extends DrizzleSchema> = MigrateSharedOptions<TSchema> & {
|
|
20
|
+
clean?: false;
|
|
21
|
+
schema?: TSchema;
|
|
22
|
+
};
|
|
23
|
+
type MigrateOptions<TSchema extends DrizzleSchema = DrizzleSchema> = CleanMigrateOptions<TSchema> | StandardMigrateOptions<TSchema>;
|
|
24
|
+
declare const runMigrations: <TSchema extends DrizzleSchema>(options: MigrateOptions<TSchema>) => Promise<void>;
|
|
9
25
|
//#endregion
|
|
10
|
-
export { type MigrateOptions,
|
|
26
|
+
export { type DrizzleSchema, type MigrateOptions, type SchemaTableName, runMigrations };
|
|
11
27
|
//# sourceMappingURL=migrate.d.mts.map
|
package/dist/db/migrate.mjs
CHANGED
|
@@ -1,19 +1,117 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
1
|
import { consola } from "consola";
|
|
2
|
+
import { DrizzleQueryError } from "drizzle-orm/errors";
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
3
4
|
import { drizzle } from "drizzle-orm/planetscale-serverless";
|
|
4
5
|
import { migrate } from "drizzle-orm/planetscale-serverless/migrator";
|
|
6
|
+
import { getTableName } from "drizzle-orm/table";
|
|
7
|
+
import { resolve } from "node:path";
|
|
5
8
|
|
|
6
9
|
//#region src/db/migrate.ts
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
const escapeIdentifier = (value) => {
|
|
11
|
+
return `\`${value.replaceAll("`", "``")}\``;
|
|
12
|
+
};
|
|
13
|
+
const parseError = (error) => {
|
|
14
|
+
if (error instanceof DrizzleQueryError) {
|
|
15
|
+
if (error.cause instanceof Error) return error.cause.message;
|
|
16
|
+
return error.message;
|
|
17
|
+
}
|
|
18
|
+
if (error instanceof Error) return error.message;
|
|
19
|
+
return String(error);
|
|
20
|
+
};
|
|
21
|
+
const buildDeleteOrder = (tableNames, preferredOrder) => {
|
|
22
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23
|
+
const ordered = [];
|
|
24
|
+
for (const tableName of preferredOrder) {
|
|
25
|
+
if (seen.has(tableName)) continue;
|
|
26
|
+
seen.add(tableName);
|
|
27
|
+
if (tableNames.includes(tableName)) ordered.push(tableName);
|
|
28
|
+
}
|
|
29
|
+
for (const tableName of tableNames) if (!seen.has(tableName)) ordered.push(tableName);
|
|
30
|
+
return ordered;
|
|
31
|
+
};
|
|
32
|
+
const listSchemaTables = (schema) => {
|
|
33
|
+
const tableNames = /* @__PURE__ */ new Set();
|
|
34
|
+
const tables = Object.values(schema);
|
|
35
|
+
for (const table of tables) {
|
|
36
|
+
const tableName = getTableName(table);
|
|
37
|
+
tableNames.add(tableName);
|
|
38
|
+
}
|
|
39
|
+
return Array.from(tableNames);
|
|
40
|
+
};
|
|
41
|
+
const deleteTableRows = async (db, tableNames) => {
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const tableName of tableNames) try {
|
|
44
|
+
const rowsDeleted = await db.$count(sql.raw(tableName));
|
|
45
|
+
await db.execute(sql.raw(`DELETE FROM ${tableName}`));
|
|
46
|
+
results.push({
|
|
47
|
+
table: tableName,
|
|
48
|
+
rowsDeleted,
|
|
49
|
+
success: true
|
|
50
|
+
});
|
|
51
|
+
const status = rowsDeleted > 0 ? `[OK] Deleted ${rowsDeleted} rows` : "[SKIP] Empty";
|
|
52
|
+
consola.log(`${status.padEnd(30)} from ${tableName}`);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const errorDetails = parseError(error) || String(error);
|
|
55
|
+
results.push({
|
|
56
|
+
table: tableName,
|
|
57
|
+
rowsDeleted: 0,
|
|
58
|
+
success: false,
|
|
59
|
+
error: errorDetails
|
|
60
|
+
});
|
|
61
|
+
consola.error(`[FAIL] Failed to delete from ${tableName}: ${errorDetails}`);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
};
|
|
66
|
+
const dropTables = async (db, tableNames) => {
|
|
67
|
+
for (const tableName of tableNames) try {
|
|
68
|
+
await db.execute(sql.raw(`DROP TABLE IF EXISTS ${escapeIdentifier(tableName)}`));
|
|
69
|
+
consola.log(`[OK] Dropped table ${tableName}`);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const errorDetails = parseError(error) || String(error);
|
|
72
|
+
consola.error(`[FAIL] Failed to drop ${tableName}: ${errorDetails}`);
|
|
73
|
+
throw new Error(`Cannot proceed: failed to drop ${tableName}`);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const cleanDatabase = async (db, options) => {
|
|
77
|
+
if (process.env.UNSAFE_CONFIRM_DELETE !== "true") {
|
|
78
|
+
consola.error("Refusing to clean database. Set UNSAFE_CONFIRM_DELETE=\"true\" to continue.");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
const tableNames = listSchemaTables(options.schema);
|
|
82
|
+
if (tableNames.length === 0) {
|
|
83
|
+
consola.log("Clean requested, but no tables were found in the provided Drizzle schema.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const ordered = buildDeleteOrder(tableNames, options.deleteOrder ?? []);
|
|
87
|
+
consola.warn("WARNING: This will delete and drop ALL tables from the database.");
|
|
88
|
+
consola.log(`Database host: ${db.$client.config.host}`);
|
|
89
|
+
consola.log("");
|
|
90
|
+
consola.log("Deleting table rows in order...\n");
|
|
91
|
+
const results = await deleteTableRows(db, ordered);
|
|
92
|
+
consola.log("\nDropping tables in order...\n");
|
|
93
|
+
await dropTables(db, ordered);
|
|
94
|
+
const successCount = results.filter((result) => result.success).length;
|
|
95
|
+
const totalRowsDeleted = results.reduce((sum, result) => sum + result.rowsDeleted, 0);
|
|
96
|
+
consola.log("");
|
|
97
|
+
consola.log("=".repeat(60));
|
|
98
|
+
consola.log("SUMMARY");
|
|
99
|
+
consola.log("=".repeat(60));
|
|
100
|
+
consola.log(`Deletion phase: ${successCount} tables processed, ${totalRowsDeleted} rows deleted`);
|
|
101
|
+
consola.success("Database is now empty and ready for migrations.");
|
|
102
|
+
};
|
|
103
|
+
const runMigrations = async (options) => {
|
|
104
|
+
const db = drizzle({ connection: {
|
|
10
105
|
host: options.host,
|
|
11
106
|
username: options.username,
|
|
12
107
|
password: options.password
|
|
13
|
-
} })
|
|
108
|
+
} });
|
|
109
|
+
if (options.clean) await cleanDatabase(db, options);
|
|
110
|
+
consola.log("Running database migrations...");
|
|
111
|
+
await migrate(db, { migrationsFolder: resolve(options.migrationsFolder) });
|
|
14
112
|
consola.log("[OK] Migrations completed successfully!");
|
|
15
113
|
};
|
|
16
114
|
|
|
17
115
|
//#endregion
|
|
18
|
-
export {
|
|
116
|
+
export { runMigrations };
|
|
19
117
|
//# sourceMappingURL=migrate.mjs.map
|
package/dist/db/migrate.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.mjs","names":[],"sources":["../../src/db/migrate.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"migrate.mjs","names":["ordered: TTableName[]","tableNames: Set<SchemaTableName<TSchema>>","results: DeleteResult[]"],"sources":["../../src/db/migrate.ts"],"sourcesContent":["import { consola } from \"consola\";\nimport { DrizzleQueryError } from \"drizzle-orm/errors\";\nimport { sql } from \"drizzle-orm\";\nimport type { AnyMySqlTable } from \"drizzle-orm/mysql-core\";\nimport { drizzle } from \"drizzle-orm/planetscale-serverless\";\nimport { migrate } from \"drizzle-orm/planetscale-serverless/migrator\";\nimport { getTableName } from \"drizzle-orm/table\";\nimport { resolve } from \"node:path\";\n\ntype DrizzleSchema = Record<string, AnyMySqlTable>;\n\ntype SchemaTableName<TSchema extends DrizzleSchema> = TSchema[keyof TSchema][\"_\"][\"name\"];\n\ninterface MigrateConnectionOptions {\n migrationsFolder: string;\n host: string;\n username: string;\n password: string;\n}\n\ninterface MigrateSharedOptions<TSchema extends DrizzleSchema> extends MigrateConnectionOptions {\n deleteOrder?: SchemaTableName<TSchema>[];\n}\n\ntype CleanMigrateOptions<TSchema extends DrizzleSchema> = MigrateSharedOptions<TSchema> & {\n clean: true;\n schema: TSchema;\n};\n\ntype StandardMigrateOptions<TSchema extends DrizzleSchema> = MigrateSharedOptions<TSchema> & {\n clean?: false;\n schema?: TSchema;\n};\n\ntype MigrateOptions<TSchema extends DrizzleSchema = DrizzleSchema> =\n | CleanMigrateOptions<TSchema>\n | StandardMigrateOptions<TSchema>;\n\ninterface DeleteResult {\n table: string;\n rowsDeleted: number;\n success: boolean;\n error?: string;\n}\n\nconst escapeIdentifier = (value: string): string => {\n return `\\`${value.replaceAll(\"`\", \"``\")}\\``;\n};\n\nconst parseError = (error: unknown): string => {\n if (error instanceof DrizzleQueryError) {\n if (error.cause instanceof Error) {\n return error.cause.message;\n }\n\n return error.message;\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n};\n\nconst buildDeleteOrder = <TTableName extends string>(tableNames: TTableName[], preferredOrder: TTableName[]) => {\n const seen = new Set<string>();\n const ordered: TTableName[] = [];\n\n for (const tableName of preferredOrder) {\n if (seen.has(tableName)) {\n continue;\n }\n\n seen.add(tableName);\n\n if (tableNames.includes(tableName)) {\n ordered.push(tableName);\n }\n }\n\n for (const tableName of tableNames) {\n if (!seen.has(tableName)) {\n ordered.push(tableName);\n }\n }\n\n return ordered;\n};\n\nconst listSchemaTables = <TSchema extends DrizzleSchema>(schema: TSchema): SchemaTableName<TSchema>[] => {\n const tableNames: Set<SchemaTableName<TSchema>> = new Set();\n const tables = Object.values(schema) as TSchema[keyof TSchema][];\n\n for (const table of tables) {\n const tableName = getTableName(table) as SchemaTableName<TSchema>;\n\n tableNames.add(tableName);\n }\n\n return Array.from(tableNames);\n};\n\nconst deleteTableRows = async (db: ReturnType<typeof drizzle>, tableNames: string[]): Promise<DeleteResult[]> => {\n const results: DeleteResult[] = [];\n\n for (const tableName of tableNames) {\n try {\n const rowsDeleted = await db.$count(sql.raw(tableName));\n\n await db.execute(sql.raw(`DELETE FROM ${tableName}`));\n\n results.push({\n table: tableName,\n rowsDeleted,\n success: true,\n });\n\n const status = rowsDeleted > 0 ? `[OK] Deleted ${rowsDeleted} rows` : \"[SKIP] Empty\";\n consola.log(`${status.padEnd(30)} from ${tableName}`);\n } catch (error) {\n const errorDetails = parseError(error) || String(error);\n\n results.push({\n table: tableName,\n rowsDeleted: 0,\n success: false,\n error: errorDetails,\n });\n\n consola.error(`[FAIL] Failed to delete from ${tableName}: ${errorDetails}`);\n throw error;\n }\n }\n\n return results;\n};\n\nconst dropTables = async (db: ReturnType<typeof drizzle>, tableNames: string[]): Promise<void> => {\n for (const tableName of tableNames) {\n try {\n await db.execute(sql.raw(`DROP TABLE IF EXISTS ${escapeIdentifier(tableName)}`));\n consola.log(`[OK] Dropped table ${tableName}`);\n } catch (error) {\n const errorDetails = parseError(error) || String(error);\n consola.error(`[FAIL] Failed to drop ${tableName}: ${errorDetails}`);\n throw new Error(`Cannot proceed: failed to drop ${tableName}`);\n }\n }\n};\n\nconst cleanDatabase = async (\n db: ReturnType<typeof drizzle>,\n options: CleanMigrateOptions<DrizzleSchema>,\n): Promise<void> => {\n if (process.env.UNSAFE_CONFIRM_DELETE !== \"true\") {\n consola.error('Refusing to clean database. Set UNSAFE_CONFIRM_DELETE=\"true\" to continue.');\n process.exit(1);\n }\n\n const tableNames = listSchemaTables(options.schema);\n\n if (tableNames.length === 0) {\n consola.log(\"Clean requested, but no tables were found in the provided Drizzle schema.\");\n return;\n }\n\n const ordered = buildDeleteOrder(tableNames, options.deleteOrder ?? []);\n\n consola.warn(\"WARNING: This will delete and drop ALL tables from the database.\");\n consola.log(`Database host: ${db.$client.config.host}`);\n\n consola.log(\"\");\n consola.log(\"Deleting table rows in order...\\n\");\n\n const results = await deleteTableRows(db, ordered);\n\n consola.log(\"\\nDropping tables in order...\\n\");\n await dropTables(db, ordered);\n\n const successCount = results.filter((result) => result.success).length;\n const totalRowsDeleted = results.reduce((sum, result) => sum + result.rowsDeleted, 0);\n\n consola.log(\"\");\n consola.log(\"=\".repeat(60));\n consola.log(\"SUMMARY\");\n consola.log(\"=\".repeat(60));\n consola.log(`Deletion phase: ${successCount} tables processed, ${totalRowsDeleted} rows deleted`);\n consola.success(\"Database is now empty and ready for migrations.\");\n};\n\nconst runMigrations = async <TSchema extends DrizzleSchema>(options: MigrateOptions<TSchema>) => {\n const db = drizzle({\n connection: {\n host: options.host,\n username: options.username,\n password: options.password,\n },\n });\n\n if (options.clean) {\n await cleanDatabase(db, options);\n }\n\n consola.log(\"Running database migrations...\");\n await migrate(db, { migrationsFolder: resolve(options.migrationsFolder) });\n consola.log(\"[OK] Migrations completed successfully!\");\n};\n\nexport { runMigrations };\nexport type { DrizzleSchema, MigrateOptions, SchemaTableName };\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { join, resolve } from "node:path";
|
|
2
1
|
import { consola } from "consola";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
|
|
5
5
|
//#region src/db/validateSchema.ts
|
|
@@ -36,9 +36,13 @@ const getLatestSnapshot = (metaDir) => {
|
|
|
36
36
|
return JSON.parse(readFileSync(snapshotPath, "utf-8"));
|
|
37
37
|
};
|
|
38
38
|
const validateDatabaseSchema = (options) => {
|
|
39
|
-
const { metaDir, verbose = false } = options;
|
|
39
|
+
const { metaDir, verbose = false, exitOnComplete = false } = options;
|
|
40
40
|
const issues = [];
|
|
41
41
|
const allConstraints = [];
|
|
42
|
+
const finalize = (exitCode) => {
|
|
43
|
+
if (exitOnComplete) process.exit(exitCode);
|
|
44
|
+
return exitCode;
|
|
45
|
+
};
|
|
42
46
|
const snapshot = getLatestSnapshot(metaDir);
|
|
43
47
|
const tables = Object.values(snapshot.tables);
|
|
44
48
|
tables.forEach((table) => {
|
|
@@ -157,7 +161,7 @@ const validateDatabaseSchema = (options) => {
|
|
|
157
161
|
consola.log("");
|
|
158
162
|
if (issues.length === 0) {
|
|
159
163
|
consola.success("No name length issues found!");
|
|
160
|
-
return 0;
|
|
164
|
+
return finalize(0);
|
|
161
165
|
}
|
|
162
166
|
consola.warn(`Found ${issues.length} name length issue(s):`);
|
|
163
167
|
consola.log("");
|
|
@@ -242,7 +246,7 @@ const validateDatabaseSchema = (options) => {
|
|
|
242
246
|
consola.log("4. Run 'pnpm generate' again after making changes");
|
|
243
247
|
consola.log("5. Run this check with --verbose to see all constraint names");
|
|
244
248
|
consola.log("");
|
|
245
|
-
return 1;
|
|
249
|
+
return finalize(1);
|
|
246
250
|
};
|
|
247
251
|
|
|
248
252
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-schema.mjs","names":["entries: JournalEntry[]","issues: NameLengthIssue[]","allConstraints: Array<{ table: string; type: string; name: string; length: number }>","parts: string[]"],"sources":["../../src/db/validateSchema.ts"],"sourcesContent":["/**\r\n * Database Schema Validator\r\n *\r\n * This script validates the database schema by analyzing all MySQL table definitions\r\n * from Drizzle migration metadata and reports any table, column, or constraint names\r\n * that exceed MySQL's maximum length limits.\r\n *\r\n * MySQL Limits:\r\n * - Table/Column names: 64 characters\r\n * - Constraint names: 64 characters\r\n *\r\n * Usage:\r\n * - Basic: pnpm -F scripts validate:db-schema\r\n * - Verbose: pnpm -F scripts validate:db-schema -- --verbose\r\n *\r\n * The verbose mode shows all constraint names being checked, sorted by length.\r\n *\r\n * This validation runs automatically:\r\n * - After `pnpm generate` (blocks if violations found)\r\n * - In CI/CD as part of PR checks\r\n */\r\n\r\nimport { consola } from \"consola\";\r\nimport { readFileSync } from \"node:fs\";\r\nimport { join, resolve } from \"node:path\";\r\n\r\ninterface NameLengthIssue {\r\n type: \"table\" | \"column\" | \"constraint\";\r\n tableName: string;\r\n name: string;\r\n actualLength: number;\r\n maxLength: number;\r\n constraintType?: \"foreign_key\" | \"primary_key\" | \"unique\" | \"index\";\r\n}\r\n\r\ninterface DrizzleSnapshot {\r\n tables: Record<string, DrizzleTable>;\r\n}\r\n\r\ninterface DrizzleTable {\r\n name: string;\r\n columns: Record<string, DrizzleColumn>;\r\n indexes: Record<string, DrizzleIndex>;\r\n foreignKeys: Record<string, DrizzleForeignKey>;\r\n compositePrimaryKeys: Record<string, DrizzlePrimaryKey>;\r\n uniqueConstraints: Record<string, DrizzleUniqueConstraint>;\r\n}\r\n\r\ninterface DrizzleColumn {\r\n name: string;\r\n type: string;\r\n primaryKey: boolean;\r\n notNull: boolean;\r\n autoincrement: boolean;\r\n}\r\n\r\ninterface DrizzleIndex {\r\n name: string;\r\n columns: string[];\r\n}\r\n\r\ninterface DrizzleForeignKey {\r\n name: string;\r\n tableFrom: string;\r\n tableTo: string;\r\n columnsFrom: string[];\r\n columnsTo: string[];\r\n}\r\n\r\ninterface DrizzlePrimaryKey {\r\n name: string;\r\n columns: string[];\r\n}\r\n\r\ninterface DrizzleUniqueConstraint {\r\n name: string;\r\n columns: string[];\r\n}\r\n\r\nconst MAX_IDENTIFIER_LENGTH = 64;\r\nconst MAX_CONSTRAINT_NAME_LENGTH = 64;\r\n\r\nconst getLatestSnapshot = (metaDir: string): DrizzleSnapshot => {\r\n const resolvedMetaDir = resolve(metaDir);\r\n\r\n // Read the journal to find the latest migration\r\n const journalPath = join(resolvedMetaDir, \"_journal.json\");\r\n const parsedJournal = JSON.parse(readFileSync(journalPath, \"utf-8\"));\r\n\r\n interface JournalEntry {\r\n tag: string;\r\n idx: number;\r\n }\r\n\r\n const entries: JournalEntry[] = parsedJournal.entries;\r\n const latestEntry = entries[entries.length - 1];\r\n\r\n if (!latestEntry) {\r\n throw new Error(\"No migrations found in journal\");\r\n }\r\n\r\n // Read the latest snapshot\r\n const snapshotPath = join(resolvedMetaDir, `${latestEntry.idx.toString().padStart(4, \"0\")}_snapshot.json`);\r\n const parsedSnapshot = JSON.parse(readFileSync(snapshotPath, \"utf-8\"));\r\n const snapshot: DrizzleSnapshot = parsedSnapshot;\r\n\r\n return snapshot;\r\n};\r\n\r\ninterface ValidateDatabaseSchemaOptions {\r\n metaDir: string;\r\n verbose?: boolean;\r\n}\r\n\r\nconst validateDatabaseSchema = (options: ValidateDatabaseSchemaOptions): number => {\r\n const { metaDir, verbose = false } = options;\r\n const issues: NameLengthIssue[] = [];\r\n const allConstraints: Array<{ table: string; type: string; name: string; length: number }> = [];\r\n\r\n const snapshot = getLatestSnapshot(metaDir);\r\n const tables = Object.values(snapshot.tables);\r\n\r\n tables.forEach((table) => {\r\n // Check table name length\r\n if (table.name.length > MAX_IDENTIFIER_LENGTH) {\r\n issues.push({\r\n type: \"table\",\r\n tableName: table.name,\r\n name: table.name,\r\n actualLength: table.name.length,\r\n maxLength: MAX_IDENTIFIER_LENGTH,\r\n });\r\n }\r\n\r\n // Check column name lengths\r\n Object.values(table.columns).forEach((column) => {\r\n if (column.name.length > MAX_IDENTIFIER_LENGTH) {\r\n issues.push({\r\n type: \"column\",\r\n tableName: table.name,\r\n name: column.name,\r\n actualLength: column.name.length,\r\n maxLength: MAX_IDENTIFIER_LENGTH,\r\n });\r\n }\r\n });\r\n\r\n // Check foreign key constraint names\r\n Object.values(table.foreignKeys).forEach((fk) => {\r\n allConstraints.push({ table: table.name, type: \"FK\", name: fk.name, length: fk.name.length });\r\n if (fk.name.length > MAX_CONSTRAINT_NAME_LENGTH) {\r\n issues.push({\r\n type: \"constraint\",\r\n tableName: table.name,\r\n name: fk.name,\r\n actualLength: fk.name.length,\r\n maxLength: MAX_CONSTRAINT_NAME_LENGTH,\r\n constraintType: \"foreign_key\",\r\n });\r\n }\r\n });\r\n\r\n // Check primary key constraint names\r\n Object.values(table.compositePrimaryKeys).forEach((pk) => {\r\n const pkType = pk.columns.length > 1 ? \"PK (composite)\" : \"PK\";\r\n allConstraints.push({ table: table.name, type: pkType, name: pk.name, length: pk.name.length });\r\n if (pk.name.length > MAX_CONSTRAINT_NAME_LENGTH) {\r\n issues.push({\r\n type: \"constraint\",\r\n tableName: table.name,\r\n name: pk.name,\r\n actualLength: pk.name.length,\r\n maxLength: MAX_CONSTRAINT_NAME_LENGTH,\r\n constraintType: \"primary_key\",\r\n });\r\n }\r\n });\r\n\r\n // Check unique constraint names\r\n Object.values(table.uniqueConstraints).forEach((unique) => {\r\n allConstraints.push({ table: table.name, type: \"UNIQUE\", name: unique.name, length: unique.name.length });\r\n if (unique.name.length > MAX_CONSTRAINT_NAME_LENGTH) {\r\n issues.push({\r\n type: \"constraint\",\r\n tableName: table.name,\r\n name: unique.name,\r\n actualLength: unique.name.length,\r\n maxLength: MAX_CONSTRAINT_NAME_LENGTH,\r\n constraintType: \"unique\",\r\n });\r\n }\r\n });\r\n\r\n // Check index names\r\n Object.values(table.indexes).forEach((index) => {\r\n allConstraints.push({ table: table.name, type: \"INDEX\", name: index.name, length: index.name.length });\r\n if (index.name.length > MAX_CONSTRAINT_NAME_LENGTH) {\r\n issues.push({\r\n type: \"constraint\",\r\n tableName: table.name,\r\n name: index.name,\r\n actualLength: index.name.length,\r\n maxLength: MAX_CONSTRAINT_NAME_LENGTH,\r\n constraintType: \"index\",\r\n });\r\n }\r\n });\r\n });\r\n\r\n // Verbose mode: show all constraints checked\r\n if (verbose) {\r\n consola.box(\"All Constraints Checked\");\r\n const sortedConstraints = allConstraints.sort((a, b) => b.length - a.length);\r\n sortedConstraints.forEach((constraint) => {\r\n const exceeds = constraint.length > MAX_CONSTRAINT_NAME_LENGTH;\r\n const lengthStr = `[${constraint.length.toString().padStart(2)} chars]`;\r\n const typeStr = constraint.type.padEnd(15);\r\n const message = `${lengthStr} ${typeStr} ${constraint.name} (${constraint.table})`;\r\n if (exceeds) {\r\n consola.error(message);\r\n } else {\r\n consola.success(message);\r\n }\r\n });\r\n consola.log(\"\");\r\n }\r\n\r\n // Print report\r\n consola.box(\"MySQL Name Length Analysis\");\r\n consola.info(`Maximum identifier length: ${MAX_IDENTIFIER_LENGTH} characters`);\r\n consola.info(`Maximum constraint name length: ${MAX_CONSTRAINT_NAME_LENGTH} characters`);\r\n consola.info(`Analyzed ${tables.length} tables`);\r\n consola.log(\"\");\r\n\r\n // Show table summary\r\n consola.start(\"Tables analyzed:\");\r\n tables.forEach((table) => {\r\n const columnCount = Object.keys(table.columns).length;\r\n const fkCount = Object.keys(table.foreignKeys).length;\r\n const pkCount = Object.keys(table.compositePrimaryKeys).length;\r\n const uniqueCount = Object.keys(table.uniqueConstraints).length;\r\n const indexCount = Object.keys(table.indexes).length;\r\n\r\n const parts: string[] = [];\r\n parts.push(`${columnCount} columns`);\r\n if (fkCount > 0) {\r\n parts.push(`${fkCount} FKs`);\r\n }\r\n if (pkCount > 0) {\r\n parts.push(`${pkCount} PKs`);\r\n }\r\n if (uniqueCount > 0) {\r\n parts.push(`${uniqueCount} unique`);\r\n }\r\n if (indexCount > 0) {\r\n parts.push(`${indexCount} indexes`);\r\n }\r\n\r\n consola.log(` - ${table.name} (${parts.join(\", \")})`);\r\n });\r\n consola.log(\"\");\r\n\r\n if (issues.length === 0) {\r\n consola.success(\"No name length issues found!\");\r\n return 0;\r\n }\r\n\r\n consola.warn(`Found ${issues.length} name length issue(s):`);\r\n consola.log(\"\");\r\n\r\n const tableIssues = issues.filter((i) => i.type === \"table\");\r\n const columnIssues = issues.filter((i) => i.type === \"column\");\r\n const constraintIssues = issues.filter((i) => i.type === \"constraint\");\r\n\r\n if (tableIssues.length > 0) {\r\n consola.error(\"Table Name Issues:\");\r\n tableIssues.forEach((issue) => {\r\n consola.log(\r\n ` - Table: ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n });\r\n consola.log(\"\");\r\n }\r\n\r\n if (columnIssues.length > 0) {\r\n consola.error(\"Column Name Issues:\");\r\n columnIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.tableName}.${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n });\r\n consola.log(\"\");\r\n }\r\n\r\n if (constraintIssues.length > 0) {\r\n consola.error(\"Constraint Name Issues:\");\r\n const fkIssues = constraintIssues.filter((i) => i.constraintType === \"foreign_key\");\r\n const pkIssues = constraintIssues.filter((i) => i.constraintType === \"primary_key\");\r\n const uniqueIssues = constraintIssues.filter((i) => i.constraintType === \"unique\");\r\n const indexIssues = constraintIssues.filter((i) => i.constraintType === \"index\");\r\n\r\n if (fkIssues.length > 0) {\r\n consola.log(\" Foreign Keys:\");\r\n fkIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n consola.log(` Table: ${issue.tableName}`);\r\n });\r\n }\r\n\r\n if (pkIssues.length > 0) {\r\n consola.log(\" Primary Keys:\");\r\n pkIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n consola.log(` Table: ${issue.tableName}`);\r\n });\r\n }\r\n\r\n if (uniqueIssues.length > 0) {\r\n consola.log(\" Unique Constraints:\");\r\n uniqueIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n consola.log(` Table: ${issue.tableName}`);\r\n });\r\n }\r\n\r\n if (indexIssues.length > 0) {\r\n consola.log(\" Indexes:\");\r\n indexIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n consola.log(` Table: ${issue.tableName}`);\r\n });\r\n }\r\n consola.log(\"\");\r\n }\r\n\r\n consola.box(\"Summary\");\r\n consola.info(`Total issues: ${issues.length}`);\r\n consola.info(` - Table names: ${tableIssues.length}`);\r\n consola.info(` - Column names: ${columnIssues.length}`);\r\n consola.info(` - Constraint names: ${constraintIssues.length}`);\r\n if (constraintIssues.length > 0) {\r\n const fkCount = constraintIssues.filter((i) => i.constraintType === \"foreign_key\").length;\r\n const pkCount = constraintIssues.filter((i) => i.constraintType === \"primary_key\").length;\r\n const uniqueCount = constraintIssues.filter((i) => i.constraintType === \"unique\").length;\r\n const indexCount = constraintIssues.filter((i) => i.constraintType === \"index\").length;\r\n consola.info(` - Foreign keys: ${fkCount}`);\r\n consola.info(` - Primary keys: ${pkCount}`);\r\n consola.info(` - Unique constraints: ${uniqueCount}`);\r\n consola.info(` - Indexes: ${indexCount}`);\r\n }\r\n consola.log(\"\");\r\n\r\n // Print actionable feedback\r\n consola.fail(\"MIGRATION BLOCKED: Name length violations detected!\");\r\n consola.log(\"\");\r\n consola.start(\"Action required:\");\r\n consola.log(\"1. Review the issues listed above\");\r\n consola.log(\"2. Shorten table/column names in your schema definitions\");\r\n consola.log(\"3. For foreign keys, consider:\");\r\n consola.log(\" - Shortening table names\");\r\n consola.log(\" - Shortening column names\");\r\n consola.log(\" - Using shorter referenced table names\");\r\n consola.log(\"4. Run 'pnpm generate' again after making changes\");\r\n consola.log(\"5. Run this check with --verbose to see all constraint names\");\r\n consola.log(\"\");\r\n\r\n return 1;\r\n};\r\n\r\nexport { validateDatabaseSchema };\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,MAAM,wBAAwB;AAC9B,MAAM,6BAA6B;AAEnC,MAAM,qBAAqB,YAAqC;CAC9D,MAAM,kBAAkB,QAAQ,QAAQ;CAGxC,MAAM,cAAc,KAAK,iBAAiB,gBAAgB;CAQ1D,MAAMA,UAPgB,KAAK,MAAM,aAAa,aAAa,QAAQ,CAAC,CAOtB;CAC9C,MAAM,cAAc,QAAQ,QAAQ,SAAS;AAE7C,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,iCAAiC;CAInD,MAAM,eAAe,KAAK,iBAAiB,GAAG,YAAY,IAAI,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB;AAI1G,QAHuB,KAAK,MAAM,aAAa,cAAc,QAAQ,CAAC;;AAWxE,MAAM,0BAA0B,YAAmD;CACjF,MAAM,EAAE,SAAS,UAAU,UAAU;CACrC,MAAMC,SAA4B,EAAE;CACpC,MAAMC,iBAAuF,EAAE;CAE/F,MAAM,WAAW,kBAAkB,QAAQ;CAC3C,MAAM,SAAS,OAAO,OAAO,SAAS,OAAO;AAE7C,QAAO,SAAS,UAAU;AAExB,MAAI,MAAM,KAAK,SAAS,sBACtB,QAAO,KAAK;GACV,MAAM;GACN,WAAW,MAAM;GACjB,MAAM,MAAM;GACZ,cAAc,MAAM,KAAK;GACzB,WAAW;GACZ,CAAC;AAIJ,SAAO,OAAO,MAAM,QAAQ,CAAC,SAAS,WAAW;AAC/C,OAAI,OAAO,KAAK,SAAS,sBACvB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,OAAO;IACb,cAAc,OAAO,KAAK;IAC1B,WAAW;IACZ,CAAC;IAEJ;AAGF,SAAO,OAAO,MAAM,YAAY,CAAC,SAAS,OAAO;AAC/C,kBAAe,KAAK;IAAE,OAAO,MAAM;IAAM,MAAM;IAAM,MAAM,GAAG;IAAM,QAAQ,GAAG,KAAK;IAAQ,CAAC;AAC7F,OAAI,GAAG,KAAK,SAAS,2BACnB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,GAAG;IACT,cAAc,GAAG,KAAK;IACtB,WAAW;IACX,gBAAgB;IACjB,CAAC;IAEJ;AAGF,SAAO,OAAO,MAAM,qBAAqB,CAAC,SAAS,OAAO;GACxD,MAAM,SAAS,GAAG,QAAQ,SAAS,IAAI,mBAAmB;AAC1D,kBAAe,KAAK;IAAE,OAAO,MAAM;IAAM,MAAM;IAAQ,MAAM,GAAG;IAAM,QAAQ,GAAG,KAAK;IAAQ,CAAC;AAC/F,OAAI,GAAG,KAAK,SAAS,2BACnB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,GAAG;IACT,cAAc,GAAG,KAAK;IACtB,WAAW;IACX,gBAAgB;IACjB,CAAC;IAEJ;AAGF,SAAO,OAAO,MAAM,kBAAkB,CAAC,SAAS,WAAW;AACzD,kBAAe,KAAK;IAAE,OAAO,MAAM;IAAM,MAAM;IAAU,MAAM,OAAO;IAAM,QAAQ,OAAO,KAAK;IAAQ,CAAC;AACzG,OAAI,OAAO,KAAK,SAAS,2BACvB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,OAAO;IACb,cAAc,OAAO,KAAK;IAC1B,WAAW;IACX,gBAAgB;IACjB,CAAC;IAEJ;AAGF,SAAO,OAAO,MAAM,QAAQ,CAAC,SAAS,UAAU;AAC9C,kBAAe,KAAK;IAAE,OAAO,MAAM;IAAM,MAAM;IAAS,MAAM,MAAM;IAAM,QAAQ,MAAM,KAAK;IAAQ,CAAC;AACtG,OAAI,MAAM,KAAK,SAAS,2BACtB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,MAAM;IACZ,cAAc,MAAM,KAAK;IACzB,WAAW;IACX,gBAAgB;IACjB,CAAC;IAEJ;GACF;AAGF,KAAI,SAAS;AACX,UAAQ,IAAI,0BAA0B;AAEtC,EAD0B,eAAe,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,OAAO,CAC1D,SAAS,eAAe;GACxC,MAAM,UAAU,WAAW,SAAS;GAGpC,MAAM,UAAU,GAFE,IAAI,WAAW,OAAO,UAAU,CAAC,SAAS,EAAE,CAAC,SAElC,GADb,WAAW,KAAK,OAAO,GAAG,CACF,GAAG,WAAW,KAAK,IAAI,WAAW,MAAM;AAChF,OAAI,QACF,SAAQ,MAAM,QAAQ;OAEtB,SAAQ,QAAQ,QAAQ;IAE1B;AACF,UAAQ,IAAI,GAAG;;AAIjB,SAAQ,IAAI,6BAA6B;AACzC,SAAQ,KAAK,8BAA8B,sBAAsB,aAAa;AAC9E,SAAQ,KAAK,mCAAmC,2BAA2B,aAAa;AACxF,SAAQ,KAAK,YAAY,OAAO,OAAO,SAAS;AAChD,SAAQ,IAAI,GAAG;AAGf,SAAQ,MAAM,mBAAmB;AACjC,QAAO,SAAS,UAAU;EACxB,MAAM,cAAc,OAAO,KAAK,MAAM,QAAQ,CAAC;EAC/C,MAAM,UAAU,OAAO,KAAK,MAAM,YAAY,CAAC;EAC/C,MAAM,UAAU,OAAO,KAAK,MAAM,qBAAqB,CAAC;EACxD,MAAM,cAAc,OAAO,KAAK,MAAM,kBAAkB,CAAC;EACzD,MAAM,aAAa,OAAO,KAAK,MAAM,QAAQ,CAAC;EAE9C,MAAMC,QAAkB,EAAE;AAC1B,QAAM,KAAK,GAAG,YAAY,UAAU;AACpC,MAAI,UAAU,EACZ,OAAM,KAAK,GAAG,QAAQ,MAAM;AAE9B,MAAI,UAAU,EACZ,OAAM,KAAK,GAAG,QAAQ,MAAM;AAE9B,MAAI,cAAc,EAChB,OAAM,KAAK,GAAG,YAAY,SAAS;AAErC,MAAI,aAAa,EACf,OAAM,KAAK,GAAG,WAAW,UAAU;AAGrC,UAAQ,IAAI,OAAO,MAAM,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC,GAAG;GACtD;AACF,SAAQ,IAAI,GAAG;AAEf,KAAI,OAAO,WAAW,GAAG;AACvB,UAAQ,QAAQ,+BAA+B;AAC/C,SAAO;;AAGT,SAAQ,KAAK,SAAS,OAAO,OAAO,wBAAwB;AAC5D,SAAQ,IAAI,GAAG;CAEf,MAAM,cAAc,OAAO,QAAQ,MAAM,EAAE,SAAS,QAAQ;CAC5D,MAAM,eAAe,OAAO,QAAQ,MAAM,EAAE,SAAS,SAAS;CAC9D,MAAM,mBAAmB,OAAO,QAAQ,MAAM,EAAE,SAAS,aAAa;AAEtE,KAAI,YAAY,SAAS,GAAG;AAC1B,UAAQ,MAAM,qBAAqB;AACnC,cAAY,SAAS,UAAU;AAC7B,WAAQ,IACN,cAAc,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC/G;IACD;AACF,UAAQ,IAAI,GAAG;;AAGjB,KAAI,aAAa,SAAS,GAAG;AAC3B,UAAQ,MAAM,sBAAsB;AACpC,eAAa,SAAS,UAAU;AAC9B,WAAQ,IACN,OAAO,MAAM,UAAU,GAAG,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC3H;IACD;AACF,UAAQ,IAAI,GAAG;;AAGjB,KAAI,iBAAiB,SAAS,GAAG;AAC/B,UAAQ,MAAM,0BAA0B;EACxC,MAAM,WAAW,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,cAAc;EACnF,MAAM,WAAW,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,cAAc;EACnF,MAAM,eAAe,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,SAAS;EAClF,MAAM,cAAc,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,QAAQ;AAEhF,MAAI,SAAS,SAAS,GAAG;AACvB,WAAQ,IAAI,kBAAkB;AAC9B,YAAS,SAAS,UAAU;AAC1B,YAAQ,IACN,SAAS,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC1G;AACD,YAAQ,IAAI,gBAAgB,MAAM,YAAY;KAC9C;;AAGJ,MAAI,SAAS,SAAS,GAAG;AACvB,WAAQ,IAAI,kBAAkB;AAC9B,YAAS,SAAS,UAAU;AAC1B,YAAQ,IACN,SAAS,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC1G;AACD,YAAQ,IAAI,gBAAgB,MAAM,YAAY;KAC9C;;AAGJ,MAAI,aAAa,SAAS,GAAG;AAC3B,WAAQ,IAAI,wBAAwB;AACpC,gBAAa,SAAS,UAAU;AAC9B,YAAQ,IACN,SAAS,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC1G;AACD,YAAQ,IAAI,gBAAgB,MAAM,YAAY;KAC9C;;AAGJ,MAAI,YAAY,SAAS,GAAG;AAC1B,WAAQ,IAAI,aAAa;AACzB,eAAY,SAAS,UAAU;AAC7B,YAAQ,IACN,SAAS,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC1G;AACD,YAAQ,IAAI,gBAAgB,MAAM,YAAY;KAC9C;;AAEJ,UAAQ,IAAI,GAAG;;AAGjB,SAAQ,IAAI,UAAU;AACtB,SAAQ,KAAK,iBAAiB,OAAO,SAAS;AAC9C,SAAQ,KAAK,oBAAoB,YAAY,SAAS;AACtD,SAAQ,KAAK,qBAAqB,aAAa,SAAS;AACxD,SAAQ,KAAK,yBAAyB,iBAAiB,SAAS;AAChE,KAAI,iBAAiB,SAAS,GAAG;EAC/B,MAAM,UAAU,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,cAAc,CAAC;EACnF,MAAM,UAAU,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,cAAc,CAAC;EACnF,MAAM,cAAc,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,SAAS,CAAC;EAClF,MAAM,aAAa,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,QAAQ,CAAC;AAChF,UAAQ,KAAK,uBAAuB,UAAU;AAC9C,UAAQ,KAAK,uBAAuB,UAAU;AAC9C,UAAQ,KAAK,6BAA6B,cAAc;AACxD,UAAQ,KAAK,kBAAkB,aAAa;;AAE9C,SAAQ,IAAI,GAAG;AAGf,SAAQ,KAAK,sDAAsD;AACnE,SAAQ,IAAI,GAAG;AACf,SAAQ,MAAM,mBAAmB;AACjC,SAAQ,IAAI,oCAAoC;AAChD,SAAQ,IAAI,2DAA2D;AACvE,SAAQ,IAAI,iCAAiC;AAC7C,SAAQ,IAAI,8BAA8B;AAC1C,SAAQ,IAAI,+BAA+B;AAC3C,SAAQ,IAAI,4CAA4C;AACxD,SAAQ,IAAI,oDAAoD;AAChE,SAAQ,IAAI,+DAA+D;AAC3E,SAAQ,IAAI,GAAG;AAEf,QAAO"}
|
|
1
|
+
{"version":3,"file":"validate-schema.mjs","names":["entries: JournalEntry[]","issues: NameLengthIssue[]","allConstraints: Array<{ table: string; type: string; name: string; length: number }>","parts: string[]"],"sources":["../../src/db/validateSchema.ts"],"sourcesContent":["/**\r\n * Database Schema Validator\r\n *\r\n * This script validates the database schema by analyzing all MySQL table definitions\r\n * from Drizzle migration metadata and reports any table, column, or constraint names\r\n * that exceed MySQL's maximum length limits.\r\n *\r\n * MySQL Limits:\r\n * - Table/Column names: 64 characters\r\n * - Constraint names: 64 characters\r\n *\r\n * Usage:\r\n * - Basic: pnpm -F scripts validate:db-schema\r\n * - Verbose: pnpm -F scripts validate:db-schema -- --verbose\r\n *\r\n * The verbose mode shows all constraint names being checked, sorted by length.\r\n *\r\n * This validation runs automatically:\r\n * - After `pnpm generate` (blocks if violations found)\r\n * - In CI/CD as part of PR checks\r\n */\r\n\r\nimport { consola } from \"consola\";\r\nimport { readFileSync } from \"node:fs\";\r\nimport { join, resolve } from \"node:path\";\r\n\r\ninterface NameLengthIssue {\r\n type: \"table\" | \"column\" | \"constraint\";\r\n tableName: string;\r\n name: string;\r\n actualLength: number;\r\n maxLength: number;\r\n constraintType?: \"foreign_key\" | \"primary_key\" | \"unique\" | \"index\";\r\n}\r\n\r\ninterface DrizzleSnapshot {\r\n tables: Record<string, DrizzleTable>;\r\n}\r\n\r\ninterface DrizzleTable {\r\n name: string;\r\n columns: Record<string, DrizzleColumn>;\r\n indexes: Record<string, DrizzleIndex>;\r\n foreignKeys: Record<string, DrizzleForeignKey>;\r\n compositePrimaryKeys: Record<string, DrizzlePrimaryKey>;\r\n uniqueConstraints: Record<string, DrizzleUniqueConstraint>;\r\n}\r\n\r\ninterface DrizzleColumn {\r\n name: string;\r\n type: string;\r\n primaryKey: boolean;\r\n notNull: boolean;\r\n autoincrement: boolean;\r\n}\r\n\r\ninterface DrizzleIndex {\r\n name: string;\r\n columns: string[];\r\n}\r\n\r\ninterface DrizzleForeignKey {\r\n name: string;\r\n tableFrom: string;\r\n tableTo: string;\r\n columnsFrom: string[];\r\n columnsTo: string[];\r\n}\r\n\r\ninterface DrizzlePrimaryKey {\r\n name: string;\r\n columns: string[];\r\n}\r\n\r\ninterface DrizzleUniqueConstraint {\r\n name: string;\r\n columns: string[];\r\n}\r\n\r\nconst MAX_IDENTIFIER_LENGTH = 64;\r\nconst MAX_CONSTRAINT_NAME_LENGTH = 64;\r\n\r\nconst getLatestSnapshot = (metaDir: string): DrizzleSnapshot => {\r\n const resolvedMetaDir = resolve(metaDir);\r\n\r\n // Read the journal to find the latest migration\r\n const journalPath = join(resolvedMetaDir, \"_journal.json\");\r\n const parsedJournal = JSON.parse(readFileSync(journalPath, \"utf-8\"));\r\n\r\n interface JournalEntry {\r\n tag: string;\r\n idx: number;\r\n }\r\n\r\n const entries: JournalEntry[] = parsedJournal.entries;\r\n const latestEntry = entries[entries.length - 1];\r\n\r\n if (!latestEntry) {\r\n throw new Error(\"No migrations found in journal\");\r\n }\r\n\r\n // Read the latest snapshot\r\n const snapshotPath = join(resolvedMetaDir, `${latestEntry.idx.toString().padStart(4, \"0\")}_snapshot.json`);\r\n const parsedSnapshot = JSON.parse(readFileSync(snapshotPath, \"utf-8\"));\r\n const snapshot: DrizzleSnapshot = parsedSnapshot;\r\n\r\n return snapshot;\r\n};\r\n\r\ninterface ValidateDatabaseSchemaOptions {\n metaDir: string;\n verbose?: boolean;\n exitOnComplete?: boolean;\n}\n\nconst validateDatabaseSchema = (options: ValidateDatabaseSchemaOptions): number => {\n const { metaDir, verbose = false, exitOnComplete = false } = options;\n const issues: NameLengthIssue[] = [];\n const allConstraints: Array<{ table: string; type: string; name: string; length: number }> = [];\n const finalize = (exitCode: number): number => {\n if (exitOnComplete) {\n process.exit(exitCode);\n }\n\n return exitCode;\n };\n\r\n const snapshot = getLatestSnapshot(metaDir);\r\n const tables = Object.values(snapshot.tables);\r\n\r\n tables.forEach((table) => {\r\n // Check table name length\r\n if (table.name.length > MAX_IDENTIFIER_LENGTH) {\r\n issues.push({\r\n type: \"table\",\r\n tableName: table.name,\r\n name: table.name,\r\n actualLength: table.name.length,\r\n maxLength: MAX_IDENTIFIER_LENGTH,\r\n });\r\n }\r\n\r\n // Check column name lengths\r\n Object.values(table.columns).forEach((column) => {\r\n if (column.name.length > MAX_IDENTIFIER_LENGTH) {\r\n issues.push({\r\n type: \"column\",\r\n tableName: table.name,\r\n name: column.name,\r\n actualLength: column.name.length,\r\n maxLength: MAX_IDENTIFIER_LENGTH,\r\n });\r\n }\r\n });\r\n\r\n // Check foreign key constraint names\r\n Object.values(table.foreignKeys).forEach((fk) => {\r\n allConstraints.push({ table: table.name, type: \"FK\", name: fk.name, length: fk.name.length });\r\n if (fk.name.length > MAX_CONSTRAINT_NAME_LENGTH) {\r\n issues.push({\r\n type: \"constraint\",\r\n tableName: table.name,\r\n name: fk.name,\r\n actualLength: fk.name.length,\r\n maxLength: MAX_CONSTRAINT_NAME_LENGTH,\r\n constraintType: \"foreign_key\",\r\n });\r\n }\r\n });\r\n\r\n // Check primary key constraint names\r\n Object.values(table.compositePrimaryKeys).forEach((pk) => {\r\n const pkType = pk.columns.length > 1 ? \"PK (composite)\" : \"PK\";\r\n allConstraints.push({ table: table.name, type: pkType, name: pk.name, length: pk.name.length });\r\n if (pk.name.length > MAX_CONSTRAINT_NAME_LENGTH) {\r\n issues.push({\r\n type: \"constraint\",\r\n tableName: table.name,\r\n name: pk.name,\r\n actualLength: pk.name.length,\r\n maxLength: MAX_CONSTRAINT_NAME_LENGTH,\r\n constraintType: \"primary_key\",\r\n });\r\n }\r\n });\r\n\r\n // Check unique constraint names\r\n Object.values(table.uniqueConstraints).forEach((unique) => {\r\n allConstraints.push({ table: table.name, type: \"UNIQUE\", name: unique.name, length: unique.name.length });\r\n if (unique.name.length > MAX_CONSTRAINT_NAME_LENGTH) {\r\n issues.push({\r\n type: \"constraint\",\r\n tableName: table.name,\r\n name: unique.name,\r\n actualLength: unique.name.length,\r\n maxLength: MAX_CONSTRAINT_NAME_LENGTH,\r\n constraintType: \"unique\",\r\n });\r\n }\r\n });\r\n\r\n // Check index names\r\n Object.values(table.indexes).forEach((index) => {\r\n allConstraints.push({ table: table.name, type: \"INDEX\", name: index.name, length: index.name.length });\r\n if (index.name.length > MAX_CONSTRAINT_NAME_LENGTH) {\r\n issues.push({\r\n type: \"constraint\",\r\n tableName: table.name,\r\n name: index.name,\r\n actualLength: index.name.length,\r\n maxLength: MAX_CONSTRAINT_NAME_LENGTH,\r\n constraintType: \"index\",\r\n });\r\n }\r\n });\r\n });\r\n\r\n // Verbose mode: show all constraints checked\r\n if (verbose) {\r\n consola.box(\"All Constraints Checked\");\r\n const sortedConstraints = allConstraints.sort((a, b) => b.length - a.length);\r\n sortedConstraints.forEach((constraint) => {\r\n const exceeds = constraint.length > MAX_CONSTRAINT_NAME_LENGTH;\r\n const lengthStr = `[${constraint.length.toString().padStart(2)} chars]`;\r\n const typeStr = constraint.type.padEnd(15);\r\n const message = `${lengthStr} ${typeStr} ${constraint.name} (${constraint.table})`;\r\n if (exceeds) {\r\n consola.error(message);\r\n } else {\r\n consola.success(message);\r\n }\r\n });\r\n consola.log(\"\");\r\n }\r\n\r\n // Print report\r\n consola.box(\"MySQL Name Length Analysis\");\r\n consola.info(`Maximum identifier length: ${MAX_IDENTIFIER_LENGTH} characters`);\r\n consola.info(`Maximum constraint name length: ${MAX_CONSTRAINT_NAME_LENGTH} characters`);\r\n consola.info(`Analyzed ${tables.length} tables`);\r\n consola.log(\"\");\r\n\r\n // Show table summary\r\n consola.start(\"Tables analyzed:\");\r\n tables.forEach((table) => {\r\n const columnCount = Object.keys(table.columns).length;\r\n const fkCount = Object.keys(table.foreignKeys).length;\r\n const pkCount = Object.keys(table.compositePrimaryKeys).length;\r\n const uniqueCount = Object.keys(table.uniqueConstraints).length;\r\n const indexCount = Object.keys(table.indexes).length;\r\n\r\n const parts: string[] = [];\r\n parts.push(`${columnCount} columns`);\r\n if (fkCount > 0) {\r\n parts.push(`${fkCount} FKs`);\r\n }\r\n if (pkCount > 0) {\r\n parts.push(`${pkCount} PKs`);\r\n }\r\n if (uniqueCount > 0) {\r\n parts.push(`${uniqueCount} unique`);\r\n }\r\n if (indexCount > 0) {\r\n parts.push(`${indexCount} indexes`);\r\n }\r\n\r\n consola.log(` - ${table.name} (${parts.join(\", \")})`);\r\n });\r\n consola.log(\"\");\r\n\r\n if (issues.length === 0) {\n consola.success(\"No name length issues found!\");\n return finalize(0);\n }\n\r\n consola.warn(`Found ${issues.length} name length issue(s):`);\r\n consola.log(\"\");\r\n\r\n const tableIssues = issues.filter((i) => i.type === \"table\");\r\n const columnIssues = issues.filter((i) => i.type === \"column\");\r\n const constraintIssues = issues.filter((i) => i.type === \"constraint\");\r\n\r\n if (tableIssues.length > 0) {\r\n consola.error(\"Table Name Issues:\");\r\n tableIssues.forEach((issue) => {\r\n consola.log(\r\n ` - Table: ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n });\r\n consola.log(\"\");\r\n }\r\n\r\n if (columnIssues.length > 0) {\r\n consola.error(\"Column Name Issues:\");\r\n columnIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.tableName}.${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n });\r\n consola.log(\"\");\r\n }\r\n\r\n if (constraintIssues.length > 0) {\r\n consola.error(\"Constraint Name Issues:\");\r\n const fkIssues = constraintIssues.filter((i) => i.constraintType === \"foreign_key\");\r\n const pkIssues = constraintIssues.filter((i) => i.constraintType === \"primary_key\");\r\n const uniqueIssues = constraintIssues.filter((i) => i.constraintType === \"unique\");\r\n const indexIssues = constraintIssues.filter((i) => i.constraintType === \"index\");\r\n\r\n if (fkIssues.length > 0) {\r\n consola.log(\" Foreign Keys:\");\r\n fkIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n consola.log(` Table: ${issue.tableName}`);\r\n });\r\n }\r\n\r\n if (pkIssues.length > 0) {\r\n consola.log(\" Primary Keys:\");\r\n pkIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n consola.log(` Table: ${issue.tableName}`);\r\n });\r\n }\r\n\r\n if (uniqueIssues.length > 0) {\r\n consola.log(\" Unique Constraints:\");\r\n uniqueIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n consola.log(` Table: ${issue.tableName}`);\r\n });\r\n }\r\n\r\n if (indexIssues.length > 0) {\r\n consola.log(\" Indexes:\");\r\n indexIssues.forEach((issue) => {\r\n consola.log(\r\n ` - ${issue.name} (${issue.actualLength} chars, exceeds max by ${issue.actualLength - issue.maxLength})`,\r\n );\r\n consola.log(` Table: ${issue.tableName}`);\r\n });\r\n }\r\n consola.log(\"\");\r\n }\r\n\r\n consola.box(\"Summary\");\r\n consola.info(`Total issues: ${issues.length}`);\r\n consola.info(` - Table names: ${tableIssues.length}`);\r\n consola.info(` - Column names: ${columnIssues.length}`);\r\n consola.info(` - Constraint names: ${constraintIssues.length}`);\r\n if (constraintIssues.length > 0) {\r\n const fkCount = constraintIssues.filter((i) => i.constraintType === \"foreign_key\").length;\r\n const pkCount = constraintIssues.filter((i) => i.constraintType === \"primary_key\").length;\r\n const uniqueCount = constraintIssues.filter((i) => i.constraintType === \"unique\").length;\r\n const indexCount = constraintIssues.filter((i) => i.constraintType === \"index\").length;\r\n consola.info(` - Foreign keys: ${fkCount}`);\r\n consola.info(` - Primary keys: ${pkCount}`);\r\n consola.info(` - Unique constraints: ${uniqueCount}`);\r\n consola.info(` - Indexes: ${indexCount}`);\r\n }\r\n consola.log(\"\");\r\n\r\n // Print actionable feedback\r\n consola.fail(\"MIGRATION BLOCKED: Name length violations detected!\");\r\n consola.log(\"\");\r\n consola.start(\"Action required:\");\r\n consola.log(\"1. Review the issues listed above\");\r\n consola.log(\"2. Shorten table/column names in your schema definitions\");\r\n consola.log(\"3. For foreign keys, consider:\");\r\n consola.log(\" - Shortening table names\");\r\n consola.log(\" - Shortening column names\");\r\n consola.log(\" - Using shorter referenced table names\");\r\n consola.log(\"4. Run 'pnpm generate' again after making changes\");\r\n consola.log(\"5. Run this check with --verbose to see all constraint names\");\r\n consola.log(\"\");\r\n\r\n return finalize(1);\n};\n\r\nexport { validateDatabaseSchema };\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,MAAM,wBAAwB;AAC9B,MAAM,6BAA6B;AAEnC,MAAM,qBAAqB,YAAqC;CAC9D,MAAM,kBAAkB,QAAQ,QAAQ;CAGxC,MAAM,cAAc,KAAK,iBAAiB,gBAAgB;CAQ1D,MAAMA,UAPgB,KAAK,MAAM,aAAa,aAAa,QAAQ,CAAC,CAOtB;CAC9C,MAAM,cAAc,QAAQ,QAAQ,SAAS;AAE7C,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,iCAAiC;CAInD,MAAM,eAAe,KAAK,iBAAiB,GAAG,YAAY,IAAI,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB;AAI1G,QAHuB,KAAK,MAAM,aAAa,cAAc,QAAQ,CAAC;;AAYxE,MAAM,0BAA0B,YAAmD;CACjF,MAAM,EAAE,SAAS,UAAU,OAAO,iBAAiB,UAAU;CAC7D,MAAMC,SAA4B,EAAE;CACpC,MAAMC,iBAAuF,EAAE;CAC/F,MAAM,YAAY,aAA6B;AAC7C,MAAI,eACF,SAAQ,KAAK,SAAS;AAGxB,SAAO;;CAGT,MAAM,WAAW,kBAAkB,QAAQ;CAC3C,MAAM,SAAS,OAAO,OAAO,SAAS,OAAO;AAE7C,QAAO,SAAS,UAAU;AAExB,MAAI,MAAM,KAAK,SAAS,sBACtB,QAAO,KAAK;GACV,MAAM;GACN,WAAW,MAAM;GACjB,MAAM,MAAM;GACZ,cAAc,MAAM,KAAK;GACzB,WAAW;GACZ,CAAC;AAIJ,SAAO,OAAO,MAAM,QAAQ,CAAC,SAAS,WAAW;AAC/C,OAAI,OAAO,KAAK,SAAS,sBACvB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,OAAO;IACb,cAAc,OAAO,KAAK;IAC1B,WAAW;IACZ,CAAC;IAEJ;AAGF,SAAO,OAAO,MAAM,YAAY,CAAC,SAAS,OAAO;AAC/C,kBAAe,KAAK;IAAE,OAAO,MAAM;IAAM,MAAM;IAAM,MAAM,GAAG;IAAM,QAAQ,GAAG,KAAK;IAAQ,CAAC;AAC7F,OAAI,GAAG,KAAK,SAAS,2BACnB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,GAAG;IACT,cAAc,GAAG,KAAK;IACtB,WAAW;IACX,gBAAgB;IACjB,CAAC;IAEJ;AAGF,SAAO,OAAO,MAAM,qBAAqB,CAAC,SAAS,OAAO;GACxD,MAAM,SAAS,GAAG,QAAQ,SAAS,IAAI,mBAAmB;AAC1D,kBAAe,KAAK;IAAE,OAAO,MAAM;IAAM,MAAM;IAAQ,MAAM,GAAG;IAAM,QAAQ,GAAG,KAAK;IAAQ,CAAC;AAC/F,OAAI,GAAG,KAAK,SAAS,2BACnB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,GAAG;IACT,cAAc,GAAG,KAAK;IACtB,WAAW;IACX,gBAAgB;IACjB,CAAC;IAEJ;AAGF,SAAO,OAAO,MAAM,kBAAkB,CAAC,SAAS,WAAW;AACzD,kBAAe,KAAK;IAAE,OAAO,MAAM;IAAM,MAAM;IAAU,MAAM,OAAO;IAAM,QAAQ,OAAO,KAAK;IAAQ,CAAC;AACzG,OAAI,OAAO,KAAK,SAAS,2BACvB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,OAAO;IACb,cAAc,OAAO,KAAK;IAC1B,WAAW;IACX,gBAAgB;IACjB,CAAC;IAEJ;AAGF,SAAO,OAAO,MAAM,QAAQ,CAAC,SAAS,UAAU;AAC9C,kBAAe,KAAK;IAAE,OAAO,MAAM;IAAM,MAAM;IAAS,MAAM,MAAM;IAAM,QAAQ,MAAM,KAAK;IAAQ,CAAC;AACtG,OAAI,MAAM,KAAK,SAAS,2BACtB,QAAO,KAAK;IACV,MAAM;IACN,WAAW,MAAM;IACjB,MAAM,MAAM;IACZ,cAAc,MAAM,KAAK;IACzB,WAAW;IACX,gBAAgB;IACjB,CAAC;IAEJ;GACF;AAGF,KAAI,SAAS;AACX,UAAQ,IAAI,0BAA0B;AAEtC,EAD0B,eAAe,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,OAAO,CAC1D,SAAS,eAAe;GACxC,MAAM,UAAU,WAAW,SAAS;GAGpC,MAAM,UAAU,GAFE,IAAI,WAAW,OAAO,UAAU,CAAC,SAAS,EAAE,CAAC,SAElC,GADb,WAAW,KAAK,OAAO,GAAG,CACF,GAAG,WAAW,KAAK,IAAI,WAAW,MAAM;AAChF,OAAI,QACF,SAAQ,MAAM,QAAQ;OAEtB,SAAQ,QAAQ,QAAQ;IAE1B;AACF,UAAQ,IAAI,GAAG;;AAIjB,SAAQ,IAAI,6BAA6B;AACzC,SAAQ,KAAK,8BAA8B,sBAAsB,aAAa;AAC9E,SAAQ,KAAK,mCAAmC,2BAA2B,aAAa;AACxF,SAAQ,KAAK,YAAY,OAAO,OAAO,SAAS;AAChD,SAAQ,IAAI,GAAG;AAGf,SAAQ,MAAM,mBAAmB;AACjC,QAAO,SAAS,UAAU;EACxB,MAAM,cAAc,OAAO,KAAK,MAAM,QAAQ,CAAC;EAC/C,MAAM,UAAU,OAAO,KAAK,MAAM,YAAY,CAAC;EAC/C,MAAM,UAAU,OAAO,KAAK,MAAM,qBAAqB,CAAC;EACxD,MAAM,cAAc,OAAO,KAAK,MAAM,kBAAkB,CAAC;EACzD,MAAM,aAAa,OAAO,KAAK,MAAM,QAAQ,CAAC;EAE9C,MAAMC,QAAkB,EAAE;AAC1B,QAAM,KAAK,GAAG,YAAY,UAAU;AACpC,MAAI,UAAU,EACZ,OAAM,KAAK,GAAG,QAAQ,MAAM;AAE9B,MAAI,UAAU,EACZ,OAAM,KAAK,GAAG,QAAQ,MAAM;AAE9B,MAAI,cAAc,EAChB,OAAM,KAAK,GAAG,YAAY,SAAS;AAErC,MAAI,aAAa,EACf,OAAM,KAAK,GAAG,WAAW,UAAU;AAGrC,UAAQ,IAAI,OAAO,MAAM,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC,GAAG;GACtD;AACF,SAAQ,IAAI,GAAG;AAEf,KAAI,OAAO,WAAW,GAAG;AACvB,UAAQ,QAAQ,+BAA+B;AAC/C,SAAO,SAAS,EAAE;;AAGpB,SAAQ,KAAK,SAAS,OAAO,OAAO,wBAAwB;AAC5D,SAAQ,IAAI,GAAG;CAEf,MAAM,cAAc,OAAO,QAAQ,MAAM,EAAE,SAAS,QAAQ;CAC5D,MAAM,eAAe,OAAO,QAAQ,MAAM,EAAE,SAAS,SAAS;CAC9D,MAAM,mBAAmB,OAAO,QAAQ,MAAM,EAAE,SAAS,aAAa;AAEtE,KAAI,YAAY,SAAS,GAAG;AAC1B,UAAQ,MAAM,qBAAqB;AACnC,cAAY,SAAS,UAAU;AAC7B,WAAQ,IACN,cAAc,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC/G;IACD;AACF,UAAQ,IAAI,GAAG;;AAGjB,KAAI,aAAa,SAAS,GAAG;AAC3B,UAAQ,MAAM,sBAAsB;AACpC,eAAa,SAAS,UAAU;AAC9B,WAAQ,IACN,OAAO,MAAM,UAAU,GAAG,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC3H;IACD;AACF,UAAQ,IAAI,GAAG;;AAGjB,KAAI,iBAAiB,SAAS,GAAG;AAC/B,UAAQ,MAAM,0BAA0B;EACxC,MAAM,WAAW,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,cAAc;EACnF,MAAM,WAAW,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,cAAc;EACnF,MAAM,eAAe,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,SAAS;EAClF,MAAM,cAAc,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,QAAQ;AAEhF,MAAI,SAAS,SAAS,GAAG;AACvB,WAAQ,IAAI,kBAAkB;AAC9B,YAAS,SAAS,UAAU;AAC1B,YAAQ,IACN,SAAS,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC1G;AACD,YAAQ,IAAI,gBAAgB,MAAM,YAAY;KAC9C;;AAGJ,MAAI,SAAS,SAAS,GAAG;AACvB,WAAQ,IAAI,kBAAkB;AAC9B,YAAS,SAAS,UAAU;AAC1B,YAAQ,IACN,SAAS,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC1G;AACD,YAAQ,IAAI,gBAAgB,MAAM,YAAY;KAC9C;;AAGJ,MAAI,aAAa,SAAS,GAAG;AAC3B,WAAQ,IAAI,wBAAwB;AACpC,gBAAa,SAAS,UAAU;AAC9B,YAAQ,IACN,SAAS,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC1G;AACD,YAAQ,IAAI,gBAAgB,MAAM,YAAY;KAC9C;;AAGJ,MAAI,YAAY,SAAS,GAAG;AAC1B,WAAQ,IAAI,aAAa;AACzB,eAAY,SAAS,UAAU;AAC7B,YAAQ,IACN,SAAS,MAAM,KAAK,IAAI,MAAM,aAAa,yBAAyB,MAAM,eAAe,MAAM,UAAU,GAC1G;AACD,YAAQ,IAAI,gBAAgB,MAAM,YAAY;KAC9C;;AAEJ,UAAQ,IAAI,GAAG;;AAGjB,SAAQ,IAAI,UAAU;AACtB,SAAQ,KAAK,iBAAiB,OAAO,SAAS;AAC9C,SAAQ,KAAK,oBAAoB,YAAY,SAAS;AACtD,SAAQ,KAAK,qBAAqB,aAAa,SAAS;AACxD,SAAQ,KAAK,yBAAyB,iBAAiB,SAAS;AAChE,KAAI,iBAAiB,SAAS,GAAG;EAC/B,MAAM,UAAU,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,cAAc,CAAC;EACnF,MAAM,UAAU,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,cAAc,CAAC;EACnF,MAAM,cAAc,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,SAAS,CAAC;EAClF,MAAM,aAAa,iBAAiB,QAAQ,MAAM,EAAE,mBAAmB,QAAQ,CAAC;AAChF,UAAQ,KAAK,uBAAuB,UAAU;AAC9C,UAAQ,KAAK,uBAAuB,UAAU;AAC9C,UAAQ,KAAK,6BAA6B,cAAc;AACxD,UAAQ,KAAK,kBAAkB,aAAa;;AAE9C,SAAQ,IAAI,GAAG;AAGf,SAAQ,KAAK,sDAAsD;AACnE,SAAQ,IAAI,GAAG;AACf,SAAQ,MAAM,mBAAmB;AACjC,SAAQ,IAAI,oCAAoC;AAChD,SAAQ,IAAI,2DAA2D;AACvE,SAAQ,IAAI,iCAAiC;AAC7C,SAAQ,IAAI,8BAA8B;AAC1C,SAAQ,IAAI,+BAA+B;AAC3C,SAAQ,IAAI,4CAA4C;AACxD,SAAQ,IAAI,oDAAoD;AAChE,SAAQ,IAAI,+DAA+D;AAC3E,SAAQ,IAAI,GAAG;AAEf,QAAO,SAAS,EAAE"}
|