migrate-mongo
Advanced tools
| const statusMock = vi.fn(); | ||
| export default statusMock; |
| // CommonJS wrapper for backward compatibility | ||
| const esmModule = import('./migrate-mongo.js'); | ||
| module.exports = esmModule.then(m => m.default || m); | ||
| // Also support direct property access for CommonJS users | ||
| const handler = { | ||
| get(target, prop) { | ||
| return esmModule.then(m => m[prop] || m.default[prop]); | ||
| } | ||
| }; | ||
| module.exports = new Proxy({}, handler); |
| const mock = { | ||
| stat: vi.fn(), | ||
| cp: vi.fn(), | ||
| mkdir: vi.fn(), | ||
| readdir: vi.fn(), | ||
| readFile: vi.fn(), | ||
| }; | ||
| export default mock; |
| const promises = { | ||
| stat: vi.fn(), | ||
| cp: vi.fn(), | ||
| mkdir: vi.fn(), | ||
| readdir: vi.fn(), | ||
| readFile: vi.fn(), | ||
| }; | ||
| export default promises; |
| export const MongoClient = { | ||
| connect: vi.fn(), | ||
| }; | ||
| export default { | ||
| MongoClient, | ||
| }; |
| import { vi } from 'vitest'; | ||
| // Make vitest functions globally available | ||
| global.vi = vi; |
| import { describe, it, expect } from 'vitest'; | ||
| import moduleLoader from '../../lib/utils/module-loader.js'; | ||
| describe('module-loader', () => { | ||
| describe('require', () => { | ||
| it('should load CommonJS modules', () => { | ||
| const result = moduleLoader.require('path'); | ||
| expect(result).toBeDefined(); | ||
| expect(typeof result.join).toBe('function'); | ||
| }); | ||
| }); | ||
| describe('import', () => { | ||
| it('should load ES modules', async () => { | ||
| const result = await moduleLoader.import('path'); | ||
| expect(result).toBeDefined(); | ||
| expect(typeof result.join).toBe('function'); | ||
| }); | ||
| }); | ||
| }); |
| import { defineConfig } from 'vitest/config'; | ||
| export default defineConfig({ | ||
| test: { | ||
| globals: true, | ||
| setupFiles: ['./test/setup.js'], | ||
| environment: 'node', | ||
| coverage: { | ||
| provider: 'v8', | ||
| reporter: ['text', 'html', 'lcov'], | ||
| include: [ | ||
| 'lib/**/*.js', | ||
| ], | ||
| exclude: [ | ||
| 'lib/**/*.test.js', | ||
| 'lib/migrate-mongo.js', // Main export file - no logic to test | ||
| 'lib/migrate-mongo.cjs', // CommonJS wrapper | ||
| 'test/**', | ||
| 'node_modules/**', | ||
| ], | ||
| }, | ||
| include: [ | ||
| 'test/**/*.test.js', | ||
| ], | ||
| exclude: [ | ||
| 'node_modules/**', | ||
| ], | ||
| clearMocks: true, | ||
| restoreMocks: true, | ||
| mockReset: true, | ||
| }, | ||
| }); |
| #! /usr/bin/env node | ||
| const { Command } = require("commander"); | ||
| const Table = require("cli-table3"); | ||
| const migrateMongo = require("../lib/migrate-mongo"); | ||
| import { Command } from "commander"; | ||
| import Table from "cli-table3"; | ||
| import { createRequire } from 'module'; | ||
| import migrateMongo from "../lib/migrate-mongo.js"; | ||
| const require = createRequire(import.meta.url); | ||
| const pkgjson = require("../package.json"); | ||
@@ -7,0 +10,0 @@ |
+17
-0
@@ -7,2 +7,19 @@ # Changelog | ||
| ## [14.0.6] - 2025-12-03 | ||
| - Migrate from Jest to Vitest for testing | ||
| - Full ESM support with native import/export | ||
| - Backward compatibility: migration scripts can still use CommonJS | ||
| - Global test functions (it, describe, expect, etc.) via setup file | ||
| - 100% code coverage maintained with unit tests alone | ||
| - Faster test execution and better developer experience | ||
| - Switch codebase to ESM (ECMAScript Modules) | ||
| - Use native import/export syntax throughout | ||
| - Maintain full CommonJS compatibility for migration scripts | ||
| - Update package.json with "type": "module" | ||
| - Add .js extensions to all relative imports | ||
| - Add CommonJS migration script tests to integration suite | ||
| - Verify backward compatibility with require/module.exports | ||
| - Ensure existing migration scripts continue to work | ||
| - Test both ESM and CommonJS migration formats | ||
| ## [14.0.5] - 2025-12-03 | ||
@@ -9,0 +26,0 @@ - Fix CI workflow to run integration tests only after successful unit tests |
+28
-13
@@ -1,19 +0,34 @@ | ||
| const jestPlugin = require("eslint-plugin-jest"); | ||
| import vitestPlugin from "eslint-plugin-vitest"; | ||
| module.exports = [ | ||
| export default [ | ||
| { | ||
| files: ["test/**/*.js"], | ||
| ...jestPlugin.configs['flat/recommended'], | ||
| plugins: { | ||
| vitest: vitestPlugin, | ||
| }, | ||
| rules: { | ||
| ...jestPlugin.configs['flat/recommended'].rules, | ||
| 'jest/prefer-expect-assertions': 'off', | ||
| 'jest/no-conditional-expect': 'off', // We use expect in try/catch blocks | ||
| 'jest/expect-expect': 'off', // We use expect.rejects and expect.resolves | ||
| 'jest/valid-title': 'warn', // Allow duplicate prefixes in nested describes | ||
| 'jest/no-disabled-tests': 'warn', | ||
| 'jest/no-focused-tests': 'error', | ||
| 'jest/no-identical-title': 'error', | ||
| 'jest/valid-expect': 'error' | ||
| } | ||
| ...vitestPlugin.configs.recommended.rules, | ||
| 'vitest/prefer-expect-assertions': 'off', | ||
| 'vitest/no-conditional-expect': 'off', // We use expect in try/catch blocks | ||
| 'vitest/expect-expect': 'off', // We use expect.rejects and expect.resolves | ||
| 'vitest/valid-title': 'warn', // Allow duplicate prefixes in nested describes | ||
| 'vitest/no-disabled-tests': 'warn', | ||
| 'vitest/no-focused-tests': 'error', | ||
| 'vitest/no-identical-title': 'error', | ||
| 'vitest/valid-expect': 'error' | ||
| }, | ||
| languageOptions: { | ||
| globals: { | ||
| ...vitestPlugin.environments.env.globals, | ||
| describe: 'readonly', | ||
| it: 'readonly', | ||
| expect: 'readonly', | ||
| beforeEach: 'readonly', | ||
| afterEach: 'readonly', | ||
| beforeAll: 'readonly', | ||
| afterAll: 'readonly', | ||
| vi: 'readonly', | ||
| }, | ||
| }, | ||
| } | ||
| ]; |
@@ -1,8 +0,12 @@ | ||
| const fs = require("fs/promises"); | ||
| const path = require("path"); | ||
| const date = require("../utils/date"); | ||
| const migrationsDir = require("../env/migrationsDir"); | ||
| const config = require("../env/config"); | ||
| import fs from "fs/promises"; | ||
| import path from "path"; | ||
| import { fileURLToPath } from 'url'; | ||
| import date from "../utils/date.js"; | ||
| import migrationsDir from "../env/migrationsDir.js"; | ||
| import config from "../env/config.js"; | ||
| module.exports = async description => { | ||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = path.dirname(__filename); | ||
| export default async (description) => { | ||
| if (!description) { | ||
@@ -9,0 +13,0 @@ throw new Error("Missing parameter: description"); |
@@ -1,7 +0,7 @@ | ||
| const status = require("./status"); | ||
| const config = require("../env/config"); | ||
| const migrationsDir = require("../env/migrationsDir"); | ||
| const lock = require("../utils/lock"); | ||
| import status from "./status.js"; | ||
| import config from "../env/config.js"; | ||
| import migrationsDir from "../env/migrationsDir.js"; | ||
| import lock from "../utils/lock.js"; | ||
| module.exports = async (db, client) => { | ||
| export default async (db, client) => { | ||
| const isBlockRollback = global.options?.block; | ||
@@ -8,0 +8,0 @@ const downgraded = []; |
@@ -1,7 +0,11 @@ | ||
| const fs = require("fs/promises"); | ||
| const path = require("path"); | ||
| import fs from "fs/promises"; | ||
| import path from "path"; | ||
| import { fileURLToPath } from 'url'; | ||
| const migrationsDir = require("../env/migrationsDir"); | ||
| const config = require("../env/config"); | ||
| import migrationsDir from "../env/migrationsDir.js"; | ||
| import config from "../env/config.js"; | ||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = path.dirname(__filename); | ||
| async function copySampleConfigFile() { | ||
@@ -21,3 +25,3 @@ const moduleSystem = global.options.module === 'esm' ? 'esm' : 'commonjs'; | ||
| module.exports = async () => { | ||
| export default async () => { | ||
| await migrationsDir.shouldNotExist(); | ||
@@ -24,0 +28,0 @@ await config.shouldNotExist(); |
@@ -1,5 +0,5 @@ | ||
| const migrationsDir = require("../env/migrationsDir"); | ||
| const config = require("../env/config"); | ||
| import migrationsDir from "../env/migrationsDir.js"; | ||
| import config from "../env/config.js"; | ||
| module.exports = async db => { | ||
| export default async (db) => { | ||
| await migrationsDir.shouldExist(); | ||
@@ -6,0 +6,0 @@ await config.shouldExist(); |
@@ -1,7 +0,7 @@ | ||
| const status = require("./status"); | ||
| const config = require("../env/config"); | ||
| const migrationsDir = require("../env/migrationsDir"); | ||
| const lock = require("../utils/lock"); | ||
| import status from "./status.js"; | ||
| import config from "../env/config.js"; | ||
| import migrationsDir from "../env/migrationsDir.js"; | ||
| import lock from "../utils/lock.js"; | ||
| module.exports = async (db, client) => { | ||
| export default async (db, client) => { | ||
| const statusItems = await status(db); | ||
@@ -8,0 +8,0 @@ const pendingItems = statusItems.filter(item => item.appliedAt === "PENDING"); |
@@ -1,5 +0,5 @@ | ||
| const fs = require("fs/promises"); | ||
| const path = require("path"); | ||
| const url = require("url"); | ||
| const moduleLoader = require('../utils/module-loader'); | ||
| import fs from "fs/promises"; | ||
| import path from "path"; | ||
| import url from "url"; | ||
| import moduleLoader from '../utils/module-loader.js'; | ||
@@ -27,3 +27,3 @@ const DEFAULT_CONFIG_FILE_NAME = "migrate-mongo-config.js"; | ||
| module.exports = { | ||
| export default { | ||
| DEFAULT_CONFIG_FILE_NAME, | ||
@@ -30,0 +30,0 @@ |
@@ -1,5 +0,5 @@ | ||
| const { MongoClient } = require("mongodb"); | ||
| const config = require("./config"); | ||
| import { MongoClient } from "mongodb"; | ||
| import config from "./config.js"; | ||
| module.exports = { | ||
| export default { | ||
| async connect() { | ||
@@ -6,0 +6,0 @@ const configContent = await config.read(); |
+12
-12
@@ -1,7 +0,7 @@ | ||
| const fs = require("fs/promises"); | ||
| const path = require("path"); | ||
| const url = require("url"); | ||
| const crypto = require("crypto"); | ||
| const config = require("./config"); | ||
| const moduleLoader = require('../utils/module-loader'); | ||
| import fs from "fs/promises"; | ||
| import path from "path"; | ||
| import url from "url"; | ||
| import crypto from "crypto"; | ||
| import config from "./config.js"; | ||
| import moduleLoader from '../utils/module-loader.js'; | ||
@@ -59,3 +59,8 @@ const DEFAULT_MIGRATIONS_DIR_NAME = "migrations"; | ||
| module.exports = { | ||
| function getModuleExports(module) { | ||
| // If ESM module format need to return default export | ||
| return module.default ? module.default : module; | ||
| } | ||
| export default { | ||
| resolve: resolveMigrationsDirPath, | ||
@@ -134,6 +139,1 @@ resolveSampleMigrationPath, | ||
| function getModuleExports(module) { | ||
| // If ESM module format need to return default export | ||
| return module.default ? module.default : module; | ||
| } | ||
+9
-16
@@ -1,17 +0,10 @@ | ||
| const init = require("./actions/init"); | ||
| const create = require("./actions/create"); | ||
| const up = require("./actions/up"); | ||
| const down = require("./actions/down"); | ||
| const status = require("./actions/status"); | ||
| const database = require("./env/database"); | ||
| const config = require("./env/config"); | ||
| import init from "./actions/init.js"; | ||
| import create from "./actions/create.js"; | ||
| import up from "./actions/up.js"; | ||
| import down from "./actions/down.js"; | ||
| import status from "./actions/status.js"; | ||
| import database from "./env/database.js"; | ||
| import config from "./env/config.js"; | ||
| module.exports = { | ||
| init, | ||
| create, | ||
| up, | ||
| down, | ||
| status, | ||
| database, | ||
| config | ||
| }; | ||
| export { init, create, up, down, status, database, config }; | ||
| export default { init, create, up, down, status, database, config }; |
@@ -25,5 +25,3 @@ const now = (dateString = Date.now()) => { | ||
| module.exports = { | ||
| now, | ||
| nowAsString | ||
| }; | ||
| export { now, nowAsString }; | ||
| export default { now, nowAsString }; |
@@ -1,2 +0,2 @@ | ||
| const config = require('../env/config'); | ||
| import config from '../env/config.js'; | ||
@@ -38,6 +38,3 @@ async function getLockCollection(db) { | ||
| module.exports = { | ||
| exist, | ||
| activate, | ||
| clear, | ||
| } | ||
| export { exist, activate, clear }; | ||
| export default { exist, activate, clear }; |
@@ -1,7 +0,12 @@ | ||
| module.exports = { | ||
| import { createRequire } from 'module'; | ||
| import { pathToFileURL } from 'url'; | ||
| import path from 'path'; | ||
| export default { | ||
| require(requirePath) { | ||
| return require(requirePath); | ||
| // Create require from the user's working directory context, not migrate-mongo's | ||
| const requireFunc = createRequire(pathToFileURL(path.join(process.cwd(), 'package.json'))); | ||
| return requireFunc(requirePath); | ||
| }, | ||
| /* istanbul ignore next */ | ||
| import(importPath) { | ||
@@ -8,0 +13,0 @@ return import(importPath); |
+16
-8
| { | ||
| "name": "migrate-mongo", | ||
| "version": "14.0.5", | ||
| "version": "14.0.6", | ||
| "description": "A database migration tool for MongoDB in Node", | ||
| "type": "module", | ||
| "main": "lib/migrate-mongo.js", | ||
| "exports": { | ||
| ".": { | ||
| "import": "./lib/migrate-mongo.js", | ||
| "require": "./lib/migrate-mongo.cjs" | ||
| } | ||
| }, | ||
| "bin": { | ||
@@ -10,6 +17,6 @@ "migrate-mongo": "bin/migrate-mongo.js" | ||
| "scripts": { | ||
| "test": "jest --coverage", | ||
| "test:watch": "jest --watch", | ||
| "test:ci": "jest --coverage --ci", | ||
| "test:integration": "jest test/integration.test.js --runInBand --detectOpenHandles --testPathIgnorePatterns=/node_modules/", | ||
| "test": "vitest run --coverage --exclude test/integration.test.js", | ||
| "test:watch": "vitest --exclude test/integration.test.js", | ||
| "test:ci": "vitest run --coverage --exclude test/integration.test.js", | ||
| "test:integration": "vitest run test/integration.test.js --no-coverage", | ||
| "lint": "eslint lib/ test/", | ||
@@ -39,7 +46,8 @@ "lint:fix": "eslint lib/ test/ --fix" | ||
| "devDependencies": { | ||
| "@vitest/coverage-v8": "^4.0.15", | ||
| "eslint": "^9.39.1", | ||
| "eslint-config-prettier": "^10.1.8", | ||
| "eslint-plugin-jest": "^29.2.1", | ||
| "jest": "^30.2.0", | ||
| "mongodb-memory-server": "^10.4.0" | ||
| "eslint-plugin-vitest": "^0.5.4", | ||
| "mongodb-memory-server": "^10.4.0", | ||
| "vitest": "^4.0.15" | ||
| }, | ||
@@ -46,0 +54,0 @@ "overrides": { |
@@ -1,27 +0,24 @@ | ||
| jest.mock("fs/promises", () => ({ | ||
| stat: jest.fn(), | ||
| cp: jest.fn(), | ||
| mkdir: jest.fn(), | ||
| readdir: jest.fn(), | ||
| readFile: jest.fn(), | ||
| })); | ||
| import { fileURLToPath } from 'url'; | ||
| const __dirname = fileURLToPath(new URL('.', import.meta.url)); | ||
| const path = require("path"); | ||
| const fs = require("fs/promises"); | ||
| const config = require("../../lib/env/config"); | ||
| const migrationsDir = require("../../lib/env/migrationsDir"); | ||
| const create = require("../../lib/actions/create"); | ||
| import path from "path"; | ||
| import fs from "fs/promises"; | ||
| import config from "../../lib/env/config.js"; | ||
| import migrationsDir from "../../lib/env/migrationsDir.js"; | ||
| import create from "../../lib/actions/create.js"; | ||
| describe("create", () => { | ||
| let cpSpy; | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest.restoreAllMocks(); | ||
| jest.spyOn(migrationsDir, 'shouldExist').mockResolvedValue(); | ||
| jest.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.js'); | ||
| jest.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(false); | ||
| jest.spyOn(config, 'shouldExist').mockResolvedValue(); | ||
| jest.spyOn(config, 'read').mockResolvedValue({ | ||
| vi.clearAllMocks(); | ||
| vi.restoreAllMocks(); | ||
| vi.spyOn(migrationsDir, 'shouldExist').mockResolvedValue(); | ||
| vi.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.js'); | ||
| vi.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(false); | ||
| vi.spyOn(config, 'shouldExist').mockResolvedValue(); | ||
| vi.spyOn(config, 'read').mockResolvedValue({ | ||
| moduleSystem: 'commonjs', | ||
| }); | ||
| fs.cp.mockResolvedValue(); | ||
| cpSpy = vi.spyOn(fs, 'cp').mockResolvedValue(); | ||
| }); | ||
@@ -39,3 +36,3 @@ | ||
| it("should yield an error when the migrations directory does not exist", async () => { | ||
| jest.spyOn(migrationsDir, 'shouldExist').mockRejectedValue( | ||
| vi.spyOn(migrationsDir, 'shouldExist').mockRejectedValue( | ||
| new Error("migrations directory does not exist") | ||
@@ -52,4 +49,4 @@ ); | ||
| it("should create a new migration file and yield the filename", async () => { | ||
| jest.useFakeTimers(); | ||
| jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| vi.useFakeTimers(); | ||
| vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
@@ -59,6 +56,4 @@ const filename = await create("my_description"); | ||
| expect(fs.cp).toHaveBeenCalled(); | ||
| expect(fs.cp.mock.calls[0][0]).toBe( | ||
| path.join(__dirname, "../../samples/commonjs/migration.js") | ||
| ); | ||
| expect(fs.cp.mock.calls[0][1]).toBe( | ||
| expect(cpSpy).toHaveBeenCalledWith( | ||
| path.join(__dirname, "../../samples/commonjs/migration.js"), | ||
| path.join(process.cwd(), "migrations", "20160609080700-my_description.js") | ||
@@ -68,17 +63,15 @@ ); | ||
| jest.useRealTimers(); | ||
| vi.useRealTimers(); | ||
| }); | ||
| it("should create a new migration file and yield the filename with custom extension", async () => { | ||
| jest.useFakeTimers(); | ||
| jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| vi.useFakeTimers(); | ||
| vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| jest.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.ts'); | ||
| vi.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.ts'); | ||
| const filename = await create("my_description"); | ||
| expect(fs.cp).toHaveBeenCalled(); | ||
| expect(fs.cp.mock.calls[0][0]).toBe( | ||
| path.join(__dirname, "../../samples/commonjs/migration.js") | ||
| ); | ||
| expect(fs.cp.mock.calls[0][1]).toBe( | ||
| expect(cpSpy).toHaveBeenCalledWith( | ||
| path.join(__dirname, "../../samples/commonjs/migration.js"), | ||
| path.join(process.cwd(), "migrations", "20160609080700-my_description.ts") | ||
@@ -88,8 +81,8 @@ ); | ||
| jest.useRealTimers(); | ||
| vi.useRealTimers(); | ||
| }); | ||
| it("should replace spaces in the description with underscores", async () => { | ||
| jest.useFakeTimers(); | ||
| jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| vi.useFakeTimers(); | ||
| vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
@@ -99,6 +92,4 @@ await create("this description contains spaces"); | ||
| expect(fs.cp).toHaveBeenCalled(); | ||
| expect(fs.cp.mock.calls[0][0]).toBe( | ||
| path.join(__dirname, "../../samples/commonjs/migration.js") | ||
| ); | ||
| expect(fs.cp.mock.calls[0][1]).toBe( | ||
| expect(cpSpy).toHaveBeenCalledWith( | ||
| path.join(__dirname, "../../samples/commonjs/migration.js"), | ||
| path.join( | ||
@@ -111,7 +102,7 @@ process.cwd(), | ||
| jest.useRealTimers(); | ||
| vi.useRealTimers(); | ||
| }); | ||
| it("should yield errors that occurred when copying the file", async () => { | ||
| fs.cp.mockRejectedValue(new Error("Copy failed")); | ||
| vi.spyOn(fs, 'cp').mockRejectedValue(new Error("Copy failed")); | ||
| await expect(create("my_description")).rejects.toThrow("Copy failed"); | ||
@@ -121,6 +112,6 @@ }); | ||
| it("should use the sample migration file if it exists", async () => { | ||
| jest.useFakeTimers(); | ||
| jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| vi.useFakeTimers(); | ||
| vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| jest.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(true); | ||
| vi.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(true); | ||
| const filename = await create("my_description"); | ||
@@ -130,6 +121,4 @@ | ||
| expect(fs.cp).toHaveBeenCalled(); | ||
| expect(fs.cp.mock.calls[0][0]).toBe( | ||
| path.join(process.cwd(), "migrations", "sample-migration.js") | ||
| ); | ||
| expect(fs.cp.mock.calls[0][1]).toBe( | ||
| expect(cpSpy).toHaveBeenCalledWith( | ||
| path.join(process.cwd(), "migrations", "sample-migration.js"), | ||
| path.join(process.cwd(), "migrations", "20160609080700-my_description.js") | ||
@@ -139,4 +128,4 @@ ); | ||
| jest.useRealTimers(); | ||
| vi.useRealTimers(); | ||
| }); | ||
| }); |
@@ -1,7 +0,7 @@ | ||
| jest.mock("../../lib/actions/status", () => jest.fn()); | ||
| import migrationsDir from "../../lib/env/migrationsDir.js"; | ||
| import config from "../../lib/env/config.js"; | ||
| import status from "../../lib/actions/status.js"; | ||
| import down from "../../lib/actions/down.js"; | ||
| const migrationsDir = require("../../lib/env/migrationsDir"); | ||
| const config = require("../../lib/env/config"); | ||
| const status = require("../../lib/actions/status"); | ||
| const down = require("../../lib/actions/down"); | ||
| vi.mock("../../lib/actions/status"); | ||
@@ -17,3 +17,3 @@ describe("down", () => { | ||
| const theMigration = { | ||
| down: jest.fn().mockResolvedValue() | ||
| down: vi.fn().mockResolvedValue() | ||
| }; | ||
@@ -25,3 +25,3 @@ return theMigration; | ||
| const mock = { | ||
| collection: jest.fn((name) => { | ||
| collection: vi.fn((name) => { | ||
| if (name === "changelog") return changelogCollection; | ||
@@ -41,3 +41,3 @@ if (name === "changelog_lock") return changelogLockCollection; | ||
| return { | ||
| deleteOne: jest.fn().mockResolvedValue() | ||
| deleteOne: vi.fn().mockResolvedValue() | ||
| }; | ||
@@ -48,10 +48,10 @@ } | ||
| const findStub = { | ||
| toArray: jest.fn().mockResolvedValue([]) | ||
| toArray: vi.fn().mockResolvedValue([]) | ||
| }; | ||
| return { | ||
| insertOne: jest.fn().mockResolvedValue(), | ||
| createIndex: jest.fn().mockResolvedValue(), | ||
| find: jest.fn().mockReturnValue(findStub), | ||
| deleteMany: jest.fn().mockResolvedValue(), | ||
| insertOne: vi.fn().mockResolvedValue(), | ||
| createIndex: vi.fn().mockResolvedValue(), | ||
| find: vi.fn().mockReturnValue(findStub), | ||
| deleteMany: vi.fn().mockResolvedValue(), | ||
| }; | ||
@@ -61,4 +61,4 @@ } | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest.restoreAllMocks(); | ||
| vi.clearAllMocks(); | ||
| vi.restoreAllMocks(); | ||
@@ -88,4 +88,4 @@ migration = mockMigration(); | ||
| jest.spyOn(config, 'shouldExist').mockResolvedValue(); | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'shouldExist').mockResolvedValue(); | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| changelogCollectionName: "changelog", | ||
@@ -96,3 +96,3 @@ lockCollectionName: "changelog_lock", | ||
| jest.spyOn(migrationsDir, 'loadMigration').mockResolvedValue(migration); | ||
| vi.spyOn(migrationsDir, 'loadMigration').mockResolvedValue(migration); | ||
| }); | ||
@@ -115,3 +115,3 @@ | ||
| await down(db); | ||
| expect(migrationsDir.loadMigration.mock.calls[0][0]).toBe( | ||
| expect(migrationsDir.loadMigration).toHaveBeenCalledWith( | ||
| "20160609113225-last_migration.js" | ||
@@ -175,3 +175,3 @@ ); | ||
| it("should ignore lock if feature is disabled", async() => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| changelogCollectionName: "changelog", | ||
@@ -182,3 +182,3 @@ lockCollectionName: "changelog_lock", | ||
| changelogLockCollection.find.mockReturnValue({ | ||
| toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| }); | ||
@@ -199,3 +199,3 @@ | ||
| changelogLockCollection.find.mockReturnValue({ | ||
| toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| }); | ||
@@ -202,0 +202,0 @@ |
@@ -1,24 +0,21 @@ | ||
| jest.mock("fs/promises", () => ({ | ||
| stat: jest.fn(), | ||
| cp: jest.fn(), | ||
| mkdir: jest.fn(), | ||
| readdir: jest.fn(), | ||
| readFile: jest.fn(), | ||
| })); | ||
| import { fileURLToPath } from 'url'; | ||
| const __dirname = fileURLToPath(new URL('.', import.meta.url)); | ||
| const path = require("path"); | ||
| const fs = require("fs/promises"); | ||
| const migrationsDir = require("../../lib/env/migrationsDir"); | ||
| const config = require("../../lib/env/config"); | ||
| const init = require("../../lib/actions/init"); | ||
| import path from "path"; | ||
| import fs from "fs/promises"; | ||
| import migrationsDir from "../../lib/env/migrationsDir.js"; | ||
| import config from "../../lib/env/config.js"; | ||
| import init from "../../lib/actions/init.js"; | ||
| describe("init", () => { | ||
| let cpSpy, mkdirSpy; | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest.restoreAllMocks(); | ||
| vi.clearAllMocks(); | ||
| vi.restoreAllMocks(); | ||
| global.options = { module: 'commonjs' }; | ||
| jest.spyOn(migrationsDir, 'shouldNotExist').mockResolvedValue(); | ||
| jest.spyOn(config, 'shouldNotExist').mockResolvedValue(); | ||
| fs.cp.mockResolvedValue(); | ||
| fs.mkdir.mockResolvedValue(); | ||
| vi.spyOn(migrationsDir, 'shouldNotExist').mockResolvedValue(); | ||
| vi.spyOn(config, 'shouldNotExist').mockResolvedValue(); | ||
| cpSpy = vi.spyOn(fs, 'cp').mockResolvedValue(); | ||
| mkdirSpy = vi.spyOn(fs, 'mkdir').mockResolvedValue(); | ||
| }); | ||
@@ -32,3 +29,3 @@ | ||
| it("should not continue and yield an error if the migrations directory already exists", async () => { | ||
| jest.spyOn(migrationsDir, 'shouldNotExist').mockRejectedValue(new Error("Dir exists")); | ||
| vi.spyOn(migrationsDir, 'shouldNotExist').mockRejectedValue(new Error("Dir exists")); | ||
@@ -50,3 +47,3 @@ try { | ||
| it("should not continue and yield an error if the config file already exists", async () => { | ||
| jest.spyOn(config, 'shouldNotExist').mockResolvedValue(new Error("Config exists")); | ||
| vi.spyOn(config, 'shouldNotExist').mockResolvedValue(new Error("Config exists")); | ||
@@ -64,12 +61,5 @@ try { | ||
| await init(); | ||
| expect(fs.cp).toHaveBeenCalled(); | ||
| expect(fs.cp).toHaveBeenCalledTimes(1); | ||
| const source = fs.cp.mock.calls[0][0]; | ||
| expect(source).toBe( | ||
| path.join(__dirname, "../../samples/commonjs/migrate-mongo-config.js") | ||
| ); | ||
| const destination = fs.cp.mock.calls[0][1]; | ||
| expect(destination).toBe( | ||
| expect(cpSpy).toHaveBeenCalledWith( | ||
| path.join(__dirname, "../../samples/commonjs/migrate-mongo-config.js"), | ||
| path.join(process.cwd(), "migrate-mongo-config.js") | ||
@@ -82,12 +72,5 @@ ); | ||
| await init(); | ||
| expect(fs.cp).toHaveBeenCalled(); | ||
| expect(fs.cp).toHaveBeenCalledTimes(1); | ||
| const source = fs.cp.mock.calls[0][0]; | ||
| expect(source).toBe( | ||
| path.join(__dirname, "../../samples/esm/migrate-mongo-config.js") | ||
| ); | ||
| const destination = fs.cp.mock.calls[0][1]; | ||
| expect(destination).toBe( | ||
| expect(cpSpy).toHaveBeenCalledWith( | ||
| path.join(__dirname, "../../samples/esm/migrate-mongo-config.js"), | ||
| path.join(process.cwd(), "migrate-mongo-config.js") | ||
@@ -98,3 +81,3 @@ ); | ||
| it("should yield errors that occurred when copying the sample config", async () => { | ||
| fs.cp.mockRejectedValue(new Error("No space left on device")); | ||
| vi.spyOn(fs, 'cp').mockRejectedValue(new Error("No space left on device")); | ||
| await expect(init()).rejects.toThrow("No space left on device"); | ||
@@ -106,6 +89,6 @@ }); | ||
| expect(fs.mkdir).toHaveBeenCalled(); | ||
| expect(fs.mkdir).toHaveBeenCalledTimes(1); | ||
| expect(fs.mkdir.mock.calls[0][0]).toEqual( | ||
| path.join(process.cwd(), "migrations") | ||
| expect(mkdirSpy).toHaveBeenCalledWith( | ||
| path.join(process.cwd(), "migrations"), | ||
| { recursive: true } | ||
| ); | ||
@@ -115,3 +98,3 @@ }); | ||
| it("should yield errors that occurred when creating the migrations directory", async () => { | ||
| fs.mkdir.mockRejectedValue(new Error("I cannot do that")); | ||
| vi.spyOn(fs, 'mkdir').mockRejectedValue(new Error("I cannot do that")); | ||
@@ -118,0 +101,0 @@ try { |
@@ -1,12 +0,6 @@ | ||
| jest.mock("fs/promises", () => ({ | ||
| stat: jest.fn(), | ||
| cp: jest.fn(), | ||
| mkdir: jest.fn(), | ||
| readdir: jest.fn(), | ||
| readFile: jest.fn(), | ||
| })); | ||
| vi.mock("fs/promises"); | ||
| const migrationsDir = require("../../lib/env/migrationsDir"); | ||
| const config = require("../../lib/env/config"); | ||
| const status = require("../../lib/actions/status"); | ||
| import migrationsDir from "../../lib/env/migrationsDir.js"; | ||
| import config from "../../lib/env/config.js"; | ||
| import status from "../../lib/actions/status.js"; | ||
@@ -32,3 +26,3 @@ describe("status", () => { | ||
| const mock = {}; | ||
| mock.collection = jest.fn((name) => { | ||
| mock.collection = vi.fn((name) => { | ||
| if (name === "changelog") return changelogCollection; | ||
@@ -42,5 +36,5 @@ return null; | ||
| return { | ||
| deleteOne: jest.fn().mockResolvedValue(), | ||
| find: jest.fn().mockReturnValue({ | ||
| toArray: jest.fn().mockResolvedValue([ | ||
| deleteOne: vi.fn().mockResolvedValue(), | ||
| find: vi.fn().mockReturnValue({ | ||
| toArray: vi.fn().mockResolvedValue([ | ||
| { | ||
@@ -60,3 +54,3 @@ fileName: "20160509113224-first_migration.js", | ||
| function enabledFileHash() { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| changelogCollectionName: "changelog", | ||
@@ -69,3 +63,3 @@ useFileHash: true | ||
| changelogCollection.find.mockReturnValue({ | ||
| toArray: jest.fn().mockResolvedValue([ | ||
| toArray: vi.fn().mockResolvedValue([ | ||
| { | ||
@@ -86,4 +80,4 @@ fileName: "20160509113224-first_migration.js", | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest.restoreAllMocks(); | ||
| vi.clearAllMocks(); | ||
| vi.restoreAllMocks(); | ||
@@ -93,4 +87,4 @@ changelogCollection = mockChangelogCollection(); | ||
| jest.spyOn(migrationsDir, 'shouldExist').mockResolvedValue(); | ||
| jest.spyOn(migrationsDir, 'getFileNames').mockResolvedValue([ | ||
| vi.spyOn(migrationsDir, 'shouldExist').mockResolvedValue(); | ||
| vi.spyOn(migrationsDir, 'getFileNames').mockResolvedValue([ | ||
| "20160509113224-first_migration.js", | ||
@@ -100,6 +94,6 @@ "20160512091701-second_migration.js", | ||
| ]); | ||
| jest.spyOn(migrationsDir, 'loadFileHash').mockImplementation(defaultLoadFileHashImpl); | ||
| vi.spyOn(migrationsDir, 'loadFileHash').mockImplementation(defaultLoadFileHashImpl); | ||
| jest.spyOn(config, 'shouldExist').mockResolvedValue(); | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'shouldExist').mockResolvedValue(); | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| changelogCollectionName: "changelog" | ||
@@ -115,3 +109,3 @@ }); | ||
| it("should yield an error when the migrations directory does not exist", async () => { | ||
| jest.spyOn(migrationsDir, 'shouldExist').mockRejectedValue( | ||
| vi.spyOn(migrationsDir, 'shouldExist').mockRejectedValue( | ||
| new Error("migrations directory does not exist") | ||
@@ -128,3 +122,3 @@ ); | ||
| it("should yield an error when config file does not exist", async () => { | ||
| jest.spyOn(config, 'shouldExist').mockRejectedValue( | ||
| vi.spyOn(config, 'shouldExist').mockRejectedValue( | ||
| new Error("config file does not exist") | ||
@@ -141,3 +135,3 @@ ); | ||
| it("should yield errors that occurred when getting the list of files in the migrations directory", async () => { | ||
| jest.spyOn(migrationsDir, 'getFileNames').mockRejectedValue( | ||
| vi.spyOn(migrationsDir, 'getFileNames').mockRejectedValue( | ||
| new Error("File system unavailable") | ||
@@ -156,3 +150,3 @@ ); | ||
| changelogCollection.find.mockReturnValue({ | ||
| toArray: jest.fn().mockRejectedValue(new Error("Cannot read from the database")) | ||
| toArray: vi.fn().mockRejectedValue(new Error("Cannot read from the database")) | ||
| }); | ||
@@ -237,3 +231,3 @@ await expect(status(db)).rejects.toThrow("Cannot read from the database"); | ||
| addHashToChangeLog(); | ||
| jest.spyOn(migrationsDir, 'loadFileHash').mockImplementation((fileName) => { | ||
| vi.spyOn(migrationsDir, 'loadFileHash').mockImplementation((fileName) => { | ||
| switch (fileName) { | ||
@@ -240,0 +234,0 @@ case "20160509113224-first_migration.js": |
+37
-39
@@ -1,7 +0,7 @@ | ||
| jest.mock("../../lib/actions/status", () => jest.fn()); | ||
| import migrationsDir from "../../lib/env/migrationsDir.js"; | ||
| import config from "../../lib/env/config.js"; | ||
| import status from "../../lib/actions/status.js"; | ||
| import up from "../../lib/actions/up.js"; | ||
| const migrationsDir = require("../../lib/env/migrationsDir"); | ||
| const config = require("../../lib/env/config"); | ||
| const status = require("../../lib/actions/status"); | ||
| const up = require("../../lib/actions/up"); | ||
| vi.mock("../../lib/actions/status"); | ||
@@ -18,3 +18,3 @@ describe("up", () => { | ||
| const migration = { | ||
| up: jest.fn().mockResolvedValue() | ||
| up: vi.fn().mockResolvedValue() | ||
| }; | ||
@@ -26,3 +26,3 @@ return migration; | ||
| const mock = { | ||
| collection: jest.fn((name) => { | ||
| collection: vi.fn((name) => { | ||
| if (name === "changelog") return changelogCollection; | ||
@@ -42,3 +42,3 @@ if (name === "changelog_lock") return changelogLockCollection; | ||
| return { | ||
| insertOne: jest.fn().mockResolvedValue() | ||
| insertOne: vi.fn().mockResolvedValue() | ||
| }; | ||
@@ -49,10 +49,10 @@ } | ||
| const findStub = { | ||
| toArray: jest.fn().mockResolvedValue([]) | ||
| toArray: vi.fn().mockResolvedValue([]) | ||
| }; | ||
| return { | ||
| insertOne: jest.fn().mockResolvedValue(), | ||
| createIndex: jest.fn().mockResolvedValue(), | ||
| find: jest.fn().mockReturnValue(findStub), | ||
| deleteMany: jest.fn().mockResolvedValue(), | ||
| insertOne: vi.fn().mockResolvedValue(), | ||
| createIndex: vi.fn().mockResolvedValue(), | ||
| find: vi.fn().mockReturnValue(findStub), | ||
| deleteMany: vi.fn().mockResolvedValue(), | ||
| }; | ||
@@ -62,4 +62,4 @@ } | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest.restoreAllMocks(); | ||
| vi.clearAllMocks(); | ||
| vi.restoreAllMocks(); | ||
@@ -92,4 +92,4 @@ firstPendingMigration = mockMigration(); | ||
| jest.spyOn(config, 'shouldExist').mockResolvedValue(); | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'shouldExist').mockResolvedValue(); | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| changelogCollectionName: "changelog", | ||
@@ -100,3 +100,3 @@ lockCollectionName: "changelog_lock", | ||
| jest.spyOn(migrationsDir, 'loadMigration') | ||
| vi.spyOn(migrationsDir, 'loadMigration') | ||
| .mockImplementation((fileName) => { | ||
@@ -122,6 +122,8 @@ if (fileName === "20160607173840-first_pending_migration.js") { | ||
| expect(migrationsDir.loadMigration).toHaveBeenCalledTimes(2); | ||
| expect(migrationsDir.loadMigration.mock.calls[0][0]).toBe( | ||
| expect(migrationsDir.loadMigration).toHaveBeenNthCalledWith( | ||
| 1, | ||
| "20160607173840-first_pending_migration.js" | ||
| ); | ||
| expect(migrationsDir.loadMigration.mock.calls[1][0]).toBe( | ||
| expect(migrationsDir.loadMigration).toHaveBeenNthCalledWith( | ||
| 2, | ||
| "20160608060209-second_pending_migration.js" | ||
@@ -135,17 +137,14 @@ ); | ||
| expect(secondPendingMigration.up).toHaveBeenCalled(); | ||
| // Check call order | ||
| const firstCallOrder = firstPendingMigration.up.mock.invocationCallOrder[0]; | ||
| const secondCallOrder = secondPendingMigration.up.mock.invocationCallOrder[0]; | ||
| expect(firstCallOrder).toBeLessThan(secondCallOrder); | ||
| // Verify second was called after first | ||
| expect(secondPendingMigration.up).toHaveBeenCalledAfter(firstPendingMigration.up); | ||
| }); | ||
| it("should populate the changelog with info about the upgraded migrations", async () => { | ||
| jest.useFakeTimers(); | ||
| jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| vi.useFakeTimers(); | ||
| vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| await up(db); | ||
| expect(changelogCollection.insertOne).toHaveBeenCalled(); | ||
| expect(changelogCollection.insertOne).toHaveBeenCalledTimes(2); | ||
| expect(changelogCollection.insertOne.mock.calls[0][0]).toEqual({ | ||
| expect(changelogCollection.insertOne).toHaveBeenNthCalledWith(1, { | ||
| appliedAt: new Date("2016-06-09T08:07:00.077Z"), | ||
@@ -156,7 +155,7 @@ fileName: "20160607173840-first_pending_migration.js", | ||
| jest.useRealTimers(); | ||
| vi.useRealTimers(); | ||
| }); | ||
| it("should populate the changelog with info about the upgraded migrations (using file hash)", async () => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| changelogCollectionName: "changelog", | ||
@@ -168,13 +167,12 @@ lockCollectionName: "changelog_lock", | ||
| changelogLockCollection.find.mockReturnValue({ | ||
| toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| }); | ||
| jest.useFakeTimers(); | ||
| jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| vi.useFakeTimers(); | ||
| vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z")); | ||
| await up(db); | ||
| expect(changelogCollection.insertOne).toHaveBeenCalled(); | ||
| expect(changelogCollection.insertOne).toHaveBeenCalledTimes(2); | ||
| expect(changelogCollection.insertOne.mock.calls[0][0]).toEqual({ | ||
| expect(changelogCollection.insertOne).toHaveBeenNthCalledWith(1, { | ||
| appliedAt: new Date("2016-06-09T08:07:00.077Z"), | ||
@@ -186,3 +184,3 @@ "fileHash": undefined, | ||
| jest.useRealTimers(); | ||
| vi.useRealTimers(); | ||
| }); | ||
@@ -245,3 +243,3 @@ | ||
| it("should ignore lock if feature is disabled", async() => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| changelogCollectionName: "changelog", | ||
@@ -252,3 +250,3 @@ lockCollectionName: "changelog_lock", | ||
| changelogLockCollection.find.mockReturnValue({ | ||
| toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| }); | ||
@@ -271,3 +269,3 @@ | ||
| changelogLockCollection.find.mockReturnValue({ | ||
| toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }]) | ||
| }); | ||
@@ -274,0 +272,0 @@ |
+22
-30
@@ -1,18 +0,10 @@ | ||
| jest.mock("fs/promises", () => ({ | ||
| stat: jest.fn(), | ||
| cp: jest.fn(), | ||
| mkdir: jest.fn(), | ||
| readdir: jest.fn(), | ||
| readFile: jest.fn(), | ||
| })); | ||
| const path = require("path"); | ||
| const fs = require("fs/promises"); | ||
| const moduleLoader = require("../../lib/utils/module-loader"); | ||
| import path from "path"; | ||
| import fs from "fs/promises"; | ||
| import moduleLoader from "../../lib/utils/module-loader.js"; | ||
| // Don't auto-mock config, we'll use the real implementation | ||
| const config = require("../../lib/env/config"); | ||
| import config from "../../lib/env/config.js"; | ||
| describe("config", () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| vi.clearAllMocks(); | ||
| // Reset config state between tests | ||
@@ -22,3 +14,3 @@ delete global.options; | ||
| // Restore any spies | ||
| jest.restoreAllMocks(); | ||
| vi.restoreAllMocks(); | ||
| }); | ||
@@ -29,3 +21,3 @@ | ||
| it('should not yield an error when the config was set manually', async () => { | ||
| fs.stat.mockRejectedValue(new Error("Not found")); | ||
| vi.spyOn(fs, 'stat').mockRejectedValue(new Error("Not found")); | ||
| config.set({ my: 'config'}) | ||
@@ -36,3 +28,3 @@ await config.shouldExist(); | ||
| it("should not yield an error if the config exists", async () => { | ||
| fs.stat.mockResolvedValue({}); | ||
| vi.spyOn(fs, 'stat').mockResolvedValue({}); | ||
| await config.shouldExist(); | ||
@@ -43,3 +35,3 @@ }); | ||
| const configPath = path.join(process.cwd(), "migrate-mongo-config.js"); | ||
| fs.stat.mockRejectedValue(new Error("It does not exist")); | ||
| vi.spyOn(fs, 'stat').mockRejectedValue(new Error("It does not exist")); | ||
| await expect(config.shouldExist()).rejects.toThrow( | ||
@@ -54,3 +46,3 @@ `config file does not exist: ${configPath}` | ||
| it('should not yield an error when the config was set manually', async () => { | ||
| fs.stat.mockRejectedValue(new Error("Not found")); | ||
| vi.spyOn(fs, 'stat').mockRejectedValue(new Error("Not found")); | ||
| config.set({ my: 'config'}) | ||
@@ -63,3 +55,3 @@ await config.shouldNotExist(); | ||
| error.code = "ENOENT"; | ||
| fs.stat.mockRejectedValue(error); | ||
| vi.spyOn(fs, 'stat').mockRejectedValue(error); | ||
| await config.shouldNotExist(); | ||
@@ -70,3 +62,3 @@ }); | ||
| const configPath = path.join(process.cwd(), "migrate-mongo-config.js"); | ||
| fs.stat.mockResolvedValue({}); | ||
| vi.spyOn(fs, 'stat').mockResolvedValue({}); | ||
| await expect(config.shouldNotExist()).rejects.toThrow( | ||
@@ -112,4 +104,4 @@ `config file already exists: ${configPath}` | ||
| error.code = 'ERR_REQUIRE_ESM'; | ||
| jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| jest.spyOn(moduleLoader, 'import').mockResolvedValue({}); | ||
| vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| vi.spyOn(moduleLoader, 'import').mockResolvedValue({}); | ||
| await config.read(); | ||
@@ -122,4 +114,4 @@ expect(moduleLoader.import).toHaveBeenCalled(); | ||
| error.code = 'ERR_REQUIRE_ASYNC_MODULE'; | ||
| jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| jest.spyOn(moduleLoader, 'import').mockResolvedValue({}); | ||
| vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| vi.spyOn(moduleLoader, 'import').mockResolvedValue({}); | ||
| await config.read(); | ||
@@ -137,3 +129,3 @@ expect(moduleLoader.import).toHaveBeenCalled(); | ||
| jest.spyOn(moduleLoader, 'require').mockResolvedValue({ | ||
| vi.spyOn(moduleLoader, 'require').mockResolvedValue({ | ||
| default: expectedConfig | ||
@@ -154,3 +146,3 @@ }); | ||
| jest.spyOn(moduleLoader, 'require').mockResolvedValue(expectedConfig); | ||
| vi.spyOn(moduleLoader, 'require').mockResolvedValue(expectedConfig); | ||
@@ -168,3 +160,3 @@ const actual = await config.read(); | ||
| jest.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig); | ||
| vi.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig); | ||
| global.options = { migrationsDir: customMigrationsDir }; | ||
@@ -185,3 +177,3 @@ | ||
| jest.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig); | ||
| vi.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig); | ||
| delete global.options; | ||
@@ -202,4 +194,4 @@ | ||
| error.code = 'ERR_REQUIRE_ESM'; | ||
| jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| jest.spyOn(moduleLoader, 'import').mockResolvedValue(originalConfig); | ||
| vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| vi.spyOn(moduleLoader, 'import').mockResolvedValue(originalConfig); | ||
| global.options = { migrationsDir: customMigrationsDir }; | ||
@@ -206,0 +198,0 @@ |
@@ -1,17 +0,7 @@ | ||
| jest.mock("mongodb", () => ({ | ||
| MongoClient: { | ||
| connect: jest.fn() | ||
| } | ||
| })); | ||
| jest.mock("fs/promises", () => ({ | ||
| stat: jest.fn(), | ||
| cp: jest.fn(), | ||
| mkdir: jest.fn(), | ||
| readdir: jest.fn(), | ||
| readFile: jest.fn(), | ||
| })); | ||
| vi.mock("mongodb"); | ||
| vi.mock("fs/promises"); | ||
| const config = require("../../lib/env/config"); | ||
| const mongodb = require("mongodb"); | ||
| const database = require("../../lib/env/database"); | ||
| import config from "../../lib/env/config.js"; | ||
| import mongodb from "mongodb"; | ||
| import database from "../../lib/env/database.js"; | ||
@@ -37,3 +27,3 @@ describe("database", () => { | ||
| return { | ||
| db: jest.fn().mockReturnValue({ the: "db" }), | ||
| db: vi.fn().mockReturnValue({ the: "db" }), | ||
| close: "theCloseFnFromMongoClient" | ||
@@ -44,8 +34,8 @@ }; | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest.restoreAllMocks(); | ||
| vi.clearAllMocks(); | ||
| vi.restoreAllMocks(); | ||
| configObj = createConfigObj(); | ||
| client = mockClient(); | ||
| jest.spyOn(config, 'read').mockReturnValue(configObj); | ||
| mongodb.MongoClient.connect.mockResolvedValue(client); | ||
| vi.spyOn(config, 'read').mockReturnValue(configObj); | ||
| vi.spyOn(mongodb.MongoClient, "connect").mockResolvedValue(client); | ||
| }); | ||
@@ -79,3 +69,3 @@ | ||
| it("should yield an error when unable to connect", async () => { | ||
| mongodb.MongoClient.connect.mockRejectedValue(new Error("Unable to connect")); | ||
| vi.spyOn(mongodb.MongoClient, "connect").mockRejectedValue(new Error("Unable to connect")); | ||
| await expect(database.connect()).rejects.toThrow("Unable to connect"); | ||
@@ -82,0 +72,0 @@ }); |
@@ -1,20 +0,12 @@ | ||
| jest.mock("fs/promises", () => ({ | ||
| stat: jest.fn(), | ||
| cp: jest.fn(), | ||
| mkdir: jest.fn(), | ||
| readdir: jest.fn(), | ||
| readFile: jest.fn(), | ||
| })); | ||
| import path from "path"; | ||
| import fs from "fs/promises"; | ||
| import config from "../../lib/env/config.js"; | ||
| import moduleLoader from "../../lib/utils/module-loader.js"; | ||
| import migrationsDir from "../../lib/env/migrationsDir.js"; | ||
| const path = require("path"); | ||
| const fs = require("fs/promises"); | ||
| const config = require("../../lib/env/config"); | ||
| const moduleLoader = require("../../lib/utils/module-loader"); | ||
| const migrationsDir = require("../../lib/env/migrationsDir"); | ||
| describe("migrationsDir", () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest.restoreAllMocks(); | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.clearAllMocks(); | ||
| vi.restoreAllMocks(); | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| migrationsDir: "migrations", | ||
@@ -27,3 +19,3 @@ migrationFileExtension: ".js" | ||
| it("should use the configured relative migrations dir when a config file is available", async () => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| migrationsDir: "custom-migrations-dir" | ||
@@ -37,3 +29,3 @@ }); | ||
| it("should use the configured absolute migrations dir when a config file is available", async () => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| migrationsDir: "/absolute/path/to/my/custom-migrations-dir" | ||
@@ -47,3 +39,3 @@ }); | ||
| it("should use the default migrations directory when no migrationsDir is specified in the config file", async () => { | ||
| jest.spyOn(config, 'read').mockReturnValue({}); | ||
| vi.spyOn(config, 'read').mockReturnValue({}); | ||
| expect(await migrationsDir.resolve()).toBe( | ||
@@ -55,3 +47,3 @@ path.join(process.cwd(), "migrations") | ||
| it("should use the default migrations directory when unable to read the config file", async () => { | ||
| jest.spyOn(config, 'read').mockImplementation(() => { throw new Error("Cannot read config file"); }); | ||
| vi.spyOn(config, 'read').mockImplementation(() => { throw new Error("Cannot read config file"); }); | ||
| expect(await migrationsDir.resolve()).toBe( | ||
@@ -65,3 +57,3 @@ path.join(process.cwd(), "migrations") | ||
| it("should not reject with an error if the migrations dir exists", async () => { | ||
| fs.stat.mockResolvedValue({}); | ||
| vi.spyOn(fs, 'stat').mockResolvedValue({}); | ||
| await migrationsDir.shouldExist(); | ||
@@ -72,3 +64,3 @@ }); | ||
| const migrationsPath = path.join(process.cwd(), "migrations"); | ||
| fs.stat.mockRejectedValue(new Error("It does not exist")); | ||
| vi.spyOn(fs, 'stat').mockRejectedValue(new Error("It does not exist")); | ||
| await expect(migrationsDir.shouldExist()).rejects.toThrow( | ||
@@ -84,3 +76,3 @@ `migrations directory does not exist: ${migrationsPath}` | ||
| error.code = "ENOENT"; | ||
| fs.stat.mockRejectedValue(error); | ||
| vi.spyOn(fs, 'stat').mockRejectedValue(error); | ||
| await migrationsDir.shouldNotExist(); | ||
@@ -91,3 +83,3 @@ }); | ||
| const migrationsPath = path.join(process.cwd(), "migrations"); | ||
| fs.stat.mockResolvedValue({}); | ||
| vi.spyOn(fs, 'stat').mockResolvedValue({}); | ||
| await expect(migrationsDir.shouldNotExist()).rejects.toThrow( | ||
@@ -101,3 +93,3 @@ `migrations directory already exists: ${migrationsPath}` | ||
| it("should read the directory and yield the result", async () => { | ||
| fs.readdir.mockResolvedValue(["file1.js", "file2.js"]); | ||
| vi.spyOn(fs, 'readdir').mockResolvedValue(["file1.js", "file2.js"]); | ||
| const files = await migrationsDir.getFileNames(); | ||
@@ -108,6 +100,6 @@ expect(files).toEqual(["file1.js", "file2.js"]); | ||
| it("should list only files with configured extension", async () => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| migrationFileExtension: ".ts" | ||
| }); | ||
| fs.readdir.mockResolvedValue(["file1.ts", "file2.ts", "file1.js", "file2.js", ".keep"]); | ||
| vi.spyOn(fs, 'readdir').mockResolvedValue(["file1.ts", "file2.ts", "file1.js", "file2.js", ".keep"]); | ||
| const files = await migrationsDir.getFileNames(); | ||
@@ -118,3 +110,3 @@ expect(files).toEqual(["file1.ts", "file2.ts"]); | ||
| it("should yield errors that occurred while reading the dir", async () => { | ||
| fs.readdir.mockRejectedValue(new Error("Could not read")); | ||
| vi.spyOn(fs, 'readdir').mockRejectedValue(new Error("Could not read")); | ||
| await expect(migrationsDir.getFileNames()).rejects.toThrow("Could not read"); | ||
@@ -124,3 +116,3 @@ }); | ||
| it("should be sorted in alphabetical order", async () => { | ||
| fs.readdir.mockResolvedValue([ | ||
| vi.spyOn(fs, 'readdir').mockResolvedValue([ | ||
| "20201014172343-test.js", | ||
@@ -152,4 +144,4 @@ "20201014172356-test3.js", | ||
| it("should use CommonJS default", async () => { | ||
| jest.spyOn(moduleLoader, 'require').mockReturnValue({ up: jest.fn(), down: jest.fn() }); | ||
| jest.spyOn(moduleLoader, 'import'); | ||
| vi.spyOn(moduleLoader, 'require').mockReturnValue({ up: vi.fn(), down: vi.fn() }); | ||
| vi.spyOn(moduleLoader, 'import'); | ||
| await migrationsDir.loadMigration("someFile.js"); | ||
@@ -163,4 +155,4 @@ expect(moduleLoader.require).toHaveBeenCalled(); | ||
| error.code = 'ERR_REQUIRE_ESM'; | ||
| jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| jest.spyOn(moduleLoader, 'import').mockResolvedValue({ default: () => jest.fn() }); | ||
| vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| vi.spyOn(moduleLoader, 'import').mockResolvedValue({ default: () => vi.fn() }); | ||
| await migrationsDir.loadMigration("someFile.js"); | ||
@@ -173,4 +165,4 @@ expect(moduleLoader.import).toHaveBeenCalled(); | ||
| error.code = 'ERR_REQUIRE_ESM'; | ||
| jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| jest.spyOn(moduleLoader, 'import').mockResolvedValue({ up: jest.fn(), down: jest.fn() }); | ||
| vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| vi.spyOn(moduleLoader, 'import').mockResolvedValue({ up: vi.fn(), down: vi.fn() }); | ||
| await migrationsDir.loadMigration("someFile.js"); | ||
@@ -183,4 +175,4 @@ expect(moduleLoader.import).toHaveBeenCalled(); | ||
| error.code = 'ERR_REQUIRE_ASYNC_MODULE'; | ||
| jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| jest.spyOn(moduleLoader, 'import').mockResolvedValue({ up: jest.fn(), down: jest.fn() }); | ||
| vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; }); | ||
| vi.spyOn(moduleLoader, 'import').mockResolvedValue({ up: vi.fn(), down: vi.fn() }); | ||
| await migrationsDir.loadMigration("someFile.js"); | ||
@@ -193,3 +185,3 @@ expect(moduleLoader.import).toHaveBeenCalled(); | ||
| it("should provide the value if specified", async () => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| migrationFileExtension: ".ts" | ||
@@ -201,3 +193,3 @@ }); | ||
| it("should error if the extension does not start with dot", async () => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| migrationFileExtension: "js" | ||
@@ -208,3 +200,3 @@ }); | ||
| it("should use the default if not specified", async() => { | ||
| jest.spyOn(config, 'read').mockReturnValue({ | ||
| vi.spyOn(config, 'read').mockReturnValue({ | ||
| migrationFileExtension: undefined | ||
@@ -216,3 +208,3 @@ }); | ||
| it("should use the default if config file not found", async() => { | ||
| jest.spyOn(config, 'read').mockImplementation(() => { throw new Error(); }); | ||
| vi.spyOn(config, 'read').mockImplementation(() => { throw new Error(); }); | ||
| const ext = await migrationsDir.resolveMigrationFileExtension(); | ||
@@ -225,3 +217,3 @@ expect(ext).toBe(".js"); | ||
| it("should return true if sample migration exists", async () => { | ||
| fs.stat.mockResolvedValue({}); | ||
| vi.spyOn(fs, 'stat').mockResolvedValue({}); | ||
| const result = await migrationsDir.doesSampleMigrationExist(); | ||
@@ -232,3 +224,3 @@ expect(result).toBe(true); | ||
| it("should return false if sample migration doesn't exists", async () => { | ||
| fs.stat.mockRejectedValue(new Error("It does not exist")); | ||
| vi.spyOn(fs, 'stat').mockRejectedValue(new Error("It does not exist")); | ||
| const result = await migrationsDir.doesSampleMigrationExist(); | ||
@@ -241,3 +233,3 @@ expect(result).toBe(false); | ||
| it("should return a hash based on the file contents", async () => { | ||
| fs.readFile.mockResolvedValue("some string to hash"); | ||
| vi.spyOn(fs, 'readFile').mockResolvedValue("some string to hash"); | ||
| const result = await migrationsDir.loadFileHash('somefile.js'); | ||
@@ -244,0 +236,0 @@ expect(result).toBe("ea83a45637a9af470a994d2c9722273ef07d47aec0660a1d10afe6e9586801ac"); |
+159
-6
@@ -1,7 +0,7 @@ | ||
| const { MongoMemoryServer } = require("mongodb-memory-server"); | ||
| const { MongoClient } = require("mongodb"); | ||
| const { execSync } = require("child_process"); | ||
| const fs = require("fs/promises"); | ||
| const path = require("path"); | ||
| const os = require("os"); | ||
| import { MongoMemoryServer } from "mongodb-memory-server"; | ||
| import { MongoClient } from "mongodb"; | ||
| import { execSync } from "child_process"; | ||
| import fs from "fs/promises"; | ||
| import path from "path"; | ||
| import os from "os"; | ||
@@ -1046,2 +1046,155 @@ describe("Integration Tests", () => { | ||
| describe("CommonJS Compatibility", () => { | ||
| beforeEach(async () => { | ||
| await fs.mkdir(migrationsDir, { recursive: true }); | ||
| await writeConfig({ moduleSystem: "commonjs" }); | ||
| }); | ||
| it("should run CommonJS migrations with module.exports", async () => { | ||
| await createMigration( | ||
| "test-commonjs", | ||
| "await db.collection('test').insertOne({ format: 'commonjs' });", | ||
| "await db.collection('test').deleteOne({ format: 'commonjs' });" | ||
| ); | ||
| const output = runMigrateMongo("up"); | ||
| expect(output).toContain("MIGRATED UP"); | ||
| const doc = await db.collection("test").findOne({ format: "commonjs" }); | ||
| expect(doc).toBeTruthy(); | ||
| expect(doc.format).toBe("commonjs"); | ||
| }); | ||
| it("should rollback CommonJS migrations", async () => { | ||
| await createMigration( | ||
| "rollback-cjs", | ||
| "await db.collection('test').insertOne({ rollback: 'cjs' });", | ||
| "await db.collection('test').deleteOne({ rollback: 'cjs' });" | ||
| ); | ||
| runMigrateMongo("up"); | ||
| const upOutput = runMigrateMongo("down"); | ||
| expect(upOutput).toContain("MIGRATED DOWN"); | ||
| const doc = await db.collection("test").findOne({ rollback: "cjs" }); | ||
| expect(doc).toBeNull(); | ||
| }); | ||
| it("should handle multiple CommonJS migrations", async () => { | ||
| await createMigration( | ||
| "cjs-multi-1", | ||
| "await db.collection('test').insertOne({ seq: 1 });", | ||
| "await db.collection('test').deleteOne({ seq: 1 });" | ||
| ); | ||
| await new Promise(resolve => setTimeout(resolve, 1000)); | ||
| await createMigration( | ||
| "cjs-multi-2", | ||
| "await db.collection('test').insertOne({ seq: 2 });", | ||
| "await db.collection('test').deleteOne({ seq: 2 });" | ||
| ); | ||
| const output = runMigrateMongo("up"); | ||
| expect(output).toContain("MIGRATED UP"); | ||
| const count = await db.collection("test").countDocuments({ seq: { $exists: true } }); | ||
| expect(count).toBe(2); | ||
| }); | ||
| it("should work with CommonJS requiring external modules", async () => { | ||
| const migrationFile = await createMigration("placeholder", "", ""); | ||
| const migrationPath = path.join(migrationsDir, migrationFile); | ||
| const content = ` | ||
| const crypto = require('crypto'); | ||
| module.exports = { | ||
| async up(db, client) { | ||
| const hash = crypto.createHash('md5').update('test').digest('hex'); | ||
| await db.collection('test').insertOne({ hash }); | ||
| }, | ||
| async down(db, client) { | ||
| await db.collection('test').deleteMany({}); | ||
| } | ||
| };`; | ||
| await fs.writeFile(migrationPath, content); | ||
| const output = runMigrateMongo("up"); | ||
| expect(output).toContain("MIGRATED UP"); | ||
| const doc = await db.collection("test").findOne({ hash: { $exists: true } }); | ||
| expect(doc).toBeTruthy(); | ||
| expect(doc.hash).toBe("098f6bcd4621d373cade4e832627b4f6"); | ||
| }); | ||
| it("should support CommonJS with complex operations", async () => { | ||
| const migrationFile = await createMigration("placeholder", "", ""); | ||
| const migrationPath = path.join(migrationsDir, migrationFile); | ||
| const content = ` | ||
| module.exports = { | ||
| async up(db, client) { | ||
| // Use client operations directly | ||
| const collections = await db.listCollections().toArray(); | ||
| await db.collection('test').insertOne({ | ||
| type: 'complex-cjs', | ||
| collectionCount: collections.length | ||
| }); | ||
| }, | ||
| async down(db, client) { | ||
| await db.collection('test').deleteMany({ type: 'complex-cjs' }); | ||
| } | ||
| };`; | ||
| await fs.writeFile(migrationPath, content); | ||
| const output = runMigrateMongo("up"); | ||
| expect(output).toContain("MIGRATED UP"); | ||
| const doc = await db.collection("test").findOne({ type: "complex-cjs" }); | ||
| expect(doc).toBeTruthy(); | ||
| expect(typeof doc.collectionCount).toBe('number'); | ||
| }); | ||
| it("should handle CommonJS migrations with synchronous module.exports", async () => { | ||
| const migrationFile = await createMigration("placeholder", "", ""); | ||
| const migrationPath = path.join(migrationsDir, migrationFile); | ||
| const content = ` | ||
| module.exports = { | ||
| up: async function(db, client) { | ||
| await db.collection('test').insertOne({ style: 'function-keyword' }); | ||
| }, | ||
| down: async function(db, client) { | ||
| await db.collection('test').deleteOne({ style: 'function-keyword' }); | ||
| } | ||
| };`; | ||
| await fs.writeFile(migrationPath, content); | ||
| const output = runMigrateMongo("up"); | ||
| expect(output).toContain("MIGRATED UP"); | ||
| const doc = await db.collection("test").findOne({ style: "function-keyword" }); | ||
| expect(doc).toBeTruthy(); | ||
| }); | ||
| it("should validate CommonJS migration structure", async () => { | ||
| const migrationFile = await createMigration("placeholder", "", ""); | ||
| const migrationPath = path.join(migrationsDir, migrationFile); | ||
| // Missing 'up' function | ||
| const invalidContent = ` | ||
| module.exports = { | ||
| down: async function(db, client) { | ||
| await db.collection('test').deleteMany({}); | ||
| } | ||
| };`; | ||
| await fs.writeFile(migrationPath, invalidContent); | ||
| const result = runMigrateMongo("up", testDir, true); | ||
| expect(result.error).toBe(true); | ||
| }); | ||
| }); | ||
| describe("Large-Scale Performance", () => { | ||
@@ -1048,0 +1201,0 @@ beforeEach(async () => { |
| module.exports = { | ||
| testEnvironment: 'node', | ||
| coverageDirectory: 'coverage', | ||
| collectCoverageFrom: [ | ||
| 'lib/**/*.js', | ||
| '!lib/**/*.test.js', | ||
| '!lib/migrate-mongo.js', // Main export file - no logic to test | ||
| ], | ||
| testMatch: [ | ||
| '**/test/**/*.test.js', | ||
| ], | ||
| testPathIgnorePatterns: [ | ||
| '/node_modules/', | ||
| '/test/integration.test.js', // Exclude integration test from unit tests | ||
| ], | ||
| coverageReporters: ['text', 'html', 'lcov'], | ||
| clearMocks: true, | ||
| restoreMocks: true, | ||
| resetMocks: true, | ||
| }; |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
173970
4.45%47
17.5%3087
5%Yes
NaN6
20%18
5.88%