Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions rewrite-javascript/rewrite/src/javascript/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import * as fsp from "fs/promises";
import * as path from "path";
import * as os from "os";
import {spawnSync} from "child_process";
import {isDeepStrictEqual} from "util";
import * as YAML from "yaml";

/**
Expand Down Expand Up @@ -572,6 +573,16 @@ export async function updateNodeResolutionMarker<T extends BaseProjectUpdateInfo
existingMarker.npmrcConfigs
);

// If the new marker is structurally identical to the existing one (ignoring the
// auto-generated id), return doc unchanged to avoid minting a fresh AST identity
// for a no-op marker update. This prevents the empty-diff guard from firing on
// recipes that don't modify package.json (e.g. lock-file-only fix strategies).
const {id: _existingId, ...existingRest} = existingMarker;
const {id: _newId, ...newRest} = newMarker;
if (isDeepStrictEqual(existingRest, newRest)) {
return doc;
}

// Replace the marker in the document
return {
...doc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,23 @@
*/
import {withDir} from "tmp-promise";
import * as fsp from "fs/promises";
import * as fs from "fs";
import * as path from "path";
import {
BaseProjectUpdateInfo,
createDependencyRecipeAccumulator,
detectPackageManager,
getAllLockFileNames,
getLockFileDetectionConfig,
getLockFileFormat,
getLockFileName,
isYarnBerryLockFile,
PackageJsonParser,
PackageManager,
runWorkspaceInstallInTempDir
runWorkspaceInstallInTempDir,
updateNodeResolutionMarker
} from "../../src/javascript";
import {Json} from "../../src/json";

describe("detectPackageManager", () => {

Expand Down Expand Up @@ -325,3 +331,78 @@ describe("runWorkspaceInstallInTempDir", () => {
expect(result.lockFileContent).toContain("is-number");
}, 120000);
});

describe("updateNodeResolutionMarker", () => {

test("returns same doc reference when accumulator has no updates", async () => {
// given a parsed package.json with a NodeResolutionResult marker
await withDir(async (dir) => {
const packageJsonContent = JSON.stringify({
name: "test-project",
version: "1.0.0",
dependencies: {
"react": "^18.2.0"
}
}, null, 2);
fs.writeFileSync(path.join(dir.path, "package.json"), packageJsonContent);

const parser = new PackageJsonParser({relativeTo: dir.path});
const docs: Json.Document[] = [];
for await (const result of parser.parse(path.join(dir.path, "package.json"))) {
docs.push(result as Json.Document);
}
const doc = docs[0];

// when calling updateNodeResolutionMarker with an accumulator that has
// no entries for this project (no package.json or lock file changes)
const acc = createDependencyRecipeAccumulator<BaseProjectUpdateInfo>();
const updateInfo = {
packageJsonPath: "package.json",
packageManager: PackageManager.Npm,
originalPackageJson: packageJsonContent
};

const result = await updateNodeResolutionMarker(doc, updateInfo, acc);

// then the same doc reference is returned (no-op marker update)
expect(result).toBe(doc);
}, {unsafeCleanup: true});
});

test("returns same doc reference when updated content is structurally identical", async () => {
// given a parsed package.json with a NodeResolutionResult marker
await withDir(async (dir) => {
const packageJsonContent = JSON.stringify({
name: "test-project",
version: "1.0.0",
dependencies: {
"react": "^18.2.0"
}
}, null, 2);
fs.writeFileSync(path.join(dir.path, "package.json"), packageJsonContent);

const parser = new PackageJsonParser({relativeTo: dir.path});
const docs: Json.Document[] = [];
for await (const result of parser.parse(path.join(dir.path, "package.json"))) {
docs.push(result as Json.Document);
}
const doc = docs[0];

// when calling updateNodeResolutionMarker with an accumulator whose
// updatedPackageJsons contains content identical to the original
const acc = createDependencyRecipeAccumulator<BaseProjectUpdateInfo>();
acc.updatedPackageJsons.set("package.json", packageJsonContent);
const updateInfo = {
packageJsonPath: "package.json",
packageManager: PackageManager.Npm,
originalPackageJson: packageJsonContent
};

const result = await updateNodeResolutionMarker(doc, updateInfo, acc);

// then the same doc reference is returned (structurally equal marker)
expect(result).toBe(doc);
}, {unsafeCleanup: true});
});

});