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',
+ },
+ })
+})