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:
parent
ce4693d957
commit
0081f77e86
4 changed files with 291 additions and 20 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
40
pnpm-lock.yaml
generated
|
|
@ -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
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue