diff --git a/README.md b/README.md index 52ae926..733cf78 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,16 @@ npm install -g mongo-migrate-ts Usage: mongo-migrate [options] [command] Options: - -h, --help output usage information + -h, --help display help for command Commands: - init Creates the migrations directory and configuration file - new [options] Create a new migration file under migrations directory - up Run all pending migrations - down [options] Undo migrations - status Show the status of the migrations + init Creates the migrations directory and configuration file + new [options] Create a new migration file under migrations directory + up Run all pending migrations + down [options] Undo migrations + status Show the status of the migrations + fix-paths [options] Convert absolute migration file paths into relative paths + help [command] display help for command ``` Create a directory for your migrations. @@ -175,4 +177,15 @@ export class Transaction1691171075957 implements MigrationInterface { } } } -``` \ No newline at end of file +``` + +## Fixing absolute migration file paths + +Old versions of this package used to store migration file paths as absolute paths which meant a rollback could only be performed from the same machine that executed the migration, or one with a similar directory structure. + +To fix these migration entries in your database you can use the `fix-paths` command: + +``` +ts-node migrations/index.ts fix-paths --base-path "[PATH_TO_MIGRATIONS_DIR_AS_SAVED_IN_DATABASE]" --dry-run +``` + diff --git a/__tests__/down.test.ts b/__tests__/down.test.ts index 0079df5..6b5bbe2 100644 --- a/__tests__/down.test.ts +++ b/__tests__/down.test.ts @@ -43,7 +43,7 @@ describe('down command', () => { new Promise((resolve) => resolve(fakeMigrations)) ); - (loadMigrationFile as jest.Mock).mockImplementation((file: string) => [ + (loadMigrationFile as jest.Mock).mockImplementation((_migrationsDir:string, file: string) => [ { ...fakeMigrations.find((m: MigrationModel) => m.file === file), instance: fakeMigrationInstance, diff --git a/__tests__/fixPaths.test.ts b/__tests__/fixPaths.test.ts new file mode 100644 index 0000000..179e5c1 --- /dev/null +++ b/__tests__/fixPaths.test.ts @@ -0,0 +1,83 @@ +jest.mock('../lib/database'); +jest.mock('../lib/migrations'); + +import { oraMock } from './__mocks__/ora.mock'; +jest.mock('ora', () => { + return jest.fn().mockImplementation(oraMock); +}); + +import { MigrationModel, mongoConnect } from '../lib/database'; +import { configMock } from './__mocks__/config.mock'; +import { connectionMock } from './__mocks__/connection.mock'; +import { fixPaths } from '../lib/commands/fixPaths'; + +describe('fixPaths command', () => { + const numberOfMigrations = 10; + const fakeMigrations: MigrationModel[] = Array(numberOfMigrations) + .fill(undefined) + .map((v: MigrationModel, index: number) => ({ + _id: `${index}`, + className: `MigrationTest${index}`, + file: `${ + index % 3 === 0 ? '/home/runner/migrations/' : '/home/user/project/' + }MigrationTest${index}.ts`, + timestamp: +new Date(), + })); + + (mongoConnect as jest.Mock).mockReturnValue( + new Promise((resolve) => resolve(connectionMock)) + ); + + const createMockCursor = () => { + let i = 0; + return { + hasNext: () => i < fakeMigrations.length, + next: () => { + const item = fakeMigrations[i]; + i += 1; + return item ?? null; + }, + }; + }; + + const mockUpdateOne = jest.fn(); + + beforeEach(() => { + (connectionMock.client.close as jest.Mock).mockReset(); + connectionMock.getMigrationsCollection.mockReset(); + mockUpdateOne.mockReset(); + + connectionMock.getMigrationsCollection.mockReturnValue({ + updateOne: mockUpdateOne, + find: jest.fn().mockImplementation(createMockCursor), + }); + }); + + it('should convert absolute paths only of matching migrations', async () => { + const basePath = '/home/runner/migrations'; + await fixPaths({ config: configMock, basePath }); + + fakeMigrations.forEach((fakeMigration, index) => { + (fakeMigration.file.startsWith(basePath) + ? expect(mockUpdateOne) + : expect(mockUpdateOne).not + ).toHaveBeenCalledWith( + { _id: `${index}` }, + { $set: { file: `MigrationTest${index}.ts` } } + ); + }); + + expect(connectionMock.client.close).toBeCalled(); + }); + + it('should not alter the database in dry run mode', async () => { + await fixPaths({ + config: configMock, + dryRun: true, + basePath: '/home/runner/migrations', + }); + + expect(mockUpdateOne).not.toHaveBeenCalled(); + expect(connectionMock.client.close).toBeCalled(); + }); +}); diff --git a/lib/cli.ts b/lib/cli.ts index 194b582..73f7a78 100644 --- a/lib/cli.ts +++ b/lib/cli.ts @@ -4,6 +4,7 @@ import { init } from './commands/init'; import { newCommand } from './commands/new'; import { status } from './commands/status'; import { up } from './commands/up'; +import { fixPaths } from './commands/fixPaths'; import { Config, processConfig } from './config'; export const cli = (config?: Config): void => { @@ -86,6 +87,22 @@ export const cli = (config?: Config): void => { .action(async () => { await status({ config }); }); + + program + .command('fix-paths') + .description('Convert absolute migration file paths into relative paths') + .option( + '-p, --base-path <path>', + 'Override the base path. The absolute migration dir on the current machine is used as default' + ) + .option('-d, --dry-run', "Show updates but don't apply them") + .action(async (opts) => { + await fixPaths({ + config, + basePath: opts.basePath, + dryRun: !!opts.dryRun, + }); + }); } program.parse(); diff --git a/lib/commands/down.ts b/lib/commands/down.ts index af9eac0..2747546 100644 --- a/lib/commands/down.ts +++ b/lib/commands/down.ts @@ -20,7 +20,8 @@ interface CommandDownOptions { const downLastAppliedMigration = async ( connection: DatabaseConnection, - collection: Collection<MigrationModel> + collection: Collection<MigrationModel>, + migrationsDir: string ): Promise<void> => { const spinner = ora(`Undoing last migration`).start(); const lastApplied = await getLastAppliedMigration(collection); @@ -31,7 +32,7 @@ const downLastAppliedMigration = async ( } spinner.text = `Undoing migration ${lastApplied.className}`; - const migrationFile = await loadMigrationFile(lastApplied.file); + const migrationFile = await loadMigrationFile(migrationsDir, lastApplied.file); const migration = migrationFile.find( (m: MigrationObject) => m.className === lastApplied.className ); @@ -51,7 +52,8 @@ const downLastAppliedMigration = async ( const downAll = async ( connection: DatabaseConnection, - collection: Collection<MigrationModel> + collection: Collection<MigrationModel>, + migrationsDir: string ): Promise<void> => { const spinner = ora(`Undoing all migrations`).start(); const appliedMigrations = await getAppliedMigrations(collection); @@ -63,7 +65,7 @@ const downAll = async ( const migrationsToUndo = await Promise.all( appliedMigrations.map(async (migration: MigrationModel) => { - const m = await loadMigrationFile(migration.file); + const m = await loadMigrationFile(migrationsDir, migration.file); if (m && m.length === 0) { throw new Error( `Can undo migration ${migration.className}, no class found` @@ -95,17 +97,17 @@ export const down = async ({ mode, config, }: CommandDownOptions): Promise<void> => { - const { uri, database, options, migrationsCollection } = + const { uri, database, options, migrationsCollection, migrationsDir } = processConfig(config); const connection = await mongoConnect(uri, database, options); const collection = connection.getMigrationsCollection(migrationsCollection); try { switch (mode) { case 'all': - await downAll(connection, collection); + await downAll(connection, collection, migrationsDir); break; case 'last': - await downLastAppliedMigration(connection, collection); + await downLastAppliedMigration(connection, collection, migrationsDir); break; } } finally { diff --git a/lib/commands/fixPaths.ts b/lib/commands/fixPaths.ts new file mode 100644 index 0000000..7222843 --- /dev/null +++ b/lib/commands/fixPaths.ts @@ -0,0 +1,53 @@ +import * as path from 'path'; +import { Config, processConfig } from '../config'; +import { mongoConnect } from '../database'; + +interface CommandFixPathsOptions { + config: Config; + basePath?: string; + dryRun?: boolean; +} + +export const fixPaths = async ({ + basePath, + config, + dryRun, +}: CommandFixPathsOptions): Promise<void> => { + const { uri, database, options, migrationsCollection, migrationsDir } = + processConfig(config); + const connection = await mongoConnect(uri, database, options); + const collection = connection.getMigrationsCollection(migrationsCollection); + + const basePathWithMigrationsDirDefault = + basePath ?? path.resolve(migrationsDir); + + + try { + const cursor = collection.find({ + file: new RegExp(`^${basePathWithMigrationsDirDefault}.*`), + }); + + while (await cursor.hasNext()) { + const migration = await cursor.next(); + if (!migration) break; + + const newFilePath = path.relative( + basePathWithMigrationsDirDefault, + migration.file + ); + + console.log( + `Updating migration path "${migration.file}" to "${newFilePath}"` + ); + + if (!dryRun) { + await collection.updateOne( + { _id: migration._id }, + { $set: { file: newFilePath } } + ); + } + } + } finally { + await connection.client.close(); + } +}; diff --git a/lib/migrations.ts b/lib/migrations.ts index 8cb7df6..b0faebd 100644 --- a/lib/migrations.ts +++ b/lib/migrations.ts @@ -21,13 +21,17 @@ const isMigration = (obj: any): boolean => { }; export const loadMigrationFile = async ( - filePath: string + migrationsDir: string, + filePath: string, ): Promise<MigrationObject[]> => { - if (!fs.existsSync(filePath)) { - throw new Error(`File ${filePath} not exists.`); + + const absoluteFilePath = path.resolve(migrationsDir, filePath); + + if (!fs.existsSync(absoluteFilePath)) { + throw new Error(`File ${absoluteFilePath} does not exist.`); } - const classes = await import(path.resolve(filePath)); + const classes = await import(path.resolve(absoluteFilePath)); return Object.keys(classes) .filter((key: string) => typeof classes[key] === 'function') @@ -51,7 +55,7 @@ export const loadMigrations = async ( const migrations = await Promise.all( paths .map((path) => (typeof path === 'string' ? path : path.fullpath())) - .map((path) => loadMigrationFile(`${migrationsDir}/${path}`)) + .map((path) => loadMigrationFile(migrationsDir, path)) ); // flat migrations because in one file can be more than one migration diff --git a/package.json b/package.json index a2d0134..6298406 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "mongodb": "^6.2.0", "prettier": "^2.5.1", "rimraf": "^4.4.0", - "rollup": "^2.66.1", + "rollup": "^4.24.0", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-peer-deps-external": "^2.2.4", @@ -58,8 +58,8 @@ "cli-table": "^0.3.11", "commander": "^9.0.0", "connection-string": "^4.3.5", - "glob": "^10.3.12", "date-fns": "^2.30.0", + "glob": "^10.3.12", "ora": "5.4.1" }, "peerDependencies": { @@ -67,7 +67,8 @@ }, "resolutions": { "string-width": "4.2.3", - "strip-ansi": "6.0.1" + "strip-ansi": "6.0.1", + "micromatch": ">=4.0.8" }, "lint-staged": { "*.{ts,js}": [ diff --git a/yarn.lock b/yarn.lock index 9831c2e..56b3c81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -971,6 +971,86 @@ estree-walker "^2.0.1" picomatch "^2.2.2" +"@rollup/rollup-android-arm-eabi@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz#1661ff5ea9beb362795304cb916049aba7ac9c54" + integrity sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA== + +"@rollup/rollup-android-arm64@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz#2ffaa91f1b55a0082b8a722525741aadcbd3971e" + integrity sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA== + +"@rollup/rollup-darwin-arm64@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz#627007221b24b8cc3063703eee0b9177edf49c1f" + integrity sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA== + +"@rollup/rollup-darwin-x64@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz#0605506142b9e796c370d59c5984ae95b9758724" + integrity sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz#62dfd196d4b10c0c2db833897164d2d319ee0cbb" + integrity sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA== + +"@rollup/rollup-linux-arm-musleabihf@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz#53ce72aeb982f1f34b58b380baafaf6a240fddb3" + integrity sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw== + +"@rollup/rollup-linux-arm64-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz#1632990f62a75c74f43e4b14ab3597d7ed416496" + integrity sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA== + +"@rollup/rollup-linux-arm64-musl@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz#8c03a996efb41e257b414b2e0560b7a21f2d9065" + integrity sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw== + +"@rollup/rollup-linux-powerpc64le-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz#5b98729628d5bcc8f7f37b58b04d6845f85c7b5d" + integrity sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw== + +"@rollup/rollup-linux-riscv64-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz#48e42e41f4cabf3573cfefcb448599c512e22983" + integrity sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg== + +"@rollup/rollup-linux-s390x-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz#e0b4f9a966872cb7d3e21b9e412a4b7efd7f0b58" + integrity sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g== + +"@rollup/rollup-linux-x64-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz#78144741993100f47bd3da72fce215e077ae036b" + integrity sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A== + +"@rollup/rollup-linux-x64-musl@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz#d9fe32971883cd1bd858336bd33a1c3ca6146127" + integrity sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ== + +"@rollup/rollup-win32-arm64-msvc@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz#71fa3ea369316db703a909c790743972e98afae5" + integrity sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ== + +"@rollup/rollup-win32-ia32-msvc@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz#653f5989a60658e17d7576a3996deb3902e342e2" + integrity sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ== + +"@rollup/rollup-win32-x64-msvc@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz#0574d7e87b44ee8511d08cc7f914bcb802b70818" + integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw== + "@sec-ant/readable-stream@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" @@ -1265,6 +1345,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -1790,7 +1875,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -4675,12 +4760,12 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== +micromatch@>=4.0.8, micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime@^4.0.0: @@ -5868,11 +5953,29 @@ rollup-pluginutils@^2.8.1: dependencies: estree-walker "^0.6.1" -rollup@^2.66.1: - version "2.79.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" - integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== +rollup@^4.24.0: + version "4.24.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.0.tgz#c14a3576f20622ea6a5c9cad7caca5e6e9555d05" + integrity sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg== + dependencies: + "@types/estree" "1.0.6" optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.24.0" + "@rollup/rollup-android-arm64" "4.24.0" + "@rollup/rollup-darwin-arm64" "4.24.0" + "@rollup/rollup-darwin-x64" "4.24.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.24.0" + "@rollup/rollup-linux-arm-musleabihf" "4.24.0" + "@rollup/rollup-linux-arm64-gnu" "4.24.0" + "@rollup/rollup-linux-arm64-musl" "4.24.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.24.0" + "@rollup/rollup-linux-riscv64-gnu" "4.24.0" + "@rollup/rollup-linux-s390x-gnu" "4.24.0" + "@rollup/rollup-linux-x64-gnu" "4.24.0" + "@rollup/rollup-linux-x64-musl" "4.24.0" + "@rollup/rollup-win32-arm64-msvc" "4.24.0" + "@rollup/rollup-win32-ia32-msvc" "4.24.0" + "@rollup/rollup-win32-x64-msvc" "4.24.0" fsevents "~2.3.2" run-parallel@^1.1.9: