fix(typescript): load config written in TypeScript without the typescript package when using Bun, Deno or tsx

This commit is contained in:
Joakim Carlstein 2024-02-09 13:50:16 +01:00 committed by Joakim Carlstein
parent 198aa545eb
commit c838ffb7f3
9 changed files with 132 additions and 32 deletions

View file

@ -0,0 +1,5 @@
---
'@emigrate/cli': minor
---
Make it possible to write the Emigrate configuration file in TypeScript and load it using `tsx` in a NodeJS environment by importing packages provided using the `--import` CLI option before loading the configuration file. This makes it possible to run Emigrate in production with a configuration file written in TypeScript without having the `typescript` package installed.

View file

@ -0,0 +1,5 @@
---
'@emigrate/docs': patch
---
Add note on how to write Emigrate's config using TypeScript in a production environment without having `typescript` installed.

View file

@ -0,0 +1,5 @@
---
'@emigrate/cli': patch
---
Don't use the `typescript` package for loading an Emigrate configuration file written in TypeScript in a Bun or Deno environment

View file

@ -7,14 +7,14 @@ import { Tabs, TabItem } from '@astrojs/starlight/components';
import Link from '@components/Link.astro';
:::tip[Using Bun or Deno?]
If you are using [Bun](https://bun.sh) or [Deno](https://deno.land) you are already good to go as they both support TypeScript out of the box.
If you are using [Bun](https://bun.sh) or [Deno](https://deno.land) you are already good to go as they both support TypeScript out of the box!
:::
You have at least the two following options to support running TypeScript migration files in NodeJS.
If you're using NodeJS you have at least the two following options to support running TypeScript migration files in NodeJS.
## Using `tsx`
If you want to be able to write and run migration files written in TypeScript the easiest way is to install the [`tsx`](https://github.com/privatenumber/tsx) package.
If you want to be able to write and run migration files written in TypeScript an easy way is to install the [`tsx`](https://github.com/privatenumber/tsx) package.
### Installing `tsx`
@ -67,9 +67,13 @@ Using the <Link href="/commands/up/#-i---import-module">`--import`</Link> flag y
</TabItem>
</Tabs>
:::note
This method is necessary if you want to write your configuration file in TypeScript without having `typescript` installed in your production environment, as `tsx` must be loaded before the configuration file is loaded.
:::
#### Via configuration file
You can also directly import `tsx` in your configuration file.
You can also directly import `tsx` in your configuration file (will only work if you're not using TypeScript for your configuration file).
```js title="emigrate.config.js" {1}
import 'tsx';

View file

@ -36,7 +36,9 @@
"immigration"
],
"devDependencies": {
"@emigrate/tsconfig": "workspace:*"
"@emigrate/tsconfig": "workspace:*",
"@types/bun": "1.0.5",
"bun-types": "1.0.26"
},
"author": "Aboviq AB <dev@aboviq.com> (https://www.aboviq.com)",
"homepage": "https://github.com/aboviq/emigrate/tree/main/packages/cli#readme",

View file

@ -24,7 +24,6 @@ const importAll = async (cwd: string, modules: string[]) => {
};
const up: Action = async (args, abortSignal) => {
const config = await getConfig('up');
const { values } = parseArgs({
args,
options: {
@ -143,6 +142,14 @@ Examples:
}
const cwd = process.cwd();
if (values.import) {
await importAll(cwd, values.import);
}
const forceImportTypeScriptAsIs = values.import?.some((module) => module === 'tsx' || module.startsWith('tsx/'));
const config = await getConfig('up', forceImportTypeScriptAsIs);
const {
directory = config.directory,
storage = config.storage,
@ -151,7 +158,6 @@ Examples:
from,
to,
limit: limitString,
import: imports = [],
'abort-respite': abortRespiteString,
'no-execution': noExecution,
} = values;
@ -177,8 +183,6 @@ Examples:
return;
}
await importAll(cwd, imports);
try {
const { default: upCommand } = await import('./commands/up.js');
process.exitCode = await upCommand({
@ -209,7 +213,6 @@ Examples:
};
const newMigration: Action = async (args) => {
const config = await getConfig('new');
const { values, positionals } = parseArgs({
args,
options: {
@ -239,6 +242,12 @@ const newMigration: Action = async (args) => {
multiple: true,
default: [],
},
import: {
type: 'string',
short: 'i',
multiple: true,
default: [],
},
color: {
type: 'boolean',
},
@ -260,14 +269,24 @@ Arguments:
Options:
-h, --help Show this help message and exit
-d, --directory <path> The directory where the migration files are located (required)
-i, --import <module> Additional modules/packages to import before creating the migration (can be specified multiple times)
For example if you want to use Dotenv to load environment variables or when using TypeScript
-r, --reporter <name> The reporter to use for reporting the migration file creation progress (default: pretty)
-p, --plugin <name> The plugin(s) to use (can be specified multiple times)
-t, --template <path> A template file to use as contents for the new migration file
(if the extension option is not provided the template file's extension will be used)
-x, --extension <ext> The extension to use for the new migration file
(if no template or plugin is provided an empty migration file will be created with the given extension)
--color Force color output (this option is passed to the reporter)
--no-color Disable color output (this option is passed to the reporter)
One of the --template, --extension or the --plugin options must be specified
@ -287,6 +306,14 @@ Examples:
}
const cwd = process.cwd();
if (values.import) {
await importAll(cwd, values.import);
}
const forceImportTypeScriptAsIs = values.import?.some((module) => module === 'tsx' || module.startsWith('tsx/'));
const config = await getConfig('new', forceImportTypeScriptAsIs);
const {
directory = config.directory,
template = config.template,
@ -312,7 +339,6 @@ Examples:
};
const list: Action = async (args) => {
const config = await getConfig('list');
const { values } = parseArgs({
args,
options: {
@ -355,12 +381,18 @@ List all migrations and their status. This command does not run any migrations.
Options:
-h, --help Show this help message and exit
-d, --directory <path> The directory where the migration files are located (required)
-i, --import <module> Additional modules/packages to import before listing the migrations (can be specified multiple times)
For example if you want to use Dotenv to load environment variables
-r, --reporter <name> The reporter to use for reporting the migrations (default: pretty)
-s, --storage <name> The storage to use to get the migration history (required)
--color Force color output (this option is passed to the reporter)
--no-color Disable color output (this option is passed to the reporter)
Examples:
@ -376,14 +408,15 @@ Examples:
}
const cwd = process.cwd();
const {
directory = config.directory,
storage = config.storage,
reporter = config.reporter,
import: imports = [],
} = values;
await importAll(cwd, imports);
if (values.import) {
await importAll(cwd, values.import);
}
const forceImportTypeScriptAsIs = values.import?.some((module) => module === 'tsx' || module.startsWith('tsx/'));
const config = await getConfig('list', forceImportTypeScriptAsIs);
const { directory = config.directory, storage = config.storage, reporter = config.reporter } = values;
try {
const { default: listCommand } = await import('./commands/list.js');
@ -401,7 +434,6 @@ Examples:
};
const remove: Action = async (args) => {
const config = await getConfig('remove');
const { values, positionals } = parseArgs({
args,
options: {
@ -453,13 +485,20 @@ Arguments:
Options:
-h, --help Show this help message and exit
-d, --directory <path> The directory where the migration files are located (required)
-i, --import <module> Additional modules/packages to import before removing the migration (can be specified multiple times)
For example if you want to use Dotenv to load environment variables
-r, --reporter <name> The reporter to use for reporting the removal process (default: pretty)
-s, --storage <name> The storage to use to get the migration history (required)
-f, --force Force removal of the migration history entry even if the migration is not in a failed state
--color Force color output (this option is passed to the reporter)
--no-color Disable color output (this option is passed to the reporter)
Examples:
@ -477,15 +516,15 @@ Examples:
}
const cwd = process.cwd();
const {
directory = config.directory,
storage = config.storage,
reporter = config.reporter,
force,
import: imports = [],
} = values;
await importAll(cwd, imports);
if (values.import) {
await importAll(cwd, values.import);
}
const forceImportTypeScriptAsIs = values.import?.some((module) => module === 'tsx' || module.startsWith('tsx/'));
const config = await getConfig('remove', forceImportTypeScriptAsIs);
const { directory = config.directory, storage = config.storage, reporter = config.reporter, force } = values;
try {
const { default: removeCommand } = await import('./commands/remove.js');

6
packages/cli/src/deno.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
declare global {
// eslint-disable-next-line @typescript-eslint/naming-convention
const Deno: any;
}
export {};

View file

@ -1,11 +1,16 @@
import { cosmiconfig } from 'cosmiconfig';
import process from 'node:process';
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
import { type Config, type EmigrateConfig } from './types.js';
const commands = ['up', 'list', 'new', 'remove'] as const;
type Command = (typeof commands)[number];
const canImportTypeScriptAsIs = Boolean(process.isBun) || typeof Deno !== 'undefined';
export const getConfig = async (command: Command): Promise<Config> => {
const explorer = cosmiconfig('emigrate');
export const getConfig = async (command: Command, forceImportTypeScriptAsIs = false): Promise<Config> => {
const explorer = cosmiconfig('emigrate', {
// eslint-disable-next-line @typescript-eslint/naming-convention
loaders: forceImportTypeScriptAsIs || canImportTypeScriptAsIs ? { '.ts': defaultLoaders['.js'] } : undefined,
});
const result = await explorer.search();

33
pnpm-lock.yaml generated
View file

@ -108,6 +108,12 @@ importers:
'@emigrate/tsconfig':
specifier: workspace:*
version: link:../tsconfig
'@types/bun':
specifier: 1.0.5
version: 1.0.5
bun-types:
specifier: 1.0.26
version: 1.0.26
packages/mysql:
dependencies:
@ -1759,6 +1765,12 @@ packages:
'@babel/types': 7.23.6
dev: false
/@types/bun@1.0.5:
resolution: {integrity: sha512-c14fs5QLLanldcZpX/GjIEKeo++NDzOlixUZ7IUWzN7AoBTisYyWxaxdXNhpAP5I1mPcd92Zagq8sdgTnUXWjg==}
dependencies:
bun-types: 1.0.26
dev: true
/@types/debug@4.1.12:
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
dependencies:
@ -1858,7 +1870,12 @@ packages:
resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==}
dependencies:
undici-types: 5.26.5
dev: false
/@types/node@20.11.17:
resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==}
dependencies:
undici-types: 5.26.5
dev: true
/@types/normalize-package-data@2.4.4:
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
@ -1886,6 +1903,12 @@ packages:
resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
dev: false
/@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
dependencies:
'@types/node': 20.10.4
dev: true
/@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.3.3):
resolution: {integrity: sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==}
engines: {node: ^16.0.0 || >=18.0.0}
@ -2646,6 +2669,13 @@ packages:
semver: 7.5.4
dev: false
/bun-types@1.0.26:
resolution: {integrity: sha512-VcSj+SCaWIcMb0uSGIAtr8P92zq9q+unavcQmx27fk6HulCthXHBVrdGuXxAZbFtv7bHVjizRzR2mk9r/U8Nkg==}
dependencies:
'@types/node': 20.11.17
'@types/ws': 8.5.10
dev: true
/bundle-name@3.0.0:
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
engines: {node: '>=12'}
@ -8603,7 +8633,6 @@ packages:
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
dev: false
/unherit@3.0.1:
resolution: {integrity: sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==}