diff --git a/package-lock.json b/package-lock.json index 4400c1f..b72d9f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { + "@types/rfdc": "^1.2.0", "@types/semver": "^7.5.8", + "rfdc": "^1.3.1", "semver": "^7.6.0" }, "devDependencies": { @@ -169,6 +171,15 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/rfdc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/rfdc/-/rfdc-1.2.0.tgz", + "integrity": "sha512-9Q81yCQwj4iuqDWVEcZMaY3QJ+w+Vj3M71ZdobgFC6T+RowLlE5CaLwRrwMiFvE3LZ7Qy1SDrR+EjGtVu9FmJQ==", + "deprecated": "This is a stub types definition. rfdc provides its own type definitions, so you do not need this installed.", + "dependencies": { + "rfdc": "*" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -882,6 +893,11 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", diff --git a/package.json b/package.json index 5f42f37..2ef085e 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "borp": "^0.10.0", "desm": "^1.3.1", "prettier": "^3.2.5", + "@types/rfdc": "^1.2.0", + "@types/semver": "^7.5.8", "typescript": "^5.3.3" }, "prettier": { @@ -57,7 +59,7 @@ "main": "./dist/semgrator.js", "types": "./dist/semgrator.d.ts", "dependencies": { - "@types/semver": "^7.5.8", + "rfdc": "^1.3.1", "semver": "^7.6.0" } } diff --git a/src/lib/semgrator.ts b/src/lib/semgrator.ts index 85383e9..c4b6ae3 100644 --- a/src/lib/semgrator.ts +++ b/src/lib/semgrator.ts @@ -2,6 +2,9 @@ import semver from 'semver' import { join } from 'node:path' import { pathToFileURL } from 'node:url' import { readdir } from 'node:fs/promises' +import rfdc from 'rfdc' + +const clone = rfdc() export type Migration = { version: string @@ -87,7 +90,7 @@ async function* processMigrations( for await (const migration of migrations) { if (semver.gt(migration.version, lastVersion)) { // @ts-expect-error - result = await migration.up(result) + result = await migration.up(clone(result)) lastVersion = migration.toVersion || migration.version changed = true } diff --git a/src/test/semgrator.test.ts b/src/test/semgrator.test.ts index 11a1ee5..5c300ef 100644 --- a/src/test/semgrator.test.ts +++ b/src/test/semgrator.test.ts @@ -2,13 +2,17 @@ import { test } from 'node:test' import { semgrator, Migration } from '../lib/semgrator.js' import { tspl } from '@matteo.collina/tspl' import { throws } from 'node:assert/strict' +import { equal, deepEqual } from 'node:assert/strict' type SeenBy = { foo: string seenBy?: string + deep?: { + seenBy?: string + } } -test('apply all migrations', async t => { +test('apply all migrations', async t => { const plan = tspl(t, { plan: 4 }) const m1: Migration = { version: '2.0.0', @@ -207,3 +211,44 @@ test('throws if version is missing', async t => { }, ) }) + +test('clones at each step', async t => { + const m1: Migration = { + version: '2.0.0', + async up(input) { + input.seenBy = '2.0.0' + input.deep ||= {} + input.deep.seenBy = '2.0.0' + return input + }, + } + + const input = { + foo: 'bar', + deep: { + seenBy: '1.0.0', + }, + } + + const res = await semgrator({ + version: '1.0.0', + migrations: [m1], + input, + }) + + equal(res.version, '2.0.0') + equal(res.changed, true) + deepEqual(res.result, { + foo: 'bar', + seenBy: '2.0.0', + deep: { + seenBy: '2.0.0', + }, + }) + deepEqual(input, { + foo: 'bar', + deep: { + seenBy: '1.0.0', + }, + }) +})