feat(emigrate): add some rough support for generating new migration files

And add some CLI args parsing and usage messages for upcoming commands as well
This commit is contained in:
Joakim Carlstein 2023-11-09 22:22:43 +01:00
parent ce4693d957
commit 0081f77e86
4 changed files with 291 additions and 20 deletions

View file

@ -49,7 +49,10 @@
}, },
"xo": { "xo": {
"space": true, "space": true,
"prettier": true "prettier": true,
"rules": {
"complexity": 0
}
}, },
"dependencies": { "dependencies": {
"@changesets/cli": "2.26.2", "@changesets/cli": "2.26.2",

View file

@ -25,16 +25,10 @@
"devDependencies": { "devDependencies": {
"@emigrate/tsconfig": "workspace:*" "@emigrate/tsconfig": "workspace:*"
}, },
"author": { "author": "Aboviq AB <dev@aboviq.com> (https://www.aboviq.com)",
"name": "Aboviq AB", "license": "MIT",
"email": "dev@aboviq.com", "dependencies": {
"url": "https://www.aboviq.com" "@emigrate/plugin-tools": "workspace:*",
}, "import-from-esm": "1.1.3"
"contributors": [ }
{
"name": "Joakim Carlstein",
"email": "joakim@aboviq.com"
}
],
"license": "MIT"
} }

View file

@ -1,4 +1,248 @@
#!/usr/bin/env node #!/usr/bin/env node
import { emigrate } from '.'; import process from 'node:process';
import { parseArgs } from 'node:util';
import { isGeneratorPlugin } from '@emigrate/plugin-tools';
import { type GeneratorPlugin } from '@emigrate/plugin-tools/types';
emigrate(); type Action = (args: string[]) => Promise<void>;
const up: Action = async (args) => {
const { values } = parseArgs({
args,
options: {
help: {
type: 'boolean',
short: 'h',
},
dir: {
type: 'string',
short: 'd',
},
plugin: {
type: 'string',
short: 'p',
multiple: true,
default: [],
},
},
allowPositionals: false,
});
const showHelp = !values.dir || values.help;
if (!values.dir) {
console.error('Missing required option: --dir\n');
}
if (showHelp) {
console.log(`Usage: emigrate up [options]
Run all pending migrations
Options:
-h, --help Show this help message and exit
-d, --dir The directory where the migration files are located (required)
-p, --plugin The plugin(s) to use (can be specified multiple times)
Examples:
emigrate up --dir src/migrations
emigrate up --dir ./migrations --plugin @emigrate/plugin-storage-mysql
`);
process.exitCode = 1;
return;
}
console.log(values);
};
const newMigration: Action = async (args) => {
const { values, positionals } = parseArgs({
args,
options: {
help: {
type: 'boolean',
short: 'h',
},
dir: {
type: 'string',
short: 'd',
},
plugin: {
type: 'string',
short: 'p',
multiple: true,
default: [],
},
},
allowPositionals: true,
});
const hasPositionals = positionals.join('').trim() !== '';
const showHelp = !values.dir || !hasPositionals || values.help;
if (!values.dir) {
console.error('Missing required option: --dir\n');
}
if (!hasPositionals) {
console.error('Missing required migration name: <name>\n');
}
if (showHelp) {
console.log(`Usage: emigrate new [options] <name>
Run all pending migrations
Options:
-h, --help Show this help message and exit
-d, --dir The directory where the migration files are located (required)
-p, --plugin The plugin(s) to use (can be specified multiple times)
Examples:
emigrate new --dir src/migrations create users table
emigrate new --dir ./migrations --plugin @emigrate/plugin-generate-sql create_users_table
`);
process.exitCode = 1;
return;
}
const { plugin: plugins = [] } = values;
if (plugins.length > 0) {
let generatorPlugin: GeneratorPlugin | undefined;
const path = await import('node:path');
for await (const plugin of plugins) {
const pluginPath = plugin.startsWith('.') ? path.resolve(process.cwd(), plugin) : plugin;
try {
const pluginModule: unknown = await import(pluginPath);
if (isGeneratorPlugin(pluginModule)) {
generatorPlugin = pluginModule;
break;
}
if (
pluginModule &&
typeof pluginModule === 'object' &&
'default' in pluginModule &&
isGeneratorPlugin(pluginModule.default)
) {
generatorPlugin = pluginModule.default;
break;
}
} catch (error) {
console.error(`Failed to load plugin: ${plugin}`);
if (error instanceof Error) {
console.error(error.message);
}
process.exitCode = 1;
return;
}
}
if (!generatorPlugin) {
console.error('No generator plugin found, please specify a generator plugin using the --plugin option\n');
process.exitCode = 1;
return;
}
const fs = await import('node:fs/promises');
const { filename, content } = await generatorPlugin.generate(positionals.join(' '));
const directory = path.resolve(process.cwd(), values.dir!);
try {
await fs.mkdir(directory, { recursive: true });
} catch (error) {
console.error(`Failed to create migration directory: ${directory}`);
if (error instanceof Error) {
console.error(error.message);
}
process.exitCode = 1;
return;
}
const file = path.resolve(directory, filename);
try {
await fs.writeFile(file, content);
console.log(`Created migration file: ${path.relative(process.cwd(), file)}`);
} catch (error) {
console.error(`Failed to write migration file: ${file}`);
if (error instanceof Error) {
console.error(error.message);
}
process.exitCode = 1;
}
}
};
const list: Action = async (args) => {
const { values } = parseArgs({
args,
options: {
help: {
type: 'boolean',
short: 'h',
},
plugin: {
type: 'string',
short: 'p',
multiple: true,
default: [],
},
},
allowPositionals: false,
});
console.log(values);
};
const commands: Record<string, Action> = {
up,
list,
new: newMigration,
};
const command = process.argv[2];
const action = command ? commands[command] : undefined;
if (!action) {
if (command) {
console.error(`Unknown command: ${command}\n`);
} else {
console.error('No command specified\n');
}
console.log(`Usage: emigrate <command>
Commands:
up Run all pending migrations
new Create a new migration file
list List all migrations
`);
process.exit(1);
}
try {
await action(process.argv.slice(3));
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exit(1);
}

40
pnpm-lock.yaml generated
View file

@ -1,5 +1,9 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers: importers:
.: .:
@ -45,9 +49,32 @@ importers:
version: 0.56.0(webpack@5.89.0) version: 0.56.0(webpack@5.89.0)
packages/emigrate: packages/emigrate:
dependencies:
'@emigrate/plugin-tools':
specifier: workspace:*
version: link:../plugin-tools
import-from-esm:
specifier: 1.1.3
version: 1.1.3
devDependencies: devDependencies:
'@emigrate/tsconfig': '@emigrate/tsconfig':
specifier: 0.0.0 specifier: workspace:*
version: link:../tsconfig
packages/plugin-generate-js:
dependencies:
'@emigrate/plugin-tools':
specifier: workspace:*
version: link:../plugin-tools
devDependencies:
'@emigrate/tsconfig':
specifier: workspace:*
version: link:../tsconfig
packages/plugin-tools:
devDependencies:
'@emigrate/tsconfig':
specifier: workspace:*
version: link:../tsconfig version: link:../tsconfig
packages/tsconfig: {} packages/tsconfig: {}
@ -3107,6 +3134,13 @@ packages:
resolve-from: 4.0.0 resolve-from: 4.0.0
dev: false dev: false
/import-from-esm@1.1.3:
resolution: {integrity: sha512-1BxFAthpQf5qabfPBaFBRAGIh8TVt6WB4ujqedfoF4oVjwyl6S/dZv26gL5kgPhbO1XBqu4hcELUlV/+IPsC3A==}
engines: {node: '>=16.20'}
dependencies:
import-meta-resolve: 4.0.0
dev: false
/import-meta-resolve@4.0.0: /import-meta-resolve@4.0.0:
resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==}
dev: false dev: false
@ -5849,7 +5883,3 @@ packages:
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
engines: {node: '>=12.20'} engines: {node: '>=12.20'}
dev: false dev: false
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false