Compare commits

..

No commits in common. "main" and "@emigrate/reporter-pino@0.6.2" have entirely different histories.

60 changed files with 5134 additions and 8013 deletions

View file

@ -13,7 +13,6 @@ jobs:
env: env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }} TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
DO_NOT_TRACK: 1
steps: steps:
- name: Check out code - name: Check out code
@ -21,12 +20,14 @@ jobs:
with: with:
fetch-depth: 2 fetch-depth: 2
- uses: pnpm/action-setup@v4.0.0 - uses: pnpm/action-setup@v3.0.0
with:
version: 8.3.1
- name: Setup Node.js environment - name: Setup Node.js environment
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22.15.0 node-version: 20.9.0
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies

View file

@ -10,7 +10,6 @@ on:
# Allow this job to clone the repo and create a page deployment # Allow this job to clone the repo and create a page deployment
permissions: permissions:
actions: read
contents: read contents: read
pages: write pages: write
id-token: write id-token: write
@ -30,10 +29,11 @@ jobs:
echo $ASTRO_SITE echo $ASTRO_SITE
echo $ASTRO_BASE echo $ASTRO_BASE
- name: Install, build, and upload your site output - name: Install, build, and upload your site output
uses: withastro/action@v2 uses: withastro/action@v1
with: with:
path: ./docs # The root location of your Astro project inside the repository. (optional) path: ./docs # The root location of your Astro project inside the repository. (optional)
package-manager: pnpm@9.4.0 # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional) node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 18. (optional)
package-manager: pnpm@8.10.2 # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
deploy: deploy:
needs: build needs: build
@ -44,4 +44,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v1

View file

@ -1,62 +0,0 @@
name: Integration Tests
on:
push:
branches: ['main', 'changeset-release/main']
pull_request:
jobs:
mysql_integration:
name: Emigrate MySQL integration tests
timeout-minutes: 15
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
DO_NOT_TRACK: 1
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: emigrate
MYSQL_USER: emigrate
MYSQL_PASSWORD: emigrate
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping -h localhost" --health-interval=10s --health-timeout=5s --health-retries=5
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: pnpm/action-setup@v4.0.0
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version: 22.15.0
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Wait for MySQL to be ready
run: |
for i in {1..30}; do
nc -z localhost 3306 && echo "MySQL is up!" && break
echo "Waiting for MySQL..."
sleep 2
done
- name: Build package
run: pnpm build --filter @emigrate/mysql
- name: Integration Tests
env:
MYSQL_HOST: '127.0.0.1'
MYSQL_PORT: 3306
run: pnpm --filter @emigrate/mysql integration

View file

@ -25,48 +25,25 @@ jobs:
persist-credentials: false persist-credentials: false
fetch-depth: 0 fetch-depth: 0
- uses: pnpm/action-setup@v4.0.0 - uses: pnpm/action-setup@v3.0.0
with:
version: 8.3.1
- name: Setup Node.js environment - name: Setup Node.js environment
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22.15.0 node-version: 20.9.0
cache: 'pnpm' cache: 'pnpm'
- name: Install Dependencies - name: Install Dependencies
run: pnpm install run: pnpm install
- name: Create Release Pull Request - name: Create Release Pull Request
id: changesets uses: changesets/action@v1.4.5
uses: aboviq/changesets-action@v1.5.2
with: with:
publish: pnpm run release publish: pnpm run release
commit: 'chore(release): version packages' commit: 'chore(release): version packages'
title: 'chore(release): version packages' title: 'chore(release): version packages'
createGithubReleases: aggregate
env: env:
GITHUB_TOKEN: ${{ secrets.PAT_GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.PAT_GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Release to @next tag on npm
if: github.ref_name == 'main' && steps.changesets.outputs.published != 'true'
run: |
git checkout main
CHANGESET_FILE=$(git diff-tree --no-commit-id --name-only HEAD -r ".changeset/*-*-*.md")
if [ -z "$CHANGESET_FILE" ]; then
echo "No changesets found, skipping release to @next tag"
exit 0
fi
AFFECTED_PACKAGES=$(sed -n '/---/,/---/p' "$CHANGESET_FILE" | sed '/---/d')
if [ -z "$AFFECTED_PACKAGES" ]; then
echo "No packages affected by changesets, skipping release to @next tag"
exit 0
fi
pnpm changeset version --snapshot next
pnpm changeset publish --tag next
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.PAT_GITHUB_TOKEN }}

View file

@ -11,7 +11,6 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/check": "^0.7.0",
"@astrojs/starlight": "^0.15.0", "@astrojs/starlight": "^0.15.0",
"@astrojs/starlight-tailwind": "2.0.1", "@astrojs/starlight-tailwind": "2.0.1",
"@astrojs/tailwind": "^5.0.3", "@astrojs/tailwind": "^5.0.3",
@ -21,6 +20,5 @@
}, },
"volta": { "volta": {
"extends": "../package.json" "extends": "../package.json"
}, }
"packageManager": "pnpm@9.4.0"
} }

View file

@ -37,10 +37,9 @@
"bugs": "https://github.com/aboviq/emigrate/issues", "bugs": "https://github.com/aboviq/emigrate/issues",
"license": "MIT", "license": "MIT",
"volta": { "volta": {
"node": "22.15.0", "node": "20.9.0",
"pnpm": "9.4.0" "pnpm": "8.10.2"
}, },
"packageManager": "pnpm@9.4.0",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@ -62,10 +61,7 @@
}, },
"overrides": [ "overrides": [
{ {
"files": [ "files": "packages/**/*.test.ts",
"packages/**/*.test.ts",
"packages/**/*.integration.ts"
],
"rules": { "rules": {
"@typescript-eslint/no-floating-promises": 0, "@typescript-eslint/no-floating-promises": 0,
"max-params": 0 "max-params": 0
@ -75,18 +71,17 @@
}, },
"dependencies": { "dependencies": {
"@changesets/cli": "2.27.1", "@changesets/cli": "2.27.1",
"@commitlint/cli": "18.6.1", "@commitlint/cli": "18.4.3",
"@commitlint/config-conventional": "18.6.1", "@commitlint/config-conventional": "18.4.3",
"@types/node": "20.10.4", "@types/node": "20.10.4",
"glob": "10.3.10", "glob": "10.3.10",
"husky": "8.0.3", "husky": "8.0.3",
"lint-staged": "15.2.0", "lint-staged": "15.2.0",
"npm-run-all": "4.1.5", "npm-run-all": "4.1.5",
"prettier": "3.1.1", "prettier": "3.1.1",
"testcontainers": "10.24.2", "tsx": "4.7.0",
"tsx": "4.15.7", "turbo": "1.10.16",
"turbo": "2.0.5", "typescript": "5.3.3",
"typescript": "5.5.2",
"xo": "0.56.0" "xo": "0.56.0"
} }
} }

View file

@ -1,35 +1,5 @@
# @emigrate/cli # @emigrate/cli
## 0.18.4
### Patch Changes
- d779286: Upgrade TypeScript to v5.5 and enable [isolatedDeclarations](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#isolated-declarations)
- Updated dependencies [d779286]
- @emigrate/plugin-tools@0.9.8
- @emigrate/types@0.12.2
## 0.18.3
### Patch Changes
- ca154fa: Minimize package size by excluding \*.tsbuildinfo files
- Updated dependencies [ca154fa]
- @emigrate/plugin-tools@0.9.7
- @emigrate/types@0.12.2
## 0.18.2
### Patch Changes
- 4152209: Handle the case where the config is returned as an object with a nested `default` property
## 0.18.1
### Patch Changes
- 57a0991: Cleanup AbortSignal listeners when they are not needed to avoid MaxListenersExceededWarning when migrating many migrations at once
## 0.18.0 ## 0.18.0
### Minor Changes ### Minor Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/cli", "name": "@emigrate/cli",
"version": "0.18.4", "version": "0.18.0",
"publishConfig": { "publishConfig": {
"access": "public", "access": "public",
"provenance": true "provenance": true
@ -19,8 +19,7 @@
"emigrate": "dist/cli.js" "emigrate": "dist/cli.js"
}, },
"files": [ "files": [
"dist", "dist"
"!dist/*.tsbuildinfo"
], ],
"scripts": { "scripts": {
"build": "tsc --pretty", "build": "tsc --pretty",

View file

@ -1,12 +1,12 @@
import { type MigrationHistoryEntry, type MigrationMetadata, type MigrationMetadataFinished } from '@emigrate/types'; import { type MigrationHistoryEntry, type MigrationMetadata, type MigrationMetadataFinished } from '@emigrate/types';
import { toMigrationMetadata } from './to-migration-metadata.js'; import { toMigrationMetadata } from './to-migration-metadata.js';
import { getMigrations as getMigrationsOriginal, type GetMigrationsFunction } from './get-migrations.js'; import { getMigrations as getMigrationsOriginal } from './get-migrations.js';
export async function* collectMigrations( export async function* collectMigrations(
cwd: string, cwd: string,
directory: string, directory: string,
history: AsyncIterable<MigrationHistoryEntry>, history: AsyncIterable<MigrationHistoryEntry>,
getMigrations: GetMigrationsFunction = getMigrationsOriginal, getMigrations = getMigrationsOriginal,
): AsyncIterable<MigrationMetadata | MigrationMetadataFinished> { ): AsyncIterable<MigrationMetadata | MigrationMetadataFinished> {
const allMigrations = await getMigrations(cwd, directory); const allMigrations = await getMigrations(cwd, directory);
const seen = new Set<string>(); const seen = new Set<string>();

View file

@ -17,7 +17,7 @@ export default async function listCommand({
storage: storageConfig, storage: storageConfig,
color, color,
cwd, cwd,
}: Config & ExtraFlags): Promise<number> { }: Config & ExtraFlags) {
if (!directory) { if (!directory) {
throw MissingOptionError.fromOption('directory'); throw MissingOptionError.fromOption('directory');
} }

View file

@ -24,7 +24,7 @@ type ExtraFlags = {
export default async function newCommand( export default async function newCommand(
{ directory, template, reporter: reporterConfig, plugins = [], cwd, extension, color }: Config & ExtraFlags, { directory, template, reporter: reporterConfig, plugins = [], cwd, extension, color }: Config & ExtraFlags,
name: string, name: string,
): Promise<void> { ) {
if (!directory) { if (!directory) {
throw MissingOptionError.fromOption('directory'); throw MissingOptionError.fromOption('directory');
} }

View file

@ -11,7 +11,6 @@ import {
StorageInitError, StorageInitError,
} from '../errors.js'; } from '../errors.js';
import { import {
assertErrorEqualEnough,
getErrorCause, getErrorCause,
getMockedReporter, getMockedReporter,
getMockedStorage, getMockedStorage,
@ -200,11 +199,6 @@ function assertPreconditionsFailed(reporter: Mocked<Required<EmigrateReporter>>,
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0, 'Total pending and skipped'); assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0, 'Total pending and skipped');
assert.strictEqual(reporter.onFinished.mock.calls.length, 1, 'Finished called once'); assert.strictEqual(reporter.onFinished.mock.calls.length, 1, 'Finished called once');
const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? []; const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? [];
// hackety hack:
if (finishedError) {
finishedError.stack = error?.stack;
}
assert.deepStrictEqual(error, finishedError, 'Finished error'); assert.deepStrictEqual(error, finishedError, 'Finished error');
const cause = getErrorCause(error); const cause = getErrorCause(error);
const expectedCause = finishedError?.cause; const expectedCause = finishedError?.cause;
@ -294,7 +288,14 @@ function assertPreconditionsFulfilled(
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0, 'Total pending and skipped'); assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0, 'Total pending and skipped');
assert.strictEqual(reporter.onFinished.mock.calls.length, 1, 'Finished called once'); assert.strictEqual(reporter.onFinished.mock.calls.length, 1, 'Finished called once');
const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? []; const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? [];
assertErrorEqualEnough(error, finishedError, 'Finished error'); assert.deepStrictEqual(error, finishedError, 'Finished error');
const cause = getErrorCause(error);
const expectedCause = finishedError?.cause;
assert.deepStrictEqual(
cause,
expectedCause ? deserializeError(expectedCause) : expectedCause,
'Finished error cause',
);
assert.strictEqual(entries?.length, expected.length, 'Finished entries length'); assert.strictEqual(entries?.length, expected.length, 'Finished entries length');
assert.deepStrictEqual( assert.deepStrictEqual(
entries.map((entry) => `${entry.name} (${entry.status})`), entries.map((entry) => `${entry.name} (${entry.status})`),

View file

@ -39,7 +39,7 @@ export default async function removeCommand(
getMigrations, getMigrations,
}: Config & ExtraFlags, }: Config & ExtraFlags,
name: string, name: string,
): Promise<number> { ) {
if (!directory) { if (!directory) {
throw MissingOptionError.fromOption('directory'); throw MissingOptionError.fromOption('directory');
} }

View file

@ -1,6 +1,13 @@
import { describe, it, mock } from 'node:test'; import { describe, it, mock } from 'node:test';
import assert from 'node:assert'; import assert from 'node:assert';
import { type EmigrateReporter, type Storage, type Plugin, type MigrationMetadataFinished } from '@emigrate/types'; import {
type EmigrateReporter,
type Storage,
type Plugin,
type SerializedError,
type MigrationMetadataFinished,
} from '@emigrate/types';
import { deserializeError } from 'serialize-error';
import { version } from '../get-package-info.js'; import { version } from '../get-package-info.js';
import { import {
BadOptionError, BadOptionError,
@ -9,6 +16,7 @@ import {
MigrationHistoryError, MigrationHistoryError,
MigrationRunError, MigrationRunError,
StorageInitError, StorageInitError,
toSerializedError,
} from '../errors.js'; } from '../errors.js';
import { import {
type Mocked, type Mocked,
@ -16,7 +24,7 @@ import {
toMigrations, toMigrations,
getMockedReporter, getMockedReporter,
getMockedStorage, getMockedStorage,
assertErrorEqualEnough, getErrorCause,
} from '../test-utils.js'; } from '../test-utils.js';
import upCommand from './up.js'; import upCommand from './up.js';
@ -922,13 +930,15 @@ function assertPreconditionsFulfilled(
for (const [index, entry] of failedEntries.entries()) { for (const [index, entry] of failedEntries.entries()) {
if (entry.status === 'failed') { if (entry.status === 'failed') {
const error = reporter.onMigrationError.mock.calls[index]?.arguments[1]; const error = reporter.onMigrationError.mock.calls[index]?.arguments[1];
assertErrorEqualEnough(error, entry.error, 'Error'); assert.deepStrictEqual(error, entry.error, 'Error');
const cause = entry.error?.cause;
assert.deepStrictEqual(error?.cause, cause ? deserializeError(cause) : cause, 'Error cause');
if (entry.started) { if (entry.started) {
const [finishedMigration, error] = storage.onError.mock.calls[index]?.arguments ?? []; const [finishedMigration, error] = storage.onError.mock.calls[index]?.arguments ?? [];
assert.strictEqual(finishedMigration?.name, entry.name); assert.strictEqual(finishedMigration?.name, entry.name);
assert.strictEqual(finishedMigration?.status, entry.status); assert.strictEqual(finishedMigration?.status, entry.status);
assertErrorEqualEnough(error, entry.error, `Entry error (${entry.name})`); assertErrorEqualEnough(error, entry.error);
} }
} }
} }
@ -936,7 +946,15 @@ function assertPreconditionsFulfilled(
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, pending + skipped, 'Total pending and skipped'); assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, pending + skipped, 'Total pending and skipped');
assert.strictEqual(reporter.onFinished.mock.calls.length, 1, 'Finished called once'); assert.strictEqual(reporter.onFinished.mock.calls.length, 1, 'Finished called once');
const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? []; const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? [];
assertErrorEqualEnough(error, finishedError, 'Finished error'); assertErrorEqualEnough(error, finishedError);
const cause = getErrorCause(error);
const expectedCause = finishedError?.cause;
assert.deepStrictEqual(
cause,
expectedCause ? deserializeError(expectedCause) : expectedCause,
'Finished error cause',
);
assert.strictEqual(entries?.length, expected.length, 'Finished entries length'); assert.strictEqual(entries?.length, expected.length, 'Finished entries length');
assert.deepStrictEqual( assert.deepStrictEqual(
entries.map((entry) => `${entry.name} (${entry.status})`), entries.map((entry) => `${entry.name} (${entry.status})`),
@ -977,6 +995,33 @@ function assertPreconditionsFailed(
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0, 'Total pending and skipped'); assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0, 'Total pending and skipped');
assert.strictEqual(reporter.onFinished.mock.calls.length, 1, 'Finished called once'); assert.strictEqual(reporter.onFinished.mock.calls.length, 1, 'Finished called once');
const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? []; const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? [];
assertErrorEqualEnough(error, finishedError, 'Finished error'); assert.deepStrictEqual(error, finishedError, 'Finished error');
const cause = getErrorCause(error);
const expectedCause = finishedError?.cause;
assert.deepStrictEqual(
cause,
expectedCause ? deserializeError(expectedCause) : expectedCause,
'Finished error cause',
);
assert.strictEqual(entries?.length, 0, 'Finished entries length'); assert.strictEqual(entries?.length, 0, 'Finished entries length');
} }
function assertErrorEqualEnough(actual?: Error | SerializedError, expected?: Error) {
if (expected === undefined) {
assert.strictEqual(actual, undefined);
return;
}
const {
cause: actualCause,
stack: actualStack,
...actualError
} = actual instanceof Error ? toSerializedError(actual) : actual ?? {};
const { cause: expectedCause, stack: expectedStack, ...expectedError } = toSerializedError(expected);
// @ts-expect-error Ignore
const { stack: actualCauseStack, ...actualCauseRest } = actualCause ?? {};
// @ts-expect-error Ignore
const { stack: expectedCauseStack, ...expectedCauseRest } = expectedCause ?? {};
assert.deepStrictEqual(actualError, expectedError);
assert.deepStrictEqual(actualCauseRest, expectedCauseRest);
}

View file

@ -8,7 +8,7 @@ import { serializeError, errorConstructors, deserializeError } from 'serialize-e
const formatter = new Intl.ListFormat('en', { style: 'long', type: 'disjunction' }); const formatter = new Intl.ListFormat('en', { style: 'long', type: 'disjunction' });
export const toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error))); export const toError = (error: unknown) => (error instanceof Error ? error : new Error(String(error)));
export const toSerializedError = (error: unknown) => { export const toSerializedError = (error: unknown) => {
const errorInstance = toError(error); const errorInstance = toError(error);
@ -30,7 +30,7 @@ export class EmigrateError extends Error {
export class ShowUsageError extends EmigrateError {} export class ShowUsageError extends EmigrateError {}
export class MissingOptionError extends ShowUsageError { export class MissingOptionError extends ShowUsageError {
static fromOption(option: string | string[]): MissingOptionError { static fromOption(option: string | string[]) {
return new MissingOptionError( return new MissingOptionError(
`Missing required option: ${Array.isArray(option) ? formatter.format(option) : option}`, `Missing required option: ${Array.isArray(option) ? formatter.format(option) : option}`,
undefined, undefined,
@ -48,7 +48,7 @@ export class MissingOptionError extends ShowUsageError {
} }
export class MissingArgumentsError extends ShowUsageError { export class MissingArgumentsError extends ShowUsageError {
static fromArgument(argument: string): MissingArgumentsError { static fromArgument(argument: string) {
return new MissingArgumentsError(`Missing required argument: ${argument}`, undefined, argument); return new MissingArgumentsError(`Missing required argument: ${argument}`, undefined, argument);
} }
@ -62,7 +62,7 @@ export class MissingArgumentsError extends ShowUsageError {
} }
export class OptionNeededError extends ShowUsageError { export class OptionNeededError extends ShowUsageError {
static fromOption(option: string, message: string): OptionNeededError { static fromOption(option: string, message: string) {
return new OptionNeededError(message, undefined, option); return new OptionNeededError(message, undefined, option);
} }
@ -76,7 +76,7 @@ export class OptionNeededError extends ShowUsageError {
} }
export class BadOptionError extends ShowUsageError { export class BadOptionError extends ShowUsageError {
static fromOption(option: string, message: string): BadOptionError { static fromOption(option: string, message: string) {
return new BadOptionError(message, undefined, option); return new BadOptionError(message, undefined, option);
} }
@ -96,7 +96,7 @@ export class UnexpectedError extends EmigrateError {
} }
export class MigrationHistoryError extends EmigrateError { export class MigrationHistoryError extends EmigrateError {
static fromHistoryEntry(entry: FailedMigrationHistoryEntry): MigrationHistoryError { static fromHistoryEntry(entry: FailedMigrationHistoryEntry) {
return new MigrationHistoryError(`Migration ${entry.name} is in a failed state, it should be fixed and removed`, { return new MigrationHistoryError(`Migration ${entry.name} is in a failed state, it should be fixed and removed`, {
cause: deserializeError(entry.error), cause: deserializeError(entry.error),
}); });
@ -108,7 +108,7 @@ export class MigrationHistoryError extends EmigrateError {
} }
export class MigrationLoadError extends EmigrateError { export class MigrationLoadError extends EmigrateError {
static fromMetadata(metadata: MigrationMetadata, cause?: Error): MigrationLoadError { static fromMetadata(metadata: MigrationMetadata, cause?: Error) {
return new MigrationLoadError(`Failed to load migration file: ${metadata.relativeFilePath}`, { cause }); return new MigrationLoadError(`Failed to load migration file: ${metadata.relativeFilePath}`, { cause });
} }
@ -118,7 +118,7 @@ export class MigrationLoadError extends EmigrateError {
} }
export class MigrationRunError extends EmigrateError { export class MigrationRunError extends EmigrateError {
static fromMetadata(metadata: FailedMigrationMetadata): MigrationRunError { static fromMetadata(metadata: FailedMigrationMetadata) {
return new MigrationRunError(`Failed to run migration: ${metadata.relativeFilePath}`, { cause: metadata.error }); return new MigrationRunError(`Failed to run migration: ${metadata.relativeFilePath}`, { cause: metadata.error });
} }
@ -128,7 +128,7 @@ export class MigrationRunError extends EmigrateError {
} }
export class MigrationNotRunError extends EmigrateError { export class MigrationNotRunError extends EmigrateError {
static fromMetadata(metadata: MigrationMetadata, cause?: Error): MigrationNotRunError { static fromMetadata(metadata: MigrationMetadata, cause?: Error) {
return new MigrationNotRunError(`Migration "${metadata.name}" is not in the migration history`, { cause }); return new MigrationNotRunError(`Migration "${metadata.name}" is not in the migration history`, { cause });
} }
@ -138,7 +138,7 @@ export class MigrationNotRunError extends EmigrateError {
} }
export class MigrationRemovalError extends EmigrateError { export class MigrationRemovalError extends EmigrateError {
static fromMetadata(metadata: MigrationMetadata, cause?: Error): MigrationRemovalError { static fromMetadata(metadata: MigrationMetadata, cause?: Error) {
return new MigrationRemovalError(`Failed to remove migration: ${metadata.relativeFilePath}`, { cause }); return new MigrationRemovalError(`Failed to remove migration: ${metadata.relativeFilePath}`, { cause });
} }
@ -148,7 +148,7 @@ export class MigrationRemovalError extends EmigrateError {
} }
export class StorageInitError extends EmigrateError { export class StorageInitError extends EmigrateError {
static fromError(error: Error): StorageInitError { static fromError(error: Error) {
return new StorageInitError('Could not initialize storage', { cause: error }); return new StorageInitError('Could not initialize storage', { cause: error });
} }
@ -158,11 +158,11 @@ export class StorageInitError extends EmigrateError {
} }
export class CommandAbortError extends EmigrateError { export class CommandAbortError extends EmigrateError {
static fromSignal(signal: NodeJS.Signals): CommandAbortError { static fromSignal(signal: NodeJS.Signals) {
return new CommandAbortError(`Command aborted due to signal: ${signal}`); return new CommandAbortError(`Command aborted due to signal: ${signal}`);
} }
static fromReason(reason: string, cause?: unknown): CommandAbortError { static fromReason(reason: string, cause?: unknown) {
return new CommandAbortError(`Command aborted: ${reason}`, { cause }); return new CommandAbortError(`Command aborted: ${reason}`, { cause });
} }
@ -172,7 +172,7 @@ export class CommandAbortError extends EmigrateError {
} }
export class ExecutionDesertedError extends EmigrateError { export class ExecutionDesertedError extends EmigrateError {
static fromReason(reason: string, cause?: Error): ExecutionDesertedError { static fromReason(reason: string, cause?: Error) {
return new ExecutionDesertedError(`Execution deserted: ${reason}`, { cause }); return new ExecutionDesertedError(`Execution deserted: ${reason}`, { cause });
} }

View file

@ -28,8 +28,6 @@ export const exec = async <Return extends Promise<any>>(
const aborter = options.abortSignal ? getAborter(options.abortSignal, options.abortRespite) : undefined; const aborter = options.abortSignal ? getAborter(options.abortSignal, options.abortRespite) : undefined;
const result = await Promise.race(aborter ? [aborter, fn()] : [fn()]); const result = await Promise.race(aborter ? [aborter, fn()] : [fn()]);
aborter?.cancel();
return [result, undefined]; return [result, undefined];
} catch (error) { } catch (error) {
return [undefined, toError(error)]; return [undefined, toError(error)];
@ -42,44 +40,27 @@ export const exec = async <Return extends Promise<any>>(
* @param signal The abort signal to listen to * @param signal The abort signal to listen to
* @param respite The time in milliseconds to wait before rejecting * @param respite The time in milliseconds to wait before rejecting
*/ */
const getAborter = ( const getAborter = async (signal: AbortSignal, respite = DEFAULT_RESPITE_SECONDS * 1000): Promise<never> => {
signal: AbortSignal, return new Promise((_, reject) => {
respite = DEFAULT_RESPITE_SECONDS * 1000, if (signal.aborted) {
): PromiseLike<never> & { cancel: () => void } => { setTimeout(
const cleanups: Array<() => void> = [];
const aborter = new Promise<never>((_, reject) => {
const abortListener = () => {
const timer = setTimeout(
reject, reject,
respite, respite,
ExecutionDesertedError.fromReason(`Deserted after ${prettyMs(respite)}`, toError(signal.reason)), ExecutionDesertedError.fromReason(`Deserted after ${prettyMs(respite)}`, toError(signal.reason)),
); ).unref();
timer.unref();
cleanups.push(() => {
clearTimeout(timer);
});
};
if (signal.aborted) {
abortListener();
return; return;
} }
signal.addEventListener('abort', abortListener, { once: true }); signal.addEventListener(
'abort',
cleanups.push(() => { () => {
signal.removeEventListener('abort', abortListener); setTimeout(
}); reject,
respite,
ExecutionDesertedError.fromReason(`Deserted after ${prettyMs(respite)}`, toError(signal.reason)),
).unref();
},
{ once: true },
);
}); });
const cancel = () => {
for (const cleanup of cleanups) {
cleanup();
}
cleanups.length = 0;
};
return Object.assign(aborter, { cancel });
}; };

View file

@ -6,18 +6,6 @@ const commands = ['up', 'list', 'new', 'remove'] as const;
type Command = (typeof commands)[number]; type Command = (typeof commands)[number];
const canImportTypeScriptAsIs = Boolean(process.isBun) || typeof Deno !== 'undefined'; const canImportTypeScriptAsIs = Boolean(process.isBun) || typeof Deno !== 'undefined';
const getEmigrateConfig = (config: any): EmigrateConfig => {
if ('default' in config && typeof config.default === 'object' && config.default !== null) {
return config.default as EmigrateConfig;
}
if (typeof config === 'object' && config !== null) {
return config as EmigrateConfig;
}
return {};
};
export const getConfig = async (command: Command, forceImportTypeScriptAsIs = false): Promise<Config> => { export const getConfig = async (command: Command, forceImportTypeScriptAsIs = false): Promise<Config> => {
const explorer = cosmiconfig('emigrate', { const explorer = cosmiconfig('emigrate', {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -30,7 +18,7 @@ export const getConfig = async (command: Command, forceImportTypeScriptAsIs = fa
return {}; return {};
} }
const config = getEmigrateConfig(result.config); const config = result.config as EmigrateConfig;
const commandConfig = config[command]; const commandConfig = config[command];

View file

@ -1,6 +1,6 @@
import process from 'node:process'; import process from 'node:process';
export const getDuration = (start: [number, number]): number => { export const getDuration = (start: [number, number]) => {
const [seconds, nanoseconds] = process.hrtime(start); const [seconds, nanoseconds] = process.hrtime(start);
return seconds * 1000 + nanoseconds / 1_000_000; return seconds * 1000 + nanoseconds / 1_000_000;
}; };

View file

@ -39,6 +39,6 @@ export const getMigrations = async (cwd: string, directory: string): Promise<Mig
extension: withLeadingPeriod(path.extname(name)), extension: withLeadingPeriod(path.extname(name)),
directory, directory,
cwd, cwd,
}; } satisfies MigrationMetadata;
}); });
}; };

View file

@ -28,7 +28,4 @@ const getPackageInfo = async () => {
throw new UnexpectedError(`Could not read package info from: ${packageInfoPath}`); throw new UnexpectedError(`Could not read package info from: ${packageInfoPath}`);
}; };
const packageInfo = await getPackageInfo(); export const { version } = await getPackageInfo();
// eslint-disable-next-line prefer-destructuring
export const version: string = packageInfo.version;

View file

@ -1,5 +1,5 @@
export * from './types.js'; export * from './types.js';
export const emigrate = (): void => { export const emigrate = () => {
// console.log('Done!'); // console.log('Done!');
}; };

View file

@ -471,6 +471,6 @@ class DefaultReporter implements Required<EmigrateReporter> {
} }
} }
const reporterDefault: EmigrateReporter = interactive ? new DefaultFancyReporter() : new DefaultReporter(); const reporterDefault = interactive ? new DefaultFancyReporter() : new DefaultReporter();
export default reporterDefault; export default reporterDefault;

View file

@ -1,8 +1,7 @@
import type { EmigrateReporter } from '@emigrate/types';
import { type Config } from '../types.js'; import { type Config } from '../types.js';
import * as reporters from './index.js'; import * as reporters from './index.js';
export const getStandardReporter = (reporter?: Config['reporter']): EmigrateReporter | undefined => { export const getStandardReporter = (reporter?: Config['reporter']) => {
if (!reporter) { if (!reporter) {
return reporters.pretty; return reporters.pretty;
} }
@ -11,5 +10,5 @@ export const getStandardReporter = (reporter?: Config['reporter']): EmigrateRepo
return reporters[reporter as keyof typeof reporters]; return reporters[reporter as keyof typeof reporters];
} }
return undefined; return; // eslint-disable-line no-useless-return
}; };

View file

@ -55,6 +55,6 @@ class JsonReporter implements EmigrateReporter {
} }
} }
const jsonReporter: EmigrateReporter = new JsonReporter(); const jsonReporter = new JsonReporter() as EmigrateReporter;
export default jsonReporter; export default jsonReporter;

View file

@ -1,6 +1,5 @@
import { mock, type Mock } from 'node:test'; import { mock, type Mock } from 'node:test';
import path from 'node:path'; import path from 'node:path';
import assert from 'node:assert';
import { import {
type SerializedError, type SerializedError,
type EmigrateReporter, type EmigrateReporter,
@ -10,14 +9,13 @@ import {
type NonFailedMigrationHistoryEntry, type NonFailedMigrationHistoryEntry,
type Storage, type Storage,
} from '@emigrate/types'; } from '@emigrate/types';
import { toSerializedError } from './errors.js';
export type Mocked<T> = { export type Mocked<T> = {
// @ts-expect-error - This is a mock // @ts-expect-error - This is a mock
[K in keyof T]: Mock<T[K]>; [K in keyof T]: Mock<T[K]>;
}; };
export async function noop(): Promise<void> { export async function noop() {
// noop // noop
} }
@ -33,8 +31,8 @@ export function getErrorCause(error: Error | undefined): Error | SerializedError
return undefined; return undefined;
} }
export function getMockedStorage(historyEntries: Array<string | MigrationHistoryEntry>): Mocked<Storage> { export function getMockedStorage(historyEntries: Array<string | MigrationHistoryEntry>) {
return { const storage: Mocked<Storage> = {
lock: mock.fn(async (migrations) => migrations), lock: mock.fn(async (migrations) => migrations),
unlock: mock.fn(async () => { unlock: mock.fn(async () => {
// void // void
@ -47,6 +45,8 @@ export function getMockedStorage(historyEntries: Array<string | MigrationHistory
onError: mock.fn(), onError: mock.fn(),
end: mock.fn(), end: mock.fn(),
}; };
return storage;
} }
export function getMockedReporter(): Mocked<Required<EmigrateReporter>> { export function getMockedReporter(): Mocked<Required<EmigrateReporter>> {
@ -112,23 +112,3 @@ export function toEntries(
): MigrationHistoryEntry[] { ): MigrationHistoryEntry[] {
return names.map((name) => (typeof name === 'string' ? toEntry(name, status) : name)); return names.map((name) => (typeof name === 'string' ? toEntry(name, status) : name));
} }
export function assertErrorEqualEnough(actual?: Error | SerializedError, expected?: Error, message?: string): void {
if (expected === undefined) {
assert.strictEqual(actual, undefined);
return;
}
const {
cause: actualCause,
stack: actualStack,
...actualError
} = actual instanceof Error ? toSerializedError(actual) : actual ?? {};
const { cause: expectedCause, stack: expectedStack, ...expectedError } = toSerializedError(expected);
// @ts-expect-error Ignore
const { stack: actualCauseStack, ...actualCauseRest } = actualCause ?? {};
// @ts-expect-error Ignore
const { stack: expectedCauseStack, ...expectedCauseRest } = expectedCause ?? {};
assert.deepStrictEqual(actualError, expectedError, message);
assert.deepStrictEqual(actualCauseRest, expectedCauseRest, message ? `${message} (cause)` : undefined);
}

View file

@ -1 +1 @@
export const withLeadingPeriod = (string: string): string => (string.startsWith('.') ? string : `.${string}`); export const withLeadingPeriod = (string: string) => (string.startsWith('.') ? string : `.${string}`);

View file

@ -1,3 +1,8 @@
{ {
"extends": "@emigrate/tsconfig/build.json" "extends": "@emigrate/tsconfig/build.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
} }

View file

@ -1,41 +1,5 @@
# @emigrate/mysql # @emigrate/mysql
## 0.3.3
### Patch Changes
- 26240f4: Make sure we can initialize multiple running instances of Emigrate using @emigrate/mysql concurrently without issues with creating the history table (for instance in a Kubernetes environment and/or with a Percona cluster).
- d779286: Upgrade TypeScript to v5.5 and enable [isolatedDeclarations](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#isolated-declarations)
- 26240f4: Either lock all or none of the migrations to run to make sure they run in order when multiple instances of Emigrate runs concurrently (for instance in a Kubernetes environment)
- Updated dependencies [d779286]
- @emigrate/plugin-tools@0.9.8
- @emigrate/types@0.12.2
## 0.3.2
### Patch Changes
- 57498db: Unreference all connections when run using Bun, to not keep the process open unnecessarily long
## 0.3.1
### Patch Changes
- ca154fa: Minimize package size by excluding \*.tsbuildinfo files
- Updated dependencies [ca154fa]
- @emigrate/plugin-tools@0.9.7
- @emigrate/types@0.12.2
## 0.3.0
### Minor Changes
- 4442604: Automatically create the database if it doesn't exist, and the user have the permissions to do so
### Patch Changes
- aef2d7c: Avoid "CREATE TABLE IF NOT EXISTS" as it's too locking in a clustered database when running it concurrently
## 0.2.8 ## 0.2.8
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/mysql", "name": "@emigrate/mysql",
"version": "0.3.3", "version": "0.2.8",
"publishConfig": { "publishConfig": {
"access": "public", "access": "public",
"provenance": true "provenance": true
@ -16,17 +16,12 @@
} }
}, },
"files": [ "files": [
"dist", "dist"
"!dist/*.tsbuildinfo",
"!dist/**/*.test.js",
"!dist/tests/*"
], ],
"scripts": { "scripts": {
"build": "tsc --pretty", "build": "tsc --pretty",
"build:watch": "tsc --pretty --watch", "build:watch": "tsc --pretty --watch",
"lint": "xo --cwd=../.. $(pwd)", "lint": "xo --cwd=../.. $(pwd)"
"integration": "glob -c \"node --import tsx --test-reporter spec --test\" \"./src/**/*.integration.ts\"",
"integration:watch": "glob -c \"node --watch --import tsx --test-reporter spec --test\" \"./src/**/*.integration.ts\""
}, },
"keywords": [ "keywords": [
"emigrate", "emigrate",
@ -49,8 +44,8 @@
}, },
"devDependencies": { "devDependencies": {
"@emigrate/tsconfig": "workspace:*", "@emigrate/tsconfig": "workspace:*",
"@types/bun": "1.1.2", "@types/bun": "1.0.5",
"bun-types": "1.1.8" "bun-types": "1.0.26"
}, },
"volta": { "volta": {
"extends": "../../package.json" "extends": "../../package.json"

View file

@ -1,103 +0,0 @@
import assert from 'node:assert';
import path from 'node:path';
import { before, after, describe, it } from 'node:test';
import type { MigrationMetadata } from '@emigrate/types';
import { startDatabase, stopDatabase } from './tests/database.js';
import { createMysqlStorage } from './index.js';
let db: { port: number; host: string };
const toEnd = new Set<{ end: () => Promise<void> }>();
describe('emigrate-mysql', async () => {
before(
async () => {
db = await startDatabase();
},
{ timeout: 60_000 },
);
after(
async () => {
for (const storage of toEnd) {
// eslint-disable-next-line no-await-in-loop
await storage.end();
}
toEnd.clear();
await stopDatabase();
},
{ timeout: 10_000 },
);
describe('migration locks', async () => {
it('either locks none or all of the given migrations', async () => {
const { initializeStorage } = createMysqlStorage({
table: 'migrations',
connection: {
host: db.host,
user: 'emigrate',
password: 'emigrate',
database: 'emigrate',
port: db.port,
},
});
const [storage1, storage2] = await Promise.all([initializeStorage(), initializeStorage()]);
toEnd.add(storage1);
toEnd.add(storage2);
const migrations = toMigrations('/emigrate', 'migrations', [
'2023-10-01-01-test.js',
'2023-10-01-02-test.js',
'2023-10-01-03-test.js',
'2023-10-01-04-test.js',
'2023-10-01-05-test.js',
'2023-10-01-06-test.js',
'2023-10-01-07-test.js',
'2023-10-01-08-test.js',
'2023-10-01-09-test.js',
'2023-10-01-10-test.js',
'2023-10-01-11-test.js',
'2023-10-01-12-test.js',
'2023-10-01-13-test.js',
'2023-10-01-14-test.js',
'2023-10-01-15-test.js',
'2023-10-01-16-test.js',
'2023-10-01-17-test.js',
'2023-10-01-18-test.js',
'2023-10-01-19-test.js',
'2023-10-01-20-test.js',
]);
const [locked1, locked2] = await Promise.all([storage1.lock(migrations), storage2.lock(migrations)]);
assert.strictEqual(
locked1.length === 0 || locked2.length === 0,
true,
'One of the processes should have no locks',
);
assert.strictEqual(
locked1.length === 20 || locked2.length === 20,
true,
'One of the processes should have all locks',
);
});
});
});
function toMigration(cwd: string, directory: string, name: string): MigrationMetadata {
return {
name,
filePath: `${cwd}/${directory}/${name}`,
relativeFilePath: `${directory}/${name}`,
extension: path.extname(name),
directory,
cwd,
};
}
function toMigrations(cwd: string, directory: string, names: string[]): MigrationMetadata[] {
return names.map((name) => toMigration(cwd, directory, name));
}

View file

@ -1,6 +1,5 @@
import process from 'node:process'; import process from 'node:process';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import { setTimeout } from 'node:timers/promises';
import { import {
createConnection, createConnection,
createPool, createPool,
@ -10,13 +9,10 @@ import {
type Pool, type Pool,
type ResultSetHeader, type ResultSetHeader,
type RowDataPacket, type RowDataPacket,
type Connection,
} from 'mysql2/promise'; } from 'mysql2/promise';
import { getTimestampPrefix, sanitizeMigrationName } from '@emigrate/plugin-tools'; import { getTimestampPrefix, sanitizeMigrationName } from '@emigrate/plugin-tools';
import { import {
type Awaitable,
type MigrationMetadata, type MigrationMetadata,
type MigrationFunction,
type EmigrateStorage, type EmigrateStorage,
type LoaderPlugin, type LoaderPlugin,
type Storage, type Storage,
@ -44,39 +40,27 @@ export type MysqlLoaderOptions = {
connection: ConnectionOptions | string; connection: ConnectionOptions | string;
}; };
const getConnection = async (options: ConnectionOptions | string) => { const getConnection = async (connection: ConnectionOptions | string) => {
let connection: Connection; if (typeof connection === 'string') {
const uri = new URL(connection);
if (typeof options === 'string') {
const uri = new URL(options);
// client side connectTimeout is unstable in mysql2 library // client side connectTimeout is unstable in mysql2 library
// it throws an error you can't catch and crashes node // it throws an error you can't catch and crashes node
// best to leave this at 0 (disabled) // best to leave this at 0 (disabled)
uri.searchParams.set('connectTimeout', '0'); uri.searchParams.set('connectTimeout', '0');
uri.searchParams.set('multipleStatements', 'true'); uri.searchParams.set('multipleStatements', 'true');
uri.searchParams.set('flags', '-FOUND_ROWS');
connection = await createConnection(uri.toString()); return createConnection(uri.toString());
} else {
connection = await createConnection({
...options,
// client side connectTimeout is unstable in mysql2 library
// it throws an error you can't catch and crashes node
// best to leave this at 0 (disabled)
connectTimeout: 0,
multipleStatements: true,
flags: ['-FOUND_ROWS'],
});
} }
if (process.isBun) { return createConnection({
// @ts-expect-error the connection is not in the types but it's there ...connection,
// eslint-disable-next-line @typescript-eslint/no-unsafe-call // client side connectTimeout is unstable in mysql2 library
connection.connection.stream.unref(); // it throws an error you can't catch and crashes node
} // best to leave this at 0 (disabled)
connectTimeout: 0,
return connection; multipleStatements: true,
});
}; };
const getPool = (connection: PoolOptions | string) => { const getPool = (connection: PoolOptions | string) => {
@ -87,7 +71,6 @@ const getPool = (connection: PoolOptions | string) => {
// it throws an error you can't catch and crashes node // it throws an error you can't catch and crashes node
// best to leave this at 0 (disabled) // best to leave this at 0 (disabled)
uri.searchParams.set('connectTimeout', '0'); uri.searchParams.set('connectTimeout', '0');
uri.searchParams.set('flags', '-FOUND_ROWS');
return createPool(uri.toString()); return createPool(uri.toString());
} }
@ -98,7 +81,6 @@ const getPool = (connection: PoolOptions | string) => {
// it throws an error you can't catch and crashes node // it throws an error you can't catch and crashes node
// best to leave this at 0 (disabled) // best to leave this at 0 (disabled)
connectTimeout: 0, connectTimeout: 0,
flags: ['-FOUND_ROWS'],
}); });
}; };
@ -109,8 +91,8 @@ type HistoryEntry = {
error?: SerializedError; error?: SerializedError;
}; };
const lockMigration = async (connection: Connection, table: string, migration: MigrationMetadata) => { const lockMigration = async (pool: Pool, table: string, migration: MigrationMetadata) => {
const [result] = await connection.execute<ResultSetHeader>({ const [result] = await pool.execute<ResultSetHeader>({
sql: ` sql: `
INSERT INTO ${escapeId(table)} (name, status, date) INSERT INTO ${escapeId(table)} (name, status, date)
VALUES (?, ?, NOW()) VALUES (?, ?, NOW())
@ -173,147 +155,20 @@ const deleteMigration = async (pool: Pool, table: string, migration: MigrationMe
return result.affectedRows === 1; return result.affectedRows === 1;
}; };
const getDatabaseName = (config: ConnectionOptions | string) => { const initializeTable = async (pool: Pool, table: string) => {
if (typeof config === 'string') { // This table definition is compatible with the one used by the immigration-mysql package
const uri = new URL(config); await pool.execute(`
CREATE TABLE IF NOT EXISTS ${escapeId(table)} (
return uri.pathname.replace(/^\//u, ''); name varchar(255) not null primary key,
} status varchar(32),
date datetime not null
return config.database ?? ''; ) Engine=InnoDB;
}; `);
const setDatabaseName = <T extends ConnectionOptions | string>(config: T, databaseName: string): T => {
if (typeof config === 'string') {
const uri = new URL(config);
uri.pathname = `/${databaseName}`;
return uri.toString() as T;
}
if (typeof config === 'object') {
return {
...config,
database: databaseName,
};
}
throw new Error('Invalid connection config');
};
const initializeDatabase = async (config: ConnectionOptions | string) => {
let connection: Connection | undefined;
try {
connection = await getConnection(config);
await connection.query('SELECT 1');
await connection.end();
} catch (error) {
await connection?.end();
// The ER_BAD_DB_ERROR error code is thrown when the database does not exist but the user might have the permissions to create it
// Otherwise the error code is ER_DBACCESS_DENIED_ERROR
if (error && typeof error === 'object' && 'code' in error && error.code === 'ER_BAD_DB_ERROR') {
const databaseName = getDatabaseName(config);
const informationSchemaConfig = setDatabaseName(config, 'information_schema');
const informationSchemaConnection = await getConnection(informationSchemaConfig);
try {
await informationSchemaConnection.query(`CREATE DATABASE ${escapeId(databaseName)}`);
// Any database creation error here will be propagated
} finally {
await informationSchemaConnection.end();
}
} else {
// In this case we don't know how to handle the error, so we rethrow it
throw error;
}
}
};
const lockWaitTimeout = 10; // seconds
const isHistoryTableExisting = async (connection: Connection, table: string) => {
const [result] = await connection.execute<RowDataPacket[]>({
sql: `
SELECT
1 as table_exists
FROM
information_schema.tables
WHERE
table_schema = DATABASE()
AND table_name = ?
`,
values: [table],
});
return result[0]?.['table_exists'] === 1;
};
const initializeTable = async (config: ConnectionOptions | string, table: string) => {
const connection = await getConnection(config);
if (await isHistoryTableExisting(connection, table)) {
await connection.end();
return;
}
const lockName = `emigrate_init_table_lock_${table}`;
const [lockResult] = await connection.query<RowDataPacket[]>(`SELECT GET_LOCK(?, ?) AS got_lock`, [
lockName,
lockWaitTimeout,
]);
const didGetLock = lockResult[0]?.['got_lock'] === 1;
if (didGetLock) {
try {
// This table definition is compatible with the one used by the immigration-mysql package
await connection.execute(`
CREATE TABLE IF NOT EXISTS ${escapeId(table)} (
name varchar(255) not null primary key,
status varchar(32),
date datetime not null
) Engine=InnoDB;
`);
} finally {
await connection.query(`SELECT RELEASE_LOCK(?)`, [lockName]);
await connection.end();
}
return;
}
// Didn't get the lock, wait to see if the table was created by another process
const maxWait = lockWaitTimeout * 1000; // milliseconds
const checkInterval = 250; // milliseconds
const start = Date.now();
try {
while (Date.now() - start < maxWait) {
// eslint-disable-next-line no-await-in-loop
if (await isHistoryTableExisting(connection, table)) {
return;
}
// eslint-disable-next-line no-await-in-loop
await setTimeout(checkInterval);
}
throw new Error(`Timeout waiting for table ${table} to be created by other process`);
} finally {
await connection.end();
}
}; };
export const createMysqlStorage = ({ table = defaultTable, connection }: MysqlStorageOptions): EmigrateStorage => { export const createMysqlStorage = ({ table = defaultTable, connection }: MysqlStorageOptions): EmigrateStorage => {
return { return {
async initializeStorage() { async initializeStorage() {
await initializeDatabase(connection);
await initializeTable(connection, table);
const pool = getPool(connection); const pool = getPool(connection);
if (process.isBun) { if (process.isBun) {
@ -324,35 +179,26 @@ export const createMysqlStorage = ({ table = defaultTable, connection }: MysqlSt
}); });
} }
await pool.query('SELECT 1');
try {
await initializeTable(pool, table);
} catch (error) {
await pool.end();
throw error;
}
const storage: Storage = { const storage: Storage = {
async lock(migrations) { async lock(migrations) {
const connection = await pool.getConnection(); const lockedMigrations: MigrationMetadata[] = [];
try { for await (const migration of migrations) {
await connection.beginTransaction(); if (await lockMigration(pool, table, migration)) {
const lockedMigrations: MigrationMetadata[] = []; lockedMigrations.push(migration);
for await (const migration of migrations) {
if (await lockMigration(connection, table, migration)) {
lockedMigrations.push(migration);
}
} }
if (lockedMigrations.length === migrations.length) {
await connection.commit();
return lockedMigrations;
}
await connection.rollback();
return [];
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
} }
return lockedMigrations;
}, },
async unlock(migrations) { async unlock(migrations) {
for await (const migration of migrations) { for await (const migration of migrations) {
@ -411,6 +257,17 @@ export const createMysqlStorage = ({ table = defaultTable, connection }: MysqlSt
}; };
}; };
export const { initializeStorage } = createMysqlStorage({
table: process.env['MYSQL_TABLE'],
connection: process.env['MYSQL_URL'] ?? {
host: process.env['MYSQL_HOST'],
port: process.env['MYSQL_PORT'] ? Number.parseInt(process.env['MYSQL_PORT'], 10) : undefined,
user: process.env['MYSQL_USER'],
password: process.env['MYSQL_PASSWORD'],
database: process.env['MYSQL_DATABASE'],
},
});
export const createMysqlLoader = ({ connection }: MysqlLoaderOptions): LoaderPlugin => { export const createMysqlLoader = ({ connection }: MysqlLoaderOptions): LoaderPlugin => {
return { return {
loadableExtensions: ['.sql'], loadableExtensions: ['.sql'],
@ -419,6 +276,12 @@ export const createMysqlLoader = ({ connection }: MysqlLoaderOptions): LoaderPlu
const contents = await fs.readFile(migration.filePath, 'utf8'); const contents = await fs.readFile(migration.filePath, 'utf8');
const conn = await getConnection(connection); const conn = await getConnection(connection);
if (process.isBun) {
// @ts-expect-error the connection is not in the types but it's there
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
conn.connection.stream.unref();
}
try { try {
await conn.query(contents); await conn.query(contents);
} finally { } finally {
@ -429,6 +292,16 @@ export const createMysqlLoader = ({ connection }: MysqlLoaderOptions): LoaderPlu
}; };
}; };
export const { loadableExtensions, loadMigration } = createMysqlLoader({
connection: process.env['MYSQL_URL'] ?? {
host: process.env['MYSQL_HOST'],
port: process.env['MYSQL_PORT'] ? Number.parseInt(process.env['MYSQL_PORT'], 10) : undefined,
user: process.env['MYSQL_USER'],
password: process.env['MYSQL_PASSWORD'],
database: process.env['MYSQL_DATABASE'],
},
});
export const generateMigration: GenerateMigrationFunction = async (name) => { export const generateMigration: GenerateMigrationFunction = async (name) => {
return { return {
filename: `${getTimestampPrefix()}_${sanitizeMigrationName(name)}.sql`, filename: `${getTimestampPrefix()}_${sanitizeMigrationName(name)}.sql`,
@ -437,34 +310,6 @@ export const generateMigration: GenerateMigrationFunction = async (name) => {
}; };
}; };
const storage = createMysqlStorage({
table: process.env['MYSQL_TABLE'],
connection: process.env['MYSQL_URL'] ?? {
host: process.env['MYSQL_HOST'],
port: process.env['MYSQL_PORT'] ? Number.parseInt(process.env['MYSQL_PORT'], 10) : undefined,
user: process.env['MYSQL_USER'],
password: process.env['MYSQL_PASSWORD'],
database: process.env['MYSQL_DATABASE'],
},
});
const loader = createMysqlLoader({
connection: process.env['MYSQL_URL'] ?? {
host: process.env['MYSQL_HOST'],
port: process.env['MYSQL_PORT'] ? Number.parseInt(process.env['MYSQL_PORT'], 10) : undefined,
user: process.env['MYSQL_USER'],
password: process.env['MYSQL_PASSWORD'],
database: process.env['MYSQL_DATABASE'],
},
});
// eslint-disable-next-line prefer-destructuring
export const initializeStorage: () => Promise<Storage> = storage.initializeStorage;
// eslint-disable-next-line prefer-destructuring
export const loadableExtensions: string[] = loader.loadableExtensions;
// eslint-disable-next-line prefer-destructuring
export const loadMigration: (migration: MigrationMetadata) => Awaitable<MigrationFunction> = loader.loadMigration;
const defaultExport: EmigrateStorage & LoaderPlugin & GeneratorPlugin = { const defaultExport: EmigrateStorage & LoaderPlugin & GeneratorPlugin = {
initializeStorage, initializeStorage,
loadableExtensions, loadableExtensions,

View file

@ -1,49 +0,0 @@
/* eslint @typescript-eslint/naming-convention:0, import/no-extraneous-dependencies: 0 */
import process from 'node:process';
import { GenericContainer, type StartedTestContainer } from 'testcontainers';
let container: StartedTestContainer | undefined;
export const startDatabase = async (): Promise<{ port: number; host: string }> => {
if (process.env['CI']) {
const config = {
port: process.env['MYSQL_PORT'] ? Number.parseInt(process.env['MYSQL_PORT'], 10) : 3306,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
host: process.env['MYSQL_HOST'] || 'localhost',
};
console.log(`Connecting to MySQL from environment variables: ${JSON.stringify(config)}`);
return config;
}
if (!container) {
console.log('Starting MySQL container...');
const containerSetup = new GenericContainer('mysql:8.2')
.withEnvironment({
MYSQL_ROOT_PASSWORD: 'admin',
MYSQL_USER: 'emigrate',
MYSQL_PASSWORD: 'emigrate',
MYSQL_DATABASE: 'emigrate',
})
.withTmpFs({ '/var/lib/mysql': 'rw' })
.withCommand(['--sql-mode=NO_ENGINE_SUBSTITUTION', '--default-authentication-plugin=mysql_native_password'])
.withExposedPorts(3306)
.withReuse();
container = await containerSetup.start();
console.log('MySQL container started');
}
return { port: container.getMappedPort(3306), host: container.getHost() };
};
export const stopDatabase = async (): Promise<void> => {
if (container) {
console.log('Stopping MySQL container...');
await container.stop();
console.log('MySQL container stopped');
container = undefined;
}
};

View file

@ -1,3 +1,8 @@
{ {
"extends": "@emigrate/tsconfig/build.json" "extends": "@emigrate/tsconfig/build.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
} }

View file

@ -1,21 +1,5 @@
# @emigrate/plugin-generate-js # @emigrate/plugin-generate-js
## 0.3.8
### Patch Changes
- Updated dependencies [d779286]
- @emigrate/plugin-tools@0.9.8
- @emigrate/types@0.12.2
## 0.3.7
### Patch Changes
- Updated dependencies [ca154fa]
- @emigrate/plugin-tools@0.9.7
- @emigrate/types@0.12.2
## 0.3.6 ## 0.3.6
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/plugin-generate-js", "name": "@emigrate/plugin-generate-js",
"version": "0.3.8", "version": "0.3.6",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },

View file

@ -1,3 +1,8 @@
{ {
"extends": "@emigrate/tsconfig/build.json" "extends": "@emigrate/tsconfig/build.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
} }

View file

@ -1,20 +1,5 @@
# @emigrate/plugin-tools # @emigrate/plugin-tools
## 0.9.8
### Patch Changes
- d779286: Upgrade TypeScript to v5.5 and enable [isolatedDeclarations](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#isolated-declarations)
- @emigrate/types@0.12.2
## 0.9.7
### Patch Changes
- ca154fa: Minimize package size by excluding \*.tsbuildinfo files
- Updated dependencies [ca154fa]
- @emigrate/types@0.12.2
## 0.9.6 ## 0.9.6
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/plugin-tools", "name": "@emigrate/plugin-tools",
"version": "0.9.8", "version": "0.9.6",
"publishConfig": { "publishConfig": {
"access": "public", "access": "public",
"provenance": true "provenance": true
@ -16,8 +16,7 @@
} }
}, },
"files": [ "files": [
"dist", "dist"
"!dist/*.tsbuildinfo"
], ],
"scripts": { "scripts": {
"build": "tsc --pretty", "build": "tsc --pretty",

View file

@ -204,7 +204,7 @@ const load = async <T>(
* *
* @returns A timestamp string in the format YYYYMMDDHHmmssmmm * @returns A timestamp string in the format YYYYMMDDHHmmssmmm
*/ */
export const getTimestampPrefix = (): string => new Date().toISOString().replaceAll(/[-:ZT.]/g, ''); export const getTimestampPrefix = () => new Date().toISOString().replaceAll(/[-:ZT.]/g, '');
/** /**
* A utility function to sanitize a migration name so that it can be used as a filename * A utility function to sanitize a migration name so that it can be used as a filename
@ -212,7 +212,7 @@ export const getTimestampPrefix = (): string => new Date().toISOString().replace
* @param name A migration name to sanitize * @param name A migration name to sanitize
* @returns A sanitized migration name that can be used as a filename * @returns A sanitized migration name that can be used as a filename
*/ */
export const sanitizeMigrationName = (name: string): string => export const sanitizeMigrationName = (name: string) =>
name name
.replaceAll(/[\W/\\:|*?'"<>_]+/g, '_') .replaceAll(/[\W/\\:|*?'"<>_]+/g, '_')
.trim() .trim()

View file

@ -1,3 +1,8 @@
{ {
"extends": "@emigrate/tsconfig/build.json" "extends": "@emigrate/tsconfig/build.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
} }

View file

@ -1,29 +1,5 @@
# @emigrate/postgres # @emigrate/postgres
## 0.3.2
### Patch Changes
- d779286: Upgrade TypeScript to v5.5 and enable [isolatedDeclarations](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#isolated-declarations)
- Updated dependencies [d779286]
- @emigrate/plugin-tools@0.9.8
- @emigrate/types@0.12.2
## 0.3.1
### Patch Changes
- ca154fa: Minimize package size by excluding \*.tsbuildinfo files
- Updated dependencies [ca154fa]
- @emigrate/plugin-tools@0.9.7
- @emigrate/types@0.12.2
## 0.3.0
### Minor Changes
- 4442604: Automatically create the database if it doesn't exist, and the user have the permissions to do so
## 0.2.6 ## 0.2.6
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/postgres", "name": "@emigrate/postgres",
"version": "0.3.2", "version": "0.2.6",
"publishConfig": { "publishConfig": {
"access": "public", "access": "public",
"provenance": true "provenance": true
@ -16,8 +16,7 @@
} }
}, },
"files": [ "files": [
"dist", "dist"
"!dist/*.tsbuildinfo"
], ],
"scripts": { "scripts": {
"build": "tsc --pretty", "build": "tsc --pretty",

View file

@ -11,8 +11,6 @@ import {
type GeneratorPlugin, type GeneratorPlugin,
type SerializedError, type SerializedError,
type MigrationHistoryEntry, type MigrationHistoryEntry,
type Awaitable,
type MigrationFunction,
} from '@emigrate/types'; } from '@emigrate/types';
const defaultTable = 'migrations'; const defaultTable = 'migrations';
@ -94,64 +92,6 @@ const deleteMigration = async (sql: Sql, table: string, migration: MigrationMeta
return result.count === 1; return result.count === 1;
}; };
const getDatabaseName = (config: ConnectionOptions | string) => {
if (typeof config === 'string') {
const uri = new URL(config);
return uri.pathname.replace(/^\//u, '');
}
return config.database ?? '';
};
const setDatabaseName = <T extends ConnectionOptions | string>(config: T, databaseName: string): T => {
if (typeof config === 'string') {
const uri = new URL(config);
uri.pathname = `/${databaseName}`;
return uri.toString() as T;
}
if (typeof config === 'object') {
return {
...config,
database: databaseName,
};
}
throw new Error('Invalid connection config');
};
const initializeDatabase = async (config: ConnectionOptions | string) => {
let sql: Sql | undefined;
try {
sql = await getPool(config);
await sql.end();
} catch (error) {
await sql?.end();
// The error code 3D000 means that the database does not exist, but the user might have the permissions to create it
if (error && typeof error === 'object' && 'code' in error && error.code === '3D000') {
const databaseName = getDatabaseName(config);
const postgresConfig = setDatabaseName(config, 'postgres');
const postgresSql = await getPool(postgresConfig);
try {
await postgresSql`CREATE DATABASE ${postgresSql(databaseName)}`;
// Any database creation error here will be propagated
} finally {
await postgresSql.end();
}
} else {
// In this case we don't know how to handle the error, so we rethrow it
throw error;
}
}
};
const initializeTable = async (sql: Sql, table: string) => { const initializeTable = async (sql: Sql, table: string) => {
const [row] = await sql<Array<{ exists: 1 }>>` const [row] = await sql<Array<{ exists: 1 }>>`
SELECT 1 as exists SELECT 1 as exists
@ -182,8 +122,6 @@ export const createPostgresStorage = ({
}: PostgresStorageOptions): EmigrateStorage => { }: PostgresStorageOptions): EmigrateStorage => {
return { return {
async initializeStorage() { async initializeStorage() {
await initializeDatabase(connection);
const sql = await getPool(connection); const sql = await getPool(connection);
try { try {
@ -257,6 +195,17 @@ export const createPostgresStorage = ({
}; };
}; };
export const { initializeStorage } = createPostgresStorage({
table: process.env['POSTGRES_TABLE'],
connection: process.env['POSTGRES_URL'] ?? {
host: process.env['POSTGRES_HOST'],
port: process.env['POSTGRES_PORT'] ? Number.parseInt(process.env['POSTGRES_PORT'], 10) : undefined,
user: process.env['POSTGRES_USER'],
password: process.env['POSTGRES_PASSWORD'],
database: process.env['POSTGRES_DB'],
},
});
export const createPostgresLoader = ({ connection }: PostgresLoaderOptions): LoaderPlugin => { export const createPostgresLoader = ({ connection }: PostgresLoaderOptions): LoaderPlugin => {
return { return {
loadableExtensions: ['.sql'], loadableExtensions: ['.sql'],
@ -275,6 +224,16 @@ export const createPostgresLoader = ({ connection }: PostgresLoaderOptions): Loa
}; };
}; };
export const { loadableExtensions, loadMigration } = createPostgresLoader({
connection: process.env['POSTGRES_URL'] ?? {
host: process.env['POSTGRES_HOST'],
port: process.env['POSTGRES_PORT'] ? Number.parseInt(process.env['POSTGRES_PORT'], 10) : undefined,
user: process.env['POSTGRES_USER'],
password: process.env['POSTGRES_PASSWORD'],
database: process.env['POSTGRES_DB'],
},
});
export const generateMigration: GenerateMigrationFunction = async (name) => { export const generateMigration: GenerateMigrationFunction = async (name) => {
return { return {
filename: `${getTimestampPrefix()}_${sanitizeMigrationName(name)}.sql`, filename: `${getTimestampPrefix()}_${sanitizeMigrationName(name)}.sql`,
@ -283,34 +242,6 @@ export const generateMigration: GenerateMigrationFunction = async (name) => {
}; };
}; };
const storage = createPostgresStorage({
table: process.env['POSTGRES_TABLE'],
connection: process.env['POSTGRES_URL'] ?? {
host: process.env['POSTGRES_HOST'],
port: process.env['POSTGRES_PORT'] ? Number.parseInt(process.env['POSTGRES_PORT'], 10) : undefined,
user: process.env['POSTGRES_USER'],
password: process.env['POSTGRES_PASSWORD'],
database: process.env['POSTGRES_DB'],
},
});
const loader = createPostgresLoader({
connection: process.env['POSTGRES_URL'] ?? {
host: process.env['POSTGRES_HOST'],
port: process.env['POSTGRES_PORT'] ? Number.parseInt(process.env['POSTGRES_PORT'], 10) : undefined,
user: process.env['POSTGRES_USER'],
password: process.env['POSTGRES_PASSWORD'],
database: process.env['POSTGRES_DB'],
},
});
// eslint-disable-next-line prefer-destructuring
export const initializeStorage: () => Promise<Storage> = storage.initializeStorage;
// eslint-disable-next-line prefer-destructuring
export const loadableExtensions: string[] = loader.loadableExtensions;
// eslint-disable-next-line prefer-destructuring
export const loadMigration: (migration: MigrationMetadata) => Awaitable<MigrationFunction> = loader.loadMigration;
const defaultExport: EmigrateStorage & LoaderPlugin & GeneratorPlugin = { const defaultExport: EmigrateStorage & LoaderPlugin & GeneratorPlugin = {
initializeStorage, initializeStorage,
loadableExtensions, loadableExtensions,

View file

@ -1,3 +1,8 @@
{ {
"extends": "@emigrate/tsconfig/build.json" "extends": "@emigrate/tsconfig/build.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
} }

View file

@ -1,26 +1,5 @@
# @emigrate/reporter-pino # @emigrate/reporter-pino
## 0.6.5
### Patch Changes
- d779286: Upgrade TypeScript to v5.5 and enable [isolatedDeclarations](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#isolated-declarations)
- @emigrate/types@0.12.2
## 0.6.4
### Patch Changes
- ca154fa: Minimize package size by excluding \*.tsbuildinfo files
- Updated dependencies [ca154fa]
- @emigrate/types@0.12.2
## 0.6.3
### Patch Changes
- 081ab34: Make sure Pino outputs logs in Bun environments
## 0.6.2 ## 0.6.2
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/reporter-pino", "name": "@emigrate/reporter-pino",
"version": "0.6.5", "version": "0.6.2",
"publishConfig": { "publishConfig": {
"access": "public", "access": "public",
"provenance": true "provenance": true
@ -16,8 +16,7 @@
} }
}, },
"files": [ "files": [
"dist", "dist"
"!dist/*.tsbuildinfo"
], ],
"scripts": { "scripts": {
"build": "tsc --pretty", "build": "tsc --pretty",
@ -41,9 +40,7 @@
"pino": "8.16.2" "pino": "8.16.2"
}, },
"devDependencies": { "devDependencies": {
"@emigrate/tsconfig": "workspace:*", "@emigrate/tsconfig": "workspace:*"
"@types/bun": "1.0.5",
"bun-types": "1.0.26"
}, },
"volta": { "volta": {
"extends": "../../package.json" "extends": "../../package.json"

View file

@ -52,7 +52,6 @@ class PinoReporter implements Required<EmigrateReporter> {
scope: command, scope: command,
version, version,
}, },
transport: process.isBun ? { target: 'pino/file', options: { destination: 1 } } : undefined,
}); });
this.#logger.info({ parameters }, `Emigrate "${command}" initialized${parameters.dry ? ' (dry-run)' : ''}`); this.#logger.info({ parameters }, `Emigrate "${command}" initialized${parameters.dry ? ' (dry-run)' : ''}`);
@ -204,8 +203,6 @@ export const createPinoReporter = (options: PinoReporterOptions = {}): EmigrateR
return new PinoReporter(options); return new PinoReporter(options);
}; };
const defaultExport: EmigrateReporter = createPinoReporter({ export default createPinoReporter({
level: process.env['LOG_LEVEL'], level: process.env['LOG_LEVEL'],
}); });
export default defaultExport;

View file

@ -1,3 +1,8 @@
{ {
"extends": "@emigrate/tsconfig/build.json" "extends": "@emigrate/tsconfig/build.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
} }

View file

@ -1,13 +1,5 @@
# @emigrate/storage-fs # @emigrate/storage-fs
## 0.4.7
### Patch Changes
- ca154fa: Minimize package size by excluding \*.tsbuildinfo files
- Updated dependencies [ca154fa]
- @emigrate/types@0.12.2
## 0.4.6 ## 0.4.6
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/storage-fs", "name": "@emigrate/storage-fs",
"version": "0.4.7", "version": "0.4.6",
"publishConfig": { "publishConfig": {
"access": "public", "access": "public",
"provenance": true "provenance": true
@ -16,8 +16,7 @@
} }
}, },
"files": [ "files": [
"dist", "dist"
"!dist/*.tsbuildinfo"
], ],
"scripts": { "scripts": {
"build": "tsc --pretty", "build": "tsc --pretty",

View file

@ -1,3 +1,8 @@
{ {
"extends": "@emigrate/tsconfig/build.json" "extends": "@emigrate/tsconfig/build.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
} }

View file

@ -1,11 +1,5 @@
# @emigrate/tsconfig # @emigrate/tsconfig
## 1.0.3
### Patch Changes
- d779286: Upgrade TypeScript to v5.5 and enable [isolatedDeclarations](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#isolated-declarations)
## 1.0.2 ## 1.0.2
### Patch Changes ### Patch Changes

View file

@ -11,7 +11,6 @@
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"inlineSources": false, "inlineSources": false,
"isolatedModules": true, "isolatedModules": true,
"isolatedDeclarations": true,
"incremental": true, "incremental": true,
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
@ -32,7 +31,5 @@
"strict": true, "strict": true,
"target": "ES2022", "target": "ES2022",
"lib": ["ESNext", "DOM", "DOM.Iterable"] "lib": ["ESNext", "DOM", "DOM.Iterable"]
}, }
"include": ["${configDir}/src"],
"exclude": ["${configDir}/dist"]
} }

View file

@ -3,7 +3,6 @@
"display": "Build", "display": "Build",
"extends": "./base.json", "extends": "./base.json",
"compilerOptions": { "compilerOptions": {
"noEmit": false, "noEmit": false
"outDir": "${configDir}/dist"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/tsconfig", "name": "@emigrate/tsconfig",
"version": "1.0.3", "version": "1.0.2",
"publishConfig": { "publishConfig": {
"access": "public", "access": "public",
"provenance": true "provenance": true

View file

@ -1,11 +1,5 @@
# @emigrate/types # @emigrate/types
## 0.12.2
### Patch Changes
- ca154fa: Minimize package size by excluding \*.tsbuildinfo files
## 0.12.1 ## 0.12.1
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@emigrate/types", "name": "@emigrate/types",
"version": "0.12.2", "version": "0.12.1",
"publishConfig": { "publishConfig": {
"access": "public", "access": "public",
"provenance": true "provenance": true
@ -16,8 +16,7 @@
} }
}, },
"files": [ "files": [
"dist", "dist"
"!dist/*.tsbuildinfo"
], ],
"scripts": { "scripts": {
"build": "tsc --pretty", "build": "tsc --pretty",

View file

@ -1,3 +1,8 @@
{ {
"extends": "@emigrate/tsconfig/build.json" "extends": "@emigrate/tsconfig/build.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
} }

11946
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
{ {
"$schema": "https://turborepo.org/schema.json", "$schema": "https://turborepo.org/schema.json",
"ui": "stream", "pipeline": {
"tasks": {
"build": { "build": {
"dependsOn": ["^build"], "dependsOn": ["^build"],
"inputs": ["src/**/*", "!src/**/*.test.ts", "tsconfig.json", "tsconfig.build.json"], "inputs": ["src/**/*", "!src/**/*.test.ts", "tsconfig.json", "tsconfig.build.json"],