@pnpm/lockfile.fs 1100.0.7 → 1100.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -3,7 +3,8 @@ export { existsNonEmptyWantedLockfile } from './existsWantedLockfile.js';
3
3
  export { getLockfileImporterId } from './getLockfileImporterId.js';
4
4
  export { cleanGitBranchLockfiles } from './gitBranchLockfile.js';
5
5
  export { convertToLockfileFile, convertToLockfileObject } from './lockfileFormatConverters.js';
6
+ export { getWantedLockfileName } from './lockfileName.js';
6
7
  export * from './read.js';
7
- export { isEmptyLockfile, writeCurrentLockfile, writeLockfileFile, writeLockfiles, writeWantedLockfile, } from './write.js';
8
+ export { isEmptyLockfile, writeCurrentLockfile, writeLockfileFile, writeLockfiles, type WriteLockfilesResult, writeWantedLockfile, } from './write.js';
8
9
  export { extractMainDocument } from './yamlDocuments.js';
9
10
  export * from '@pnpm/lockfile.types';
package/lib/index.js CHANGED
@@ -3,6 +3,7 @@ export { existsNonEmptyWantedLockfile } from './existsWantedLockfile.js';
3
3
  export { getLockfileImporterId } from './getLockfileImporterId.js';
4
4
  export { cleanGitBranchLockfiles } from './gitBranchLockfile.js';
5
5
  export { convertToLockfileFile, convertToLockfileObject } from './lockfileFormatConverters.js';
6
+ export { getWantedLockfileName } from './lockfileName.js';
6
7
  export * from './read.js';
7
8
  export { isEmptyLockfile, writeCurrentLockfile, writeLockfileFile, writeLockfiles, writeWantedLockfile, } from './write.js';
8
9
  export { extractMainDocument } from './yamlDocuments.js';
package/lib/read.d.ts CHANGED
@@ -19,6 +19,7 @@ export declare function readWantedLockfile(pkgPath: string, opts: {
19
19
  useGitBranchLockfile?: boolean;
20
20
  mergeGitBranchLockfiles?: boolean;
21
21
  }): Promise<LockfileObject | null>;
22
+ export declare function wantedLockfileHasMergeConflictsSync(pkgPath: string): boolean;
22
23
  export declare function createLockfileObject(importerIds: ProjectId[], opts: {
23
24
  lockfileVersion: string;
24
25
  autoInstallPeers: boolean;
package/lib/read.js CHANGED
@@ -1,4 +1,4 @@
1
- import { promises as fs } from 'node:fs';
1
+ import fs, { promises as fsp } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import util from 'node:util';
4
4
  import { LOCKFILE_VERSION, WANTED_LOCKFILE, } from '@pnpm/constants';
@@ -28,11 +28,23 @@ export async function readWantedLockfileAndAutofixConflicts(pkgPath, opts) {
28
28
  export async function readWantedLockfile(pkgPath, opts) {
29
29
  return (await _readWantedLockfile(pkgPath, opts)).lockfile;
30
30
  }
31
+ export function wantedLockfileHasMergeConflictsSync(pkgPath) {
32
+ try {
33
+ const lockfileRawContent = stripBom(fs.readFileSync(path.join(pkgPath, WANTED_LOCKFILE), 'utf8'));
34
+ return isDiff(extractMainDocument(lockfileRawContent));
35
+ }
36
+ catch (err) {
37
+ if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') {
38
+ return false;
39
+ }
40
+ throw err;
41
+ }
42
+ }
31
43
  async function _read(lockfilePath, prefix, // only for logging
32
44
  opts) {
33
45
  let lockfileRawContent;
34
46
  try {
35
- lockfileRawContent = stripBom(await fs.readFile(lockfilePath, 'utf8'));
47
+ lockfileRawContent = stripBom(await fsp.readFile(lockfilePath, 'utf8'));
36
48
  }
37
49
  catch (err) {
38
50
  if (!(util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT')) {
package/lib/write.d.ts CHANGED
@@ -1,12 +1,32 @@
1
1
  import type { LockfileFile, LockfileObject } from '@pnpm/lockfile.types';
2
2
  export declare function lockfileYamlDump(obj: object): string;
3
+ /**
4
+ * Returns the canonical post-write lockfile — structurally identical
5
+ * to what `readWantedLockfile` would parse back. Lets callers like
6
+ * the verification cache hash the as-saved form without re-reading.
7
+ */
3
8
  export declare function writeWantedLockfile(pkgPath: string, wantedLockfile: LockfileObject, opts?: {
4
9
  useGitBranchLockfile?: boolean;
5
10
  mergeGitBranchLockfiles?: boolean;
6
- }): Promise<void>;
7
- export declare function writeCurrentLockfile(virtualStoreDir: string, currentLockfile: LockfileObject): Promise<void>;
11
+ /** Pre-resolved filename; skips the `getWantedLockfileName` (and
12
+ * its `getCurrentBranch`) call when supplied. */
13
+ lockfileName?: string;
14
+ }): Promise<LockfileObject>;
15
+ export declare function writeCurrentLockfile(virtualStoreDir: string, currentLockfile: LockfileObject): Promise<LockfileObject | undefined>;
8
16
  export declare function writeLockfileFile(lockfilePath: string, wantedLockfile: LockfileFile): Promise<void>;
9
17
  export declare function isEmptyLockfile(lockfile: LockfileObject): boolean;
18
+ export interface WriteLockfilesResult {
19
+ /**
20
+ * The canonical "as-saved" wanted lockfile — the inverse converter
21
+ * applied to the same object that was serialized to YAML. Hashing
22
+ * this is equivalent to hashing the lockfile the next install will
23
+ * load from disk (modulo undefined values that YAML drops, which any
24
+ * sensible canonicalization-then-hash routine should strip).
25
+ */
26
+ wantedLockfile: LockfileObject;
27
+ /** Same as above for the current lockfile, or undefined when it was skipped because empty. */
28
+ currentLockfile: LockfileObject | undefined;
29
+ }
10
30
  export declare function writeLockfiles(opts: {
11
31
  wantedLockfile: LockfileObject;
12
32
  wantedLockfileDir: string;
@@ -14,4 +34,6 @@ export declare function writeLockfiles(opts: {
14
34
  currentLockfileDir: string;
15
35
  useGitBranchLockfile?: boolean;
16
36
  mergeGitBranchLockfiles?: boolean;
17
- }): Promise<void>;
37
+ /** See {@link writeWantedLockfile}'s `lockfileName` option. */
38
+ wantedLockfileName?: string;
39
+ }): Promise<WriteLockfilesResult>;
package/lib/write.js CHANGED
@@ -5,7 +5,7 @@ import { rimraf } from '@zkochan/rimraf';
5
5
  import yaml from 'js-yaml';
6
6
  import { isEmpty } from 'ramda';
7
7
  import writeFileAtomic from 'write-file-atomic';
8
- import { convertToLockfileFile } from './lockfileFormatConverters.js';
8
+ import { convertToLockfileFile, convertToLockfileObject } from './lockfileFormatConverters.js';
9
9
  import { getWantedLockfileName } from './lockfileName.js';
10
10
  import { lockfileLogger as logger } from './logger.js';
11
11
  import { sortLockfileKeys } from './sortLockfileKeys.js';
@@ -20,15 +20,20 @@ const LOCKFILE_YAML_FORMAT = {
20
20
  export function lockfileYamlDump(obj) {
21
21
  return yaml.dump(obj, LOCKFILE_YAML_FORMAT);
22
22
  }
23
+ /**
24
+ * Returns the canonical post-write lockfile — structurally identical
25
+ * to what `readWantedLockfile` would parse back. Lets callers like
26
+ * the verification cache hash the as-saved form without re-reading.
27
+ */
23
28
  export async function writeWantedLockfile(pkgPath, wantedLockfile, opts) {
24
- const wantedLockfileName = await getWantedLockfileName(opts);
29
+ const wantedLockfileName = opts?.lockfileName ?? await getWantedLockfileName(opts);
25
30
  return writeLockfile(wantedLockfileName, pkgPath, wantedLockfile);
26
31
  }
27
32
  export async function writeCurrentLockfile(virtualStoreDir, currentLockfile) {
28
33
  // empty lockfile is not saved
29
34
  if (isEmptyLockfile(currentLockfile)) {
30
35
  await rimraf(path.join(virtualStoreDir, 'lock.yaml'));
31
- return;
36
+ return undefined;
32
37
  }
33
38
  await fs.mkdir(virtualStoreDir, { recursive: true });
34
39
  return writeLockfile('lock.yaml', virtualStoreDir, currentLockfile);
@@ -45,9 +50,29 @@ async function writeLockfile(lockfileFilename, pkgPath, wantedLockfile) {
45
50
  // in the OS page cache and streaming stops at the first separator.
46
51
  const envDoc = await streamReadFirstYamlDocument(lockfilePath);
47
52
  const envPrefix = envDoc != null ? `${YAML_DOCUMENT_START}${envDoc}${YAML_DOCUMENT_SEPARATOR}` : '';
48
- return writeFileAtomic(lockfilePath, `${envPrefix}${yamlDoc}`);
53
+ await writeFileAtomic(lockfilePath, `${envPrefix}${yamlDoc}`);
49
54
  }
50
- return writeFileAtomic(lockfilePath, yamlDoc);
55
+ else {
56
+ await writeFileAtomic(lockfilePath, yamlDoc);
57
+ }
58
+ // YAML drops undefined on serialize, so the in-memory LockfileFile
59
+ // can carry fields (like an unset settings.dedupePeers) that won't
60
+ // survive a round-trip; strip them to mirror what the next reader
61
+ // will parse back.
62
+ return convertToLockfileObject(stripUndefinedDeep(lockfileToStringify));
63
+ }
64
+ function stripUndefinedDeep(value) {
65
+ if (value === null || typeof value !== 'object')
66
+ return value;
67
+ if (Array.isArray(value))
68
+ return value.map(stripUndefinedDeep);
69
+ const out = {};
70
+ for (const [k, v] of Object.entries(value)) {
71
+ if (v === undefined)
72
+ continue;
73
+ out[k] = stripUndefinedDeep(v);
74
+ }
75
+ return out;
51
76
  }
52
77
  export function writeLockfileFile(lockfilePath, wantedLockfile) {
53
78
  const yamlDoc = yamlStringify(wantedLockfile);
@@ -61,7 +86,7 @@ export function isEmptyLockfile(lockfile) {
61
86
  return Object.values(lockfile.importers).every((importer) => isEmpty(importer.specifiers ?? {}) && isEmpty(importer.dependencies ?? {}));
62
87
  }
63
88
  export async function writeLockfiles(opts) {
64
- const wantedLockfileName = await getWantedLockfileName(opts);
89
+ const wantedLockfileName = opts.wantedLockfileName ?? await getWantedLockfileName(opts);
65
90
  const wantedLockfilePath = path.join(opts.wantedLockfileDir, wantedLockfileName);
66
91
  const currentLockfilePath = path.join(opts.currentLockfileDir, 'lock.yaml');
67
92
  const wantedLockfileToStringify = convertToLockfileFile(opts.wantedLockfile);
@@ -92,7 +117,12 @@ export async function writeLockfiles(opts) {
92
117
  }
93
118
  })(),
94
119
  ]);
95
- return;
120
+ // Both files share the same source object; strip once and reuse.
121
+ const normalized = convertToLockfileObject(stripUndefinedDeep(wantedLockfileToStringify));
122
+ return {
123
+ wantedLockfile: normalized,
124
+ currentLockfile: isEmptyLockfile(opts.wantedLockfile) ? undefined : normalized,
125
+ };
96
126
  }
97
127
  logger.debug({
98
128
  message: `\`${WANTED_LOCKFILE}\` differs from \`${path.relative(opts.wantedLockfileDir, currentLockfilePath)}\``,
@@ -100,10 +130,13 @@ export async function writeLockfiles(opts) {
100
130
  });
101
131
  const currentLockfileToStringify = convertToLockfileFile(opts.currentLockfile);
102
132
  const currentYamlDoc = yamlStringify(currentLockfileToStringify);
133
+ // Filtered-current callers (deps-restorer) can pass an empty
134
+ // current against a non-empty wanted; key off the current.
135
+ const currentIsEmpty = isEmptyLockfile(opts.currentLockfile);
103
136
  await Promise.all([
104
137
  writeFileAtomic(wantedLockfilePath, wantedYamlDoc),
105
138
  (async () => {
106
- if (isEmptyLockfile(opts.wantedLockfile)) {
139
+ if (currentIsEmpty) {
107
140
  await rimraf(currentLockfilePath);
108
141
  }
109
142
  else {
@@ -112,5 +145,11 @@ export async function writeLockfiles(opts) {
112
145
  }
113
146
  })(),
114
147
  ]);
148
+ return {
149
+ wantedLockfile: convertToLockfileObject(stripUndefinedDeep(wantedLockfileToStringify)),
150
+ currentLockfile: currentIsEmpty
151
+ ? undefined
152
+ : convertToLockfileObject(stripUndefinedDeep(currentLockfileToStringify)),
153
+ };
115
154
  }
116
155
  //# sourceMappingURL=write.js.map
@@ -24,6 +24,8 @@ export async function streamReadFirstYamlDocument(filePath) {
24
24
  else {
25
25
  buffer += chunk.value;
26
26
  }
27
+ // Normalize CRLF (Windows) to LF so document separator detection works.
28
+ buffer = buffer.replace(/\r\n/g, '\n');
27
29
  if (buffer.length >= YAML_DOCUMENT_START.length)
28
30
  break;
29
31
  }
@@ -41,7 +43,8 @@ export async function streamReadFirstYamlDocument(filePath) {
41
43
  const chunk = await chunks.next(); // eslint-disable-line no-await-in-loop
42
44
  if (chunk.done)
43
45
  break;
44
- buffer += chunk.value;
46
+ // Normalize CRLF (Windows) to LF so the separator search matches on Windows-checked-out files.
47
+ buffer = (buffer + chunk.value).replace(/\r\n/g, '\n');
45
48
  }
46
49
  }
47
50
  catch (err) {
@@ -60,6 +63,7 @@ export async function streamReadFirstYamlDocument(filePath) {
60
63
  * Otherwise returns the entire content (no env document present).
61
64
  */
62
65
  export function extractMainDocument(content) {
66
+ content = content.replace(/\r\n/g, '\n');
63
67
  if (!content.startsWith(YAML_DOCUMENT_START))
64
68
  return content;
65
69
  const sep = content.indexOf(YAML_DOCUMENT_SEPARATOR, YAML_DOCUMENT_START.length);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/lockfile.fs",
3
- "version": "1100.0.7",
3
+ "version": "1100.1.0",
4
4
  "description": "Read/write pnpm-lock.yaml files",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -35,14 +35,14 @@
35
35
  "strip-bom": "^5.0.0",
36
36
  "write-file-atomic": "^7.0.0",
37
37
  "@pnpm/deps.path": "1100.0.3",
38
- "@pnpm/error": "1100.0.0",
39
- "@pnpm/lockfile.merger": "1100.0.5",
40
- "@pnpm/lockfile.utils": "1100.0.7",
38
+ "@pnpm/constants": "1100.0.0",
39
+ "@pnpm/lockfile.merger": "1100.0.6",
41
40
  "@pnpm/object.key-sorting": "1100.0.0",
42
- "@pnpm/lockfile.types": "1100.0.5",
43
- "@pnpm/types": "1101.1.0",
44
41
  "@pnpm/network.git-utils": "1100.0.1",
45
- "@pnpm/constants": "1100.0.0"
42
+ "@pnpm/lockfile.utils": "1100.0.8",
43
+ "@pnpm/types": "1101.1.0",
44
+ "@pnpm/lockfile.types": "1100.0.6",
45
+ "@pnpm/error": "1100.0.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@pnpm/logger": ">=1001.0.0 <1002.0.0"
@@ -57,8 +57,8 @@
57
57
  "tempy": "3.0.0",
58
58
  "write-yaml-file": "^6.0.0",
59
59
  "yaml-tag": "1.1.0",
60
- "@pnpm/lockfile.fs": "1100.0.7",
61
- "@pnpm/logger": "1100.0.0"
60
+ "@pnpm/logger": "1100.0.0",
61
+ "@pnpm/lockfile.fs": "1100.1.0"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=22.13"