@noego/proper 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1140 -0
- package/bin/cli.js.map +1 -0
- package/bin/cli.mjs +1192 -0
- package/bin/cli.mjs.map +1 -0
- package/bin/index.js +971 -0
- package/bin/index.js.map +1 -0
- package/bin/index.mjs +935 -0
- package/bin/index.mjs.map +1 -0
- package/package.json +55 -0
- package/readme.md +236 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../framework/MigrationRunner.ts","../framework/MigrationDirectoryReader.ts","../framework/errors.ts","../framework/MigrationNode.ts","../framework/SqlMigrationBuilder.ts","../framework/MigrationSetup.ts","../framework/MigrationFilter.ts","../framework/MigrationDialectParser.ts","../framework/SQLRunner.ts"],"sourcesContent":["// Node built-in modules\nimport fs from 'fs';\n\n// Database drivers\nimport mysql from 'mysql2/promise';\nimport * as sqlite from 'sqlite';\nimport * as sqlite3 from 'sqlite3';\n\n// Helper function to convert any error to Error object\nfunction toError(error: unknown): Error {\n if (error instanceof Error) return error;\n return new Error(String(error));\n}\n\n// Migration framework\nimport { MigrationConfig } from \"./MigrationConfig\";\nimport { MigrationDirectoryReader } from './MigrationDirectoryReader';\nimport { MigrationNode } from './MigrationNode';\nimport { MigrationSetup } from './MigrationSetup';\nimport { migration_filter } from './MigrationFilter';\nimport * as dialect from \"./MigrationDialectParser\";\n\n\nimport { ISQLRunner,SQLRunner,SQLiteRunner } from './SQLRunner';\nimport { ErrorMessage } from './ErrorMessage';\nimport { \n ConfigurationError, \n DatabaseConnectionError, \n MigrationExecutionError,\n CLIError \n} from './errors';\n\n\nexport class MigrationRunnerFactory {\n\n static async create(configFile:string,conn?:any){\n const configReader = new FileMigrationConfigReader(configFile);\n const config = configReader.loadFile();\n\n if(!conn){\n conn = await this.createConnection(config)\n }\n return new MigrationRunnerFactory().create(config,conn)\n }\n\n static async createConnection(config:MigrationConfig){\n let conn = null as any\n switch(config.database){\n case \"sql\":\n if (!config.sql){\n throw ConfigurationError.missingDatabaseConfiguration(\"sql\");\n }\n const settings = Object.assign({\n password:process.env.SQL_PASSWORD\n },config.sql);\n try {\n conn = await mysql.createConnection(settings);\n return conn;\n } catch (error) {\n throw DatabaseConnectionError.connectionFailed(\"sql\", toError(error).message);\n }\n case \"sqlite\":\n if (!config.sqlite){\n throw ConfigurationError.missingDatabaseConfiguration(\"sqlite\");\n }\n try {\n conn = await sqlite.open({\n filename:config.sqlite.database,\n driver:sqlite3.Database\n });\n return conn;\n } catch (error) {\n throw DatabaseConnectionError.connectionFailed(\"sqlite\", toError(error).message);\n }\n default:\n throw ConfigurationError.unknownDatabaseType(config.database);\n }\n \n }\n\n static async createEmpty(configFile:string){\n const configReader = new FileMigrationConfigReader(configFile);\n const config = configReader.loadFile();\n return new MigrationRunnerFactory().createEmpty(config)\n }\n\n\n async create(config:MigrationConfig,conn:any):Promise<MySQLMigrationRunner>{\n let sqlrunner:ISQLRunner\n switch(config.database){\n case \"sql\":\n sqlrunner = new SQLRunner(conn)\n break;\n case \"sqlite\":\n sqlrunner = new SQLiteRunner(conn)\n break;\n default:\n throw ConfigurationError.unknownDatabaseType(config.database);\n }\n const setup = new MigrationSetup(sqlrunner,config);\n const read_strategy = this.getReadStategy(config)\n\n const migration_files = new MigrationDirectoryReader(config.migration_folder,read_strategy,sqlrunner)\n\n // Ensure the migrations infrastructure (i.e. the migrations table)\n // exists before we hand the runner back to the caller – most callers\n // expect the runner to be immediately usable without having to invoke\n // `setup()` manually. This is particularly important for projects\n // (like Groom) that directly call `getMigrations()` and\n // `migrate(...)` after obtaining the runner.\n await setup.setup();\n\n return new MySQLMigrationRunner(config,migration_files,setup,sqlrunner,conn);\n }\n\n\n async createEmpty(config:MigrationConfig):Promise<MySQLMigrationRunner>{\n const conn = null as any\n let sqlrunner:ISQLRunner\n\n switch(config.database){\n case \"sql\":\n sqlrunner = new SQLRunner(conn)\n break;\n case \"sqlite\":\n sqlrunner = new SQLiteRunner(conn)\n break;\n default:\n throw ConfigurationError.unknownDatabaseType(config.database);\n }\n const setup = new MigrationSetup(sqlrunner,config);\n const read_strategy = this.getReadStategy(config)\n \n const migration_files = new MigrationDirectoryReader(config.migration_folder,read_strategy,sqlrunner)\n return new MySQLMigrationRunner(config,migration_files,setup,sqlrunner,conn);\n }\n\n private getReadStategy(config:MigrationConfig){\n switch(config.database){\n case \"sql\":\n return dialect.MySqlDialectParser\n case \"sqlite\":\n return dialect.SqliteDialectParser\n default:\n throw ConfigurationError.unknownDatabaseType(config.database);\n }\n }\n}\n\n\nexport interface MigrationHistory{\n name:string\n up:string\n down:string\n}\n\nexport interface IMigrationRunner {\n setup(): Promise<void>;\n terminate(): Promise<void>;\n getMigrationsHistory(): Promise<MigrationHistory[]>;\n getMigrations(): Promise<MigrationNode[]>;\n getPendingMigrations(): Promise<MigrationNode[]>;\n getCompletedMigrations(): Promise<MigrationNode[]>;\n migrate(migrationNodes: MigrationNode[], forward: boolean): Promise<void>;\n reset(): Promise<void>;\n createMigration(name: string): void;\n close(): Promise<void>;\n init(config_file: string): Promise<void>;\n}\n\nexport class MySQLMigrationRunner implements IMigrationRunner {\n constructor(\n private config:MigrationConfig,\n private directory:MigrationDirectoryReader,\n private setupRunner:MigrationSetup,\n private sqlrunner:ISQLRunner,\n private connection:mysql.Connection\n){\n\n }\n\n async setup(){\n try {\n await this.setupRunner.setup();\n } catch (error) {\n throw new MigrationExecutionError('Failed to set up migration database', undefined, undefined, toError(error));\n }\n }\n\n async terminate(){\n try {\n await this.setupRunner.teardown();\n } catch (error) {\n throw new MigrationExecutionError('Failed to tear down migration database', undefined, undefined, toError(error));\n }\n }\n\n async getMigrationsHistory():Promise<MigrationHistory[]>{\n try {\n const results = await this.sqlrunner.query(`\n select * \n from ${this.config.migration_table}\n `);\n return results[0] as any;\n } catch (error) {\n throw new MigrationExecutionError('Failed to get migration history', undefined, undefined, toError(error));\n }\n }\n\n\n async getMigrations(){\n try {\n return this.directory.loadMigrations(this.config.migration_table,this.connection);\n } catch (error) {\n throw new MigrationExecutionError('Failed to load migrations', undefined, undefined, toError(error));\n }\n }\n\n async getPendingMigrations(){\n try {\n const migrations = await this.getMigrations();\n return migration_filter(migrations,false);\n } catch (error) {\n if (error instanceof MigrationExecutionError) {\n throw error;\n }\n throw new MigrationExecutionError('Failed to get pending migrations', undefined, undefined, toError(error));\n }\n }\n\n async getCompletedMigrations(){\n try {\n const migrations = await this.getMigrations();\n return migration_filter(migrations,true);\n } catch (error) {\n if (error instanceof MigrationExecutionError) {\n throw error;\n }\n throw new MigrationExecutionError('Failed to get completed migrations', undefined, undefined, toError(error));\n }\n }\n\n async migrate(migrationNodes:MigrationNode[],forward:boolean):Promise<void>{\n for(let node of migrationNodes){\n try {\n if(forward){\n await node.up();\n }else{\n await node.down();\n }\n } catch (error) {\n throw new MigrationExecutionError(\n `Failed to ${forward ? 'apply' : 'rollback'} migration`,\n node.name || String(node),\n forward ? node.up_sql() : node.down_sql(),\n toError(error)\n );\n }\n }\n }\n\n\n async reset(){\n try {\n let migrations = await this.getMigrations();\n const rollback = await migration_filter(migrations,true);\n await this.migrate(rollback.reverse(),false);\n migrations = await this.getMigrations();\n const rollforward = await migration_filter(migrations,false);\n await this.migrate(rollforward,true);\n } catch (error) {\n if (error instanceof MigrationExecutionError) {\n throw error;\n }\n throw new MigrationExecutionError('Failed to reset migrations', undefined, undefined, toError(error));\n }\n }\n\n\n createMigration(name:string){\n try {\n const creator = new MigrationCreator(this.config);\n creator.create(name);\n } catch (error) {\n throw new MigrationExecutionError(`Failed to create migration: ${name}`, undefined, undefined, toError(error));\n }\n }\n\n\n async close(){\n if (this.sqlrunner) {\n try {\n await this.sqlrunner.end();\n } catch (error) {\n throw new DatabaseConnectionError(`Failed to close database connection: ${toError(error).message}`);\n }\n }\n }\n\n\n async init(config_file:string){\n try {\n console.log(`Checking for ${config_file}`);\n const config_exist = fs.existsSync(config_file);\n\n if(!config_exist){\n console.log(`Creating ${config_file}`);\n const default_config = {\n \"migration_folder\":\"migrations\",\n \"migration_table\":\"proper_migrations\",\n \"database\": \"sql\",\n \"sql\":{\n \"host\":\"localhost\",\n \"user\":\"root\",\n \"database\":\"proper\",\n \"password\":\"\"\n }\n };\n fs.writeFileSync(config_file,JSON.stringify(default_config,null,2)); \n console.log(`Created ${config_file}`);\n }\n } catch (error) {\n throw new ConfigurationError(`Failed to initialize config file: ${toError(error).message}`);\n }\n }\n}\n\n\nexport class FileMigrationConfigReader{\n\n constructor(private configFile:string){\n\n }\n\n loadFile(){\n try {\n const fileContent = fs.readFileSync(this.configFile);\n const config:MigrationConfig = JSON.parse(fileContent.toString());\n \n // Validate required fields\n if (!config.migration_folder) {\n throw ConfigurationError.missingRequiredProperty('migration_folder');\n }\n \n if (!config.migration_table) {\n throw ConfigurationError.missingRequiredProperty('migration_table');\n }\n \n // For backward compatibility: if database type is omitted we derive\n // it from the presence of the dialect specific configuration\n // blocks. This keeps older `proper.json` files – that only defined\n // an \"sql\" or \"sqlite\" section – working without modification.\n if (!config.database) {\n if (config.sql) {\n config.database = 'sql';\n } else if (config.sqlite) {\n config.database = 'sqlite';\n } else {\n throw ConfigurationError.missingRequiredProperty('database');\n }\n }\n \n return config;\n } catch (error) {\n if (error instanceof ConfigurationError) {\n throw error;\n }\n const err = toError(error);\n if (err.message.includes('ENOENT')) {\n throw new ConfigurationError(`Config file not found: ${this.configFile}`);\n }\n throw new ConfigurationError(`Failed to load config file: ${err.message}`);\n }\n }\n}\n\n\nexport class MigrationCreator{\n constructor(private config:MigrationConfig){\n \n }\n\n\n create(name:string){\n if (!name) {\n throw new CLIError('Migration name is required');\n }\n\n try {\n // Ensure the migration folder exists\n if (!fs.existsSync(this.config.migration_folder)) {\n fs.mkdirSync(this.config.migration_folder, { recursive: true });\n }\n\n const now_timestamp = Date.now();\n\n const filename_up = `${now_timestamp}_${name}.up.sql`;\n const filename_down = `${now_timestamp}_${name}.down.sql`;\n\n fs.writeFileSync(`${this.config.migration_folder}/${filename_up}`,`\n-- Write your up migration here\n `.trim());\n\n fs.writeFileSync(`${this.config.migration_folder}/${filename_down}`,`\n-- Write your down migration here\n `.trim());\n \n console.log(`Created migration files:`);\n console.log(` ${filename_up}`);\n console.log(` ${filename_down}`);\n } catch (error) {\n if (error instanceof CLIError) {\n throw error;\n }\n throw new MigrationExecutionError(`Failed to create migration files: ${toError(error).message}`);\n }\n }\n}","import fs from \"fs\";\nimport path from \"path\";\nimport { MigrationNode } from './MigrationNode';\nimport { SqlMigrationBuilder } from './SqlMigrationBuilder';\nimport mysql from \"mysql2/promise\";\nimport { ISQLRunner } from \"./SQLRunner\";\n\n\nexport interface MigrationOptions{\n conn:mysql.Connection\n}\n\nexport class MigrationDirectoryReader {\n\n constructor(private directory: string,private read_strategy:any,private sqlrunner:ISQLRunner) {\n \n }\n\n loadMigrations(table:string,connection:any):MigrationNode[] {\n\n fs.existsSync(this.directory) || fs.mkdirSync(this.directory);\n const dir_content = fs.readdirSync(this.directory,{withFileTypes:true}).filter(file=>file.isFile()).map(file=>file.name);\n\n let migration_files = dir_content.map(file => {\n return {\n migration_key: file.replace(/\\.(up|down)\\.(sql|js)/i, \"\").toLowerCase(),\n directory: this.directory,\n relative_path: path.join(this.directory, file),\n file,\n };\n }).sort();\n\n\n const migration_sorter: any = {};\n\n migration_files.forEach((migration) => {\n const builder = migration_sorter[migration.migration_key] = migration_sorter[migration.migration_key] || new SqlMigrationBuilder(migration.migration_key);\n this.loadMigration(builder, migration.relative_path);\n });\n\n\n const keys = Object.keys(migration_sorter)\n\n keys.sort()\n\n const built = keys.map(key=>{\n return migration_sorter[key].build(table,this.sqlrunner) \n })\n return built\n\n }\n\n\n loadMigration(builder: SqlMigrationBuilder, file: string) {\n const is_sql = /sql$/i.test(file);\n const is_js = /js$/i.test(file);\n const is_up = /up\\.(js|sql)/i.test(file);\n const is_down = /down\\.(js|sql)/i.test(file);\n if (is_sql && is_up) {\n const content = this.sql_up(file);\n builder.set_up(file,content);\n } else if (is_sql && is_down) {\n const content = this.sql_down(file);\n builder.set_down(file,content);\n } else {\n throw new Error(`Invalid migration file: ${file}`);\n }\n return builder;\n }\n\n\n sql_up(file: string) {\n let content = fs.readFileSync(file).toString();\n content = this.read_strategy(content);\n return content.trim();\n }\n\n sql_down(file: string) {\n let content = fs.readFileSync(file).toString();\n content = this.read_strategy(content);\n return content.trim();\n }\n\n}\n","/**\n * Base class for all migration-related errors\n */\nexport class MigrationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'MigrationError';\n // This is needed to make instanceof work correctly in ES5\n Object.setPrototypeOf(this, MigrationError.prototype);\n }\n}\n\n/**\n * Error thrown when there is an issue with the configuration\n */\nexport class ConfigurationError extends MigrationError {\n constructor(message: string) {\n super(`Configuration Error: ${message}`);\n this.name = 'ConfigurationError';\n Object.setPrototypeOf(this, ConfigurationError.prototype);\n }\n\n /**\n * Helper method for missing database configuration\n * @param dbType The database type (e.g., 'sql', 'sqlite')\n * @returns A ConfigurationError with appropriate message\n */\n static missingDatabaseConfiguration(dbType: string): ConfigurationError {\n return new ConfigurationError(`Missing ${dbType} configuration`);\n }\n\n /**\n * Helper method for unknown database type\n * @param dbType The unknown database type\n * @returns A ConfigurationError with appropriate message\n */\n static unknownDatabaseType(dbType: string): ConfigurationError {\n return new ConfigurationError(`Unknown database type: ${dbType}`);\n }\n\n /**\n * Helper method for missing required configuration properties\n * @param property The name of the missing property\n * @returns A ConfigurationError with appropriate message\n */\n static missingRequiredProperty(property: string): ConfigurationError {\n return new ConfigurationError(`Missing required property: ${property}`);\n }\n}\n\n/**\n * Error thrown when there is an issue with the database connection\n */\nexport class DatabaseConnectionError extends MigrationError {\n constructor(message: string) {\n super(`Database Connection Error: ${message}`);\n this.name = 'DatabaseConnectionError';\n Object.setPrototypeOf(this, DatabaseConnectionError.prototype);\n }\n\n /**\n * Helper method for connection errors\n * @param dbType The database type (e.g., 'sql', 'sqlite')\n * @param details Additional error details\n * @returns A DatabaseConnectionError with appropriate message\n */\n static connectionFailed(dbType: string, details?: string): DatabaseConnectionError {\n const message = details \n ? `Failed to connect to ${dbType} database: ${details}`\n : `Failed to connect to ${dbType} database`;\n return new DatabaseConnectionError(message);\n }\n\n /**\n * Helper method for authentication errors\n * @param dbType The database type (e.g., 'sql', 'sqlite')\n * @returns A DatabaseConnectionError with appropriate message\n */\n static authenticationFailed(dbType: string): DatabaseConnectionError {\n return new DatabaseConnectionError(`Authentication failed for ${dbType} database`);\n }\n}\n\n/**\n * Error thrown when there is an issue with executing a migration\n */\nexport class MigrationExecutionError extends MigrationError {\n constructor(\n message: string,\n public readonly migrationName?: string,\n public readonly sql?: string,\n public readonly originalError?: Error\n ) {\n /*\n * The public `message` that gets surfaced to callers **must not** include the\n * raw SQL text. The SQL string is already exposed through the dedicated\n * `sql` property and adding it to the message makes it unnecessarily noisy\n * and difficult to assert against in unit-tests. Therefore we only embed\n * the essential information (error type, optional migration name and the\n * short error message) in the main message string while keeping the full\n * SQL available separately.\n */\n let fullMessage = `Migration Execution Error${migrationName ? ` in '${migrationName}'` : ''}: ${message}`;\n\n if(originalError){\n fullMessage += `\\nOriginal Error:\\n${originalError.message}`;\n }\n\n super(fullMessage);\n this.name = 'MigrationExecutionError';\n Object.setPrototypeOf(this, MigrationExecutionError.prototype);\n }\n\n /**\n * Helper method for SQL execution errors\n * @param migrationName The name of the migration\n * @param sql The SQL that caused the error\n * @param originalError The original error thrown by the database driver\n * @returns A MigrationExecutionError with appropriate message\n */\n static sqlExecutionFailed(migrationName: string, sql: string, originalError: Error): MigrationExecutionError {\n return new MigrationExecutionError(\n originalError.message,\n migrationName,\n sql,\n originalError\n );\n }\n\n /**\n * Helper method for missing migration file errors\n * @param filename The missing file\n * @returns A MigrationExecutionError with appropriate message\n */\n static missingMigrationFile(filename: string): MigrationExecutionError {\n return new MigrationExecutionError(`Migration file not found: ${filename}`);\n }\n\n /**\n * Helper method for invalid migration file format errors\n * @param filename The invalid file\n * @param details Additional error details\n * @returns A MigrationExecutionError with appropriate message\n */\n static invalidMigrationFile(filename: string, details?: string): MigrationExecutionError {\n const message = details\n ? `Invalid migration file format in ${filename}: ${details}`\n : `Invalid migration file format in ${filename}`;\n return new MigrationExecutionError(message);\n }\n}\n\n/**\n * Error thrown when there is an issue with the migration CLI\n */\nexport class CLIError extends MigrationError {\n constructor(message: string) {\n super(`CLI Error: ${message}`);\n this.name = 'CLIError';\n Object.setPrototypeOf(this, CLIError.prototype);\n }\n\n /**\n * Helper method for missing command errors\n * @returns A CLIError with appropriate message\n */\n static missingCommand(): CLIError {\n return new CLIError('No command specified. Run with --help for usage information.');\n }\n\n /**\n * Helper method for unknown command errors\n * @param command The unknown command\n * @returns A CLIError with appropriate message\n */\n static unknownCommand(command: string): CLIError {\n return new CLIError(`Unknown command: ${command}. Run with --help for usage information.`);\n }\n\n /**\n * Helper method for missing required argument errors\n * @param argument The missing argument\n * @returns A CLIError with appropriate message\n */\n static missingRequiredArgument(argument: string): CLIError {\n return new CLIError(`Missing required argument: ${argument}`);\n }\n}\n","import { Connection } from 'mysql2/promise';\nimport { MigrationStatus } from './MigrationStatus';\nimport { ISQLRunner } from './SQLRunner';\nimport { MigrationExecutionError } from './errors';\n\nexport abstract class MigrationNode {\n name: string;\n\n constructor(name?: string) {\n this.name = name || '';\n }\n\n abstract get_key():string;\n abstract status(): Promise<MigrationStatus>;\n abstract up(): Promise<void>;\n abstract down(): Promise<void>;\n abstract up_sql(): string;\n abstract down_sql(): string;\n}\n\n\nexport class SqlMigrationNode extends MigrationNode {\n up_sql(): string {\n return this.sql_up;\n }\n down_sql(): string {\n return this.sql_down;\n }\n get_key(): string {\n return this.key;\n }\n\n constructor(private conn:ISQLRunner,\n private table:string, \n private key:string,\n private up_file:string,\n private sql_up:string,\n private down_file:string,\n private sql_down:string) {\n super(key);\n }\n\n async status(): Promise<MigrationStatus> {\n try {\n const result = await this.conn.query(`\n select * \n from ${this.table}\n where migration_key = ?;\n `, [this.key]);\n\n if(result.length > 0 && result[0].length > 0){\n return {\n completed: true\n };\n } else {\n return {\n completed: false\n };\n }\n } catch (error) {\n throw new MigrationExecutionError(\n `Error checking status for migration`,\n this.key,\n undefined,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n async up(): Promise<void> {\n try {\n await this.conn.execute(this.sql_up);\n } catch (error) {\n throw new MigrationExecutionError(\n `Error executing UP migration`,\n this.key,\n this.sql_up,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n \n try {\n await this.conn.execute(`\n insert into ${this.table} (migration_key,up,down)\n values (?,?,?)\n `, [this.key, this.up_file, this.down_file]);\n } catch (error) {\n throw new MigrationExecutionError(\n `Error recording migration completion`,\n this.key,\n undefined,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n async down(): Promise<void> {\n try {\n await this.conn.execute(this.sql_down);\n } catch (error) {\n throw new MigrationExecutionError(\n `Error executing DOWN migration`,\n this.key,\n this.sql_down,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n \n try {\n await this.conn.execute(`\n delete from ${this.table}\n where migration_key = ?\n `, [this.key]);\n } catch (error) {\n throw new MigrationExecutionError(\n `Error removing migration record`,\n this.key,\n undefined,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n}\n\n\nexport class SqliteMigrationNode extends MigrationNode {\n up_sql(): string {\n return this.sql_up;\n }\n down_sql(): string {\n return this.sql_down;\n }\n constructor(\n private conn: ISQLRunner, // your SQLite runner\n private table: string,\n private key: string,\n private up_file: string,\n private sql_up: string,\n private down_file: string,\n private sql_down: string\n ) {\n super(key);\n }\n\n /**\n * Returns the unique key for this migration (e.g. timestamp + name).\n */\n get_key(): string {\n return this.key;\n }\n\n /**\n * Check if this migration is already completed by looking in the migration table.\n */\n async status(): Promise<MigrationStatus> {\n try {\n // Usually `this.conn.query(...)` returns an array of rows.\n // The first element might be the row set, depending on your runner.\n const cursor = await this.conn.query(\n `SELECT * FROM ${this.table} WHERE migration_key = ?;`,\n [this.key]\n );\n\n // Usually, `cursor[0]` is the row array in many MySQL runners;\n // In a SQLite scenario, it may be just `cursor`\n // So adjust depending on your actual runner's return shape.\n const rows = cursor[0] as any[];\n\n if (rows && rows.length > 0) {\n return { completed: true };\n } else {\n return { completed: false };\n }\n } catch (error) {\n throw new MigrationExecutionError(\n `Error checking status for migration`,\n this.key,\n undefined,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Run the 'up' SQL, then record this migration in the table.\n */\n async up(): Promise<void> {\n try {\n // Execute the 'up' script\n await this.conn.execute(this.sql_up);\n } catch (error) {\n throw new MigrationExecutionError(\n `Error executing UP migration`,\n this.key,\n this.sql_up,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n\n // Insert a record of this migration having completed\n try {\n await this.conn.execute(\n `\n INSERT INTO ${this.table} (migration_key, up, down)\n VALUES (?, ?, ?)\n `,\n [this.key, this.up_file, this.down_file]\n );\n } catch (error) {\n throw new MigrationExecutionError(\n `Error recording migration completion`,\n this.key,\n undefined,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Run the 'down' SQL, then remove this migration record from the table.\n */\n async down(): Promise<void> {\n try {\n // Execute the 'down' script\n await this.conn.execute(this.sql_down);\n } catch (error) {\n throw new MigrationExecutionError(\n `Error executing DOWN migration`,\n this.key,\n this.sql_down,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n\n // Remove the migration from the table\n try {\n await this.conn.execute(\n `\n DELETE FROM ${this.table}\n WHERE migration_key = ?\n `,\n [this.key]\n );\n } catch (error) {\n throw new MigrationExecutionError(\n `Error removing migration record`,\n this.key,\n undefined,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n}","import { Connection } from 'mysql2/promise';\nimport { SqlMigrationNode } from './MigrationNode';\nimport { ISQLRunner } from './SQLRunner';\n\n\n\nexport class SqlMigrationBuilder {\n\n private up: string = '';\n private up_file: string = '';\n private down: string = '';\n private down_file: string = '';\n\n constructor(private key: string) {\n }\n\n set_up(file:string,up: string) {\n this.up_file = file;\n this.up = up;\n }\n\n set_down(file:string,down: string) {\n this.down_file = file;\n this.down = down;\n }\n\n build(table:string,conn: ISQLRunner) {\n return new SqlMigrationNode(conn, table,this.key, this.up_file, this.up, this.down_file,this.down);\n }\n}\n","import { MigrationConfig } from \"./MigrationConfig\";\nimport fs from \"fs\";\nimport { ISQLRunner } from './SQLRunner';\n\nexport class MigrationSetup {\n constructor(private sqlrunner: ISQLRunner, private config: MigrationConfig) {}\n\n\n async setup() {\n\n fs.existsSync(this.config.migration_folder) || fs.mkdirSync(this.config.migration_folder);\n\n const tableName = this.config.migration_table;\n\n // Use dialect-specific DDL so that the migrations table can be created\n // both in MySQL/MariaDB and in SQLite. The two dialects differ mainly\n // in the auto-increment syntax and the column types.\n const createTableSql = this.config.database === 'sqlite'\n ? `CREATE TABLE IF NOT EXISTS ${tableName} (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n migration_key TEXT,\n up TEXT,\n down TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )`\n : `CREATE TABLE IF NOT EXISTS ${tableName} (\n id INT AUTO_INCREMENT PRIMARY KEY,\n migration_key VARCHAR(255),\n up VARCHAR(255),\n down VARCHAR(255),\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )`;\n\n await this.sqlrunner.query(createTableSql);\n }\n\n async teardown() {\n await this.sqlrunner.execute(`DROP TABLE ${this.config.migration_table}`);\n }\n}\n\n\n\n\n","import { MigrationNode } from \"./MigrationNode\";\n\n\n\n\nexport async function migration_filter(migrations:MigrationNode[],completed:boolean=true,keep:MigrationNode[]=[]){\n if(migrations.length === 0){\n return keep\n }\n\n const [first,...rest] = migrations\n\n const status = await first.status()\n\n if(status.completed == completed){\n keep.push(first)\n }\n\n return migration_filter(rest,completed,keep)\n}","export function MySqlDialectParser(sql: string): string {\n const lines = sql.split('\\n');\n let result = '';\n \n // We start \"in MySQL\" so that lines before [sql] are kept.\n let isInMySQLBlock = true;\n \n // e.g. `-- [sql]`, `-- [ sql ]`, `-- [mysql]`, ignoring case/spaces\n const startSQLRegex = /^\\s*--\\s*\\[\\s*(sql|mysql)\\s*\\]\\s*$/i;\n // e.g. `-- [sqlite]`, `-- [ pg ]`, `-- [mydialect]` (any bracket means \"turn off MySQL\")\n const anyDialectRegex = /^\\s*--\\s*\\[\\s*\\w+\\s*\\]\\s*$/i;\n \n for (const line of lines) {\n const trimmed = line.trim();\n \n // If line is \" -- [sql] \" or \" -- [mysql] \", we *turn on* MySQL capturing:\n if (startSQLRegex.test(trimmed)) {\n isInMySQLBlock = true;\n // We also include this line in output\n result += line + '\\n';\n continue;\n }\n \n // If line is *some other* bracket, e.g. \" -- [sqlite] \"\n // then switch MySQL off, and do NOT keep that line\n if (anyDialectRegex.test(trimmed) && !startSQLRegex.test(trimmed)) {\n isInMySQLBlock = false;\n // Skip to next line\n continue;\n }\n \n // If we get here and see a line without a [dialect] marker,\n // and we were previously turned off because of non-mysql dialect,\n // then check if it's common SQL (like CREATE INDEX).\n // Turn MySQL back on for this line\n if (!isInMySQLBlock && line.toLowerCase().includes('create index')) {\n isInMySQLBlock = true;\n }\n \n // If we're in MySQL mode, keep the line\n if (isInMySQLBlock) {\n result += line + '\\n';\n }\n }\n \n return result;\n}\n\nexport function SqliteDialectParser(sql: string): string {\n const lines = sql.split('\\n');\n let result = '';\n let isInSqliteBlock = true;\n\n // Regex to detect a line that starts Sqlite code block:\n const startSqliteRegex = /^\\s*--\\s*\\[\\s*sqlite\\s*\\]\\s*$/i;\n const anyDialectRegex = /^\\s*--\\s*\\[\\s*\\w+\\s*\\]\\s*$/i;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (startSqliteRegex.test(trimmed)) {\n // If we see a \"-- [sqlite]\" line, start capturing\n isInSqliteBlock = true;\n result += line + '\\n';\n continue;\n } else if (anyDialectRegex.test(trimmed) && !startSqliteRegex.test(trimmed)) {\n // If we see a *different* bracket line, turn off capturing\n isInSqliteBlock = false;\n continue;\n }\n \n // If we get here and see a line without a [dialect] marker,\n // and we were previously turned off because of non-sqlite dialect,\n // then check if it's common SQL (like CREATE INDEX).\n // Turn SQLite back on for this line\n if (!isInSqliteBlock && line.toLowerCase().includes('create index')) {\n isInSqliteBlock = true;\n }\n \n // If we are in the Sqlite block, capture the line\n if (isInSqliteBlock) {\n result += line + '\\n';\n }\n }\n \n return result;\n}","\nimport * as sqlite from 'sqlite';\nimport { Statement } from 'sqlite3';\nimport mysql from 'mysql2/promise';\n\n\n\ntype QueryResult = {\n stmt: Statement;\n lastID: number;\n changes: number;\n}\n\nexport interface ISQLRunner {\n query(sql: string, params?: any[]): Promise<any>;\n execute(sql: string, params?: any[]): Promise<any>;\n end(): Promise<void>;\n}\n\n/**\n * Base class that implements the common contract and helper utilities that are\n * shared between the different dialect runners. The concrete subclasses only\n * need to implement the three primitive methods `_query`, `_execute` and\n * `_end` that perform the actual driver-specific interaction. Everything else\n * – such as ensuring a uniform return shape – is handled here once.\n */\nabstract class BaseSQLRunner implements ISQLRunner {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n abstract _query(sql: string, params?: any[]): Promise<any>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n abstract _execute(sql: string, params?: any[]): Promise<any>;\n abstract _end(): Promise<void>;\n\n /**\n * Ensures that both MySQL and SQLite return the same tuple shape that callers\n * expect: `[rowsOrResult, extra]`. For SQLite there is no `extra` metadata\n * comparable to MySQL's `FieldPacket[]`, so we just use `undefined`.\n */\n async query(sql: string, params: any[] = []): Promise<any> {\n const result = await this._query(sql, params);\n\n // MySQL already returns the desired tuple. For SQLite we need to wrap.\n return Array.isArray(result) && result.length === 2 ? result : [result, undefined];\n }\n\n async execute(sql: string, params: any[] = []): Promise<any> {\n const result = await this._execute(sql, params);\n return Array.isArray(result) && result.length === 2 ? result : [result, undefined];\n }\n\n async end(): Promise<void> {\n await this._end();\n }\n}\n\nexport class SQLRunner extends BaseSQLRunner {\n constructor(private connection: mysql.Connection) {\n super();\n }\n\n // The MySQL driver already returns the correct tuple shapes.\n async _query(sql: string, params: any[] = []) {\n return await this.connection.query(sql, params);\n }\n\n async _execute(sql: string, params: any[] = []) {\n return await this.connection.execute(sql, params);\n }\n\n async _end() {\n if (this.connection) {\n await this.connection.end();\n }\n }\n}\n\nexport class SQLiteRunner extends BaseSQLRunner {\n constructor(private connection: sqlite.Database) {\n super();\n }\n\n async _query(sql: string, params: any[] = []) {\n const stmt = await this.connection.prepare(sql);\n try {\n const rows = await stmt.all(...params);\n return rows;\n } finally {\n await stmt.finalize();\n }\n }\n\n async _execute(sql: string, params: any[] = []) {\n if (this.isMultiStatement(sql)) {\n return await this.executeMultiStatement(sql, params)\n .catch((err) => {\n console.error((\n \"Error executing multi-statement SQL:\\n\"+\n `${sql}\\n`\n ), err);\n throw err;\n });\n }\n \n const stmt = await this.connection.prepare(sql);\n try {\n const info = await stmt.run(...params);\n return info;\n } finally {\n await stmt.finalize();\n }\n }\n\n private isMultiStatement(sql: string): boolean {\n // SQLite does not support multiple statements in a single query.\n return sql.split(';').length > 1;\n }\n\n private async executeMultiStatement(sql: string, params: any[] = []) {\n const statements = sql.split(';')\n .map((s) => s.split('\\n')\n .map(\n s=>this.removeComments(s))\n .filter(s=>s.trim()!='')\n .join('\\n')\n )\n .filter((s) => s.trim() !== '');\n \n const infos = await statements.reduce(async (prev,statement) => {\n const infos = await prev;\n const stmt = await this.connection.prepare(`${statement};`);\n try {\n const info = await stmt.run(...params);\n infos.push(info);\n return infos;\n }\n finally {\n await stmt.finalize();\n }\n },Promise.resolve([null] as any[]))\n .then((infos:QueryResult[]) => {\n return infos.filter((info) => info !== null);\n })\n return infos.reduce((acc,latest)=>{\n if(latest){\n acc.stmt = latest.stmt;\n acc.lastID = latest.lastID;\n acc.changes += latest.changes;\n }\n return acc\n })\n }\n\n private removeComments(sql: string): string {\n // Remove single-line comments\n sql = sql.replace(/--.*$/gm, '');\n return sql;\n }\n\n\n async _end() {\n if (this.connection) {\n await this.connection.close();\n }\n }\n}\n"],"mappings":"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","names":["fs","fs","fs","s","infos","fs"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noego/proper",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "bin/index.js",
|
|
6
|
+
"module": "bin/index.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./bin/index.mjs",
|
|
10
|
+
"require": "./bin/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./cli": {
|
|
13
|
+
"import": "./bin/cli.mjs",
|
|
14
|
+
"require": "./bin/cli.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"proper": "bin/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin",
|
|
22
|
+
"readme.md"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "jest",
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"prepublishOnly": "npm run build",
|
|
28
|
+
"migrate:sqlite": "npx tsx cli.ts --config sqlite/proper.json up"
|
|
29
|
+
},
|
|
30
|
+
"author": "Shavauhn Gabay",
|
|
31
|
+
"license": "ISC",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/jest": "^29.5.12",
|
|
34
|
+
"@types/node": "^20.11.25",
|
|
35
|
+
"jest": "^29.7.0",
|
|
36
|
+
"jest-extended": "^4.0.2",
|
|
37
|
+
"ts-jest": "^29.2.5",
|
|
38
|
+
"ts-node": "^10.9.2",
|
|
39
|
+
"tsup": "^8.5.0",
|
|
40
|
+
"tsx": "^4.19.4",
|
|
41
|
+
"typescript": "^5.3.3",
|
|
42
|
+
"typesync": "^0.13.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@types/argparse": "^2.0.15",
|
|
46
|
+
"@types/minimist": "^1.2.5",
|
|
47
|
+
"argparse": "^2.0.1",
|
|
48
|
+
"console-table-printer": "^2.12.0",
|
|
49
|
+
"minimist": "^1.2.8",
|
|
50
|
+
"mysql2": "^3.9.2",
|
|
51
|
+
"source-map-support": "^0.5.21",
|
|
52
|
+
"sqlite": "^5.1.1",
|
|
53
|
+
"sqlite3": "^5.1.7"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
## SQL Proper
|
|
2
|
+
|
|
3
|
+
**SQL Proper** is a lightweight and straightforward SQL migration tool for both **MySQL/MariaDB** (via `mysql2`) and **SQLite**. It allows you to:
|
|
4
|
+
|
|
5
|
+
- Easily run migrations (`up`) or roll them back (`down`)
|
|
6
|
+
- Reset your database to a clean state
|
|
7
|
+
- Check migration status
|
|
8
|
+
- Create new migration files
|
|
9
|
+
- Seamlessly manage your migration process via a simple **CLI** or by **importing** the library in your Node.js project
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick Start (CLI)
|
|
14
|
+
|
|
15
|
+
Below is a fast-track on **how to work** with SQL Proper. Skip to [Configuration](#configuration) to learn how to connect to your database.
|
|
16
|
+
|
|
17
|
+
1. **Install** SQL Proper globally or locally:
|
|
18
|
+
```bash
|
|
19
|
+
# Globally (optional)
|
|
20
|
+
npm install -g @noego/proper
|
|
21
|
+
|
|
22
|
+
# Or locally to your project:
|
|
23
|
+
npm install --save @noego/proper
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. **Initialize a config file** (creates `proper.json` by default):
|
|
27
|
+
```bash
|
|
28
|
+
proper init
|
|
29
|
+
```
|
|
30
|
+
You can specify a custom file:
|
|
31
|
+
```bash
|
|
32
|
+
proper init --config path/to/my-config.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This generates a `proper.json` with default settings. By default it targets MySQL (`"database": "sql"`). Switch to SQLite by setting `"database": "sqlite"` and filling the `sqlite` block. Ensure it contains at least:
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"migration_folder": "migrations",
|
|
39
|
+
"migration_table": "proper_migrations",
|
|
40
|
+
"database": "sqlite",
|
|
41
|
+
"sqlite": { "database": "db.sqlite" }
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Also create a `migrations/` directory in your project root to hold `.up.sql` and `.down.sql` files. File names follow `<timestamp>_<name>.up.sql` and `<timestamp>_<name>.down.sql`.
|
|
46
|
+
|
|
47
|
+
3. **Create a new migration** (generates timestamped `.up.sql` & `.down.sql` files):
|
|
48
|
+
```bash
|
|
49
|
+
proper create --name "create_users_table"
|
|
50
|
+
```
|
|
51
|
+
Edit those `.sql` files and add your migration scripts.
|
|
52
|
+
|
|
53
|
+
4. **Apply migrations**:
|
|
54
|
+
```bash
|
|
55
|
+
proper up
|
|
56
|
+
```
|
|
57
|
+
- Applies all **pending** migrations.
|
|
58
|
+
- Use `--increment <number>` to limit the count, e.g. `proper up --increment 1`.
|
|
59
|
+
|
|
60
|
+
5. **Roll back migrations**:
|
|
61
|
+
```bash
|
|
62
|
+
proper down
|
|
63
|
+
```
|
|
64
|
+
- Rolls back the **latest** applied migrations.
|
|
65
|
+
- Use `--increment <number>` to limit the count, e.g. `proper down --increment 1`.
|
|
66
|
+
|
|
67
|
+
6. **Reset your database**:
|
|
68
|
+
```bash
|
|
69
|
+
proper reset
|
|
70
|
+
```
|
|
71
|
+
- Rolls back **all** migrations, then re-applies them.
|
|
72
|
+
|
|
73
|
+
7. **Check status**:
|
|
74
|
+
```bash
|
|
75
|
+
proper status
|
|
76
|
+
```
|
|
77
|
+
- Prints a table of all migrations and their status (`completed` or `pending`).
|
|
78
|
+
|
|
79
|
+
**All commands** accept a `--config <path>` to specify a config file other than the default `proper.json`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
Before you run migrations, SQL Proper needs to know how to connect to your DB and where to store the migration files.
|
|
86
|
+
Here’s a sample `proper.json` for **SQLite** (change `"database": "sql"` if using MySQL):
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"migration_folder": "migrations",
|
|
91
|
+
"migration_table": "proper_migrations",
|
|
92
|
+
"database": "sqlite",
|
|
93
|
+
"sql": {
|
|
94
|
+
"host": "localhost",
|
|
95
|
+
"user": "root",
|
|
96
|
+
"database": "proper",
|
|
97
|
+
"password": "password123"
|
|
98
|
+
},
|
|
99
|
+
"sqlite": {
|
|
100
|
+
"database": ":memory:"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- **migration_folder**: The directory holding your `.up.sql` and `.down.sql` files.
|
|
106
|
+
- **migration_table**: Name of the DB table that keeps track of applied migrations.
|
|
107
|
+
- **database**: Either `"sql"` (for MySQL) or `"sqlite"`.
|
|
108
|
+
- If using `"sql"`, fill out the `sql` object (`host`, `user`, `password`, `database`).
|
|
109
|
+
- If using `"sqlite"`, define `"sqlite": { "database": "<path or :memory:>" }`.
|
|
110
|
+
- **host** / **user** / **password** / **database**: Standard MySQL connection details if `"database": "sql"`.
|
|
111
|
+
|
|
112
|
+
Use `proper init` to quickly generate a default config, or manually create your own. When running the CLI, if your config isn’t in `proper.json`, specify `--config`:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
proper up --config path/to/my-custom.json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Usage in Code (Import)
|
|
121
|
+
|
|
122
|
+
If you prefer integrating migrations in your Node.js scripts, import SQL Proper:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { MigrationRunnerFactory } from "@noego/proper";
|
|
126
|
+
|
|
127
|
+
(async () => {
|
|
128
|
+
// Suppose "myconfig.json" is your config file
|
|
129
|
+
const runner = await MigrationRunnerFactory.create("myconfig.json");
|
|
130
|
+
await runner.setup();
|
|
131
|
+
|
|
132
|
+
// Apply pending migrations
|
|
133
|
+
const pending = await runner.getPendingMigrations();
|
|
134
|
+
await runner.migrate(pending, true);
|
|
135
|
+
|
|
136
|
+
console.log("Migrations applied!");
|
|
137
|
+
|
|
138
|
+
// Check completed migrations
|
|
139
|
+
const completed = await runner.getCompletedMigrations();
|
|
140
|
+
console.log("Completed:", completed);
|
|
141
|
+
|
|
142
|
+
// Wrap up
|
|
143
|
+
await runner.close();
|
|
144
|
+
})();
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Key methods**:
|
|
148
|
+
- `setup()`: Prepares DB environment (creates migration table if missing).
|
|
149
|
+
- `getMigrations()`: Loads all migrations from the configured folder.
|
|
150
|
+
- `getPendingMigrations()` / `getCompletedMigrations()`: Filtered migration sets.
|
|
151
|
+
- `migrate(nodes, forward)`: Executes migrations (forward = `true` for `up`).
|
|
152
|
+
- `reset()`: Rolls back everything, then reapplies them.
|
|
153
|
+
- `createMigration(name)`: Scaffolds `<timestamp>_<name>.up.sql` and `.down.sql`.
|
|
154
|
+
- `init(configPath)`: Creates a default `proper.json` if missing.
|
|
155
|
+
- `close()`: Closes the DB connection.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Examples
|
|
160
|
+
|
|
161
|
+
An [`example/`](example) directory is included with:
|
|
162
|
+
- A **docker-compose** setup for MySQL
|
|
163
|
+
- Sample migrations
|
|
164
|
+
- A sample `proper.json`
|
|
165
|
+
- A **Makefile** demonstrating typical commands:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Start MySQL in Docker
|
|
169
|
+
make db_up
|
|
170
|
+
|
|
171
|
+
# Stop MySQL
|
|
172
|
+
make db_down
|
|
173
|
+
|
|
174
|
+
# Apply migrations
|
|
175
|
+
make up
|
|
176
|
+
|
|
177
|
+
# Rollback
|
|
178
|
+
make down
|
|
179
|
+
|
|
180
|
+
# Reset DB
|
|
181
|
+
make reset
|
|
182
|
+
|
|
183
|
+
# Create migration
|
|
184
|
+
make create name="create_posts"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Environment
|
|
190
|
+
|
|
191
|
+
Provide sensitive values via environment variables when possible:
|
|
192
|
+
|
|
193
|
+
- `SQL_PASSWORD`: Provide your MySQL password via environment variable instead of storing it in the config file. If a password is set in your config, that value is used.
|
|
194
|
+
|
|
195
|
+
Example:
|
|
196
|
+
```bash
|
|
197
|
+
SQL_PASSWORD=supersecret proper up --config proper.json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Development
|
|
203
|
+
|
|
204
|
+
- Node: 16+ recommended
|
|
205
|
+
- Install: `npm install`
|
|
206
|
+
- Build: `npm run build` (emits to `bin/` via tsup)
|
|
207
|
+
- Test: `npm test`
|
|
208
|
+
- Run CLI from source (TypeScript): `npx tsx cli.ts --config sqlite/proper.json up`
|
|
209
|
+
|
|
210
|
+
Debugging in VS Code:
|
|
211
|
+
- Use the provided `.vscode/launch.json`:
|
|
212
|
+
- “Jest: Current File” runs the open test file
|
|
213
|
+
- “Jest: All Tests” runs the full suite
|
|
214
|
+
- “Jest: CLI Test File” runs only `tests/unit/cli.test.ts`
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Exit Codes
|
|
219
|
+
|
|
220
|
+
- `0`: success (e.g., `help`, completed operations)
|
|
221
|
+
- Non‑zero: CLI error (e.g., unknown command, missing `--name` for `create`, config validation failures)
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
ISC — (c) 2023–2025 Shavauhn Gabay
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Additional Notes
|
|
232
|
+
|
|
233
|
+
- Ensure your migration files follow the `<timestamp>_<name>.up.sql` / `.down.sql` pattern in the configured `migration_folder`.
|
|
234
|
+
- Back up your database before running migrations, especially in production.
|
|
235
|
+
- Use version control to track migration files and configurations.
|
|
236
|
+
- Test migrations in a staging environment before production.
|