@pnpm/workspace.project-manifest-reader 1100.0.29 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @pnpm/read-project-manifest
2
2
 
3
+ ## 1100.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Added `findWorkspaceDirSync`, `findPackagesSync`, `findWorkspaceProjectsSync`, `findWorkspaceProjectsNoCheckSync`, `readWorkspaceManifestSync`, and `readExactProjectManifestSync`.
8
+
9
+ ### Patch Changes
10
+
11
+ - `pnpm add` and `pnpm install` keep an empty `peerDependencies`, `dependencies`, `devDependencies`, or `optionalDependencies` field that was already in `package.json`. pnpm still drops such a field when it removes the last entry itself, as `pnpm remove` does [#5096](https://github.com/pnpm/pnpm/issues/5096).
12
+
13
+ - Dependencies and executable binaries are now correctly linked and accessible for workspace packages using `publishConfig.directory` and `publishConfig.linkDirectory` [pnpm/pnpm#8338](https://github.com/pnpm/pnpm/issues/8338).
14
+
15
+ - Preserve CRLF line endings when modifying project manifests.
16
+
17
+ - Updated dependencies:
18
+ - @pnpm/error@1100.2.0
19
+ - @pnpm/fs.graceful-fs@1100.2.3
20
+ - @pnpm/pkg-manifest.utils@1100.4.6
21
+ - @pnpm/types@1102.1.1
22
+ - @pnpm/workspace.project-manifest-writer@1100.0.18
23
+
3
24
  ## 1100.0.29
4
25
 
5
26
  ### Patch Changes
package/lib/index.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import type { ProjectManifest } from '@pnpm/types';
2
2
  export type WriteProjectManifest = (manifest: ProjectManifest, force?: boolean) => Promise<void>;
3
3
  export declare function safeReadProjectManifestOnly(projectDir: string): Promise<ProjectManifest | null>;
4
+ export declare function safeReadPublishManifest(projectDir: string): Promise<ProjectManifest | null>;
5
+ /**
6
+ * Finds the manifest of a project whose `publishConfig.directory` is `publishDir`,
7
+ * searching the ancestors of `publishDir`.
8
+ */
9
+ export declare function safeReadParentPublishManifest(publishDir: string): Promise<ProjectManifest | null>;
4
10
  export declare function readProjectManifest(projectDir: string): Promise<{
5
11
  fileName: string;
6
12
  manifest: ProjectManifest;
@@ -17,4 +23,5 @@ interface ReadExactProjectManifestResult {
17
23
  writeProjectManifest: WriteProjectManifest;
18
24
  }
19
25
  export declare function readExactProjectManifest(manifestPath: string): Promise<ReadExactProjectManifestResult>;
26
+ export declare function readExactProjectManifestSync(manifestPath: string): ReadExactProjectManifestResult;
20
27
  export {};
package/lib/index.js CHANGED
@@ -8,8 +8,8 @@ import detectIndent from 'detect-indent';
8
8
  import equal from 'fast-deep-equal';
9
9
  import isWindows from 'is-windows';
10
10
  import pLimit from 'p-limit';
11
- import { readYamlFile } from 'read-yaml-file';
12
- import { readJson5File, readJsonFile, } from './readFile.js';
11
+ import { readYamlFile, readYamlFileSync } from 'read-yaml-file';
12
+ import { readJson5File, readJson5FileSync, readJsonFile, readJsonFileSync, } from './readFile.js';
13
13
  const limitProjectManifestReads = pLimit(4);
14
14
  export async function safeReadProjectManifestOnly(projectDir) {
15
15
  return limitProjectManifestReads(async () => {
@@ -24,6 +24,29 @@ export async function safeReadProjectManifestOnly(projectDir) {
24
24
  }
25
25
  });
26
26
  }
27
+ export async function safeReadPublishManifest(projectDir) {
28
+ return (await safeReadProjectManifestOnly(projectDir)) ?? safeReadParentPublishManifest(projectDir);
29
+ }
30
+ /**
31
+ * Finds the manifest of a project whose `publishConfig.directory` is `publishDir`,
32
+ * searching the ancestors of `publishDir`.
33
+ */
34
+ export async function safeReadParentPublishManifest(publishDir) {
35
+ const normalizedTarget = path.resolve(publishDir);
36
+ let searchDir = path.dirname(normalizedTarget);
37
+ while (true) {
38
+ // eslint-disable-next-line no-await-in-loop
39
+ const parentManifest = await safeReadProjectManifestOnly(searchDir);
40
+ if (parentManifest?.publishConfig?.directory &&
41
+ path.resolve(searchDir, parentManifest.publishConfig.directory) === normalizedTarget) {
42
+ return parentManifest;
43
+ }
44
+ const next = path.dirname(searchDir);
45
+ if (next === searchDir)
46
+ return null;
47
+ searchDir = next;
48
+ }
49
+ }
27
50
  export async function readProjectManifest(projectDir) {
28
51
  const result = await tryReadProjectManifest(projectDir);
29
52
  if (result.manifest !== null) {
@@ -39,11 +62,13 @@ export async function tryReadProjectManifest(projectDir) {
39
62
  try {
40
63
  const manifestPath = path.join(projectDir, 'package.json');
41
64
  const { data, text } = await readJsonFile(manifestPath);
65
+ const emptyDependencyFields = findEmptyDependencyFields(data);
42
66
  return {
43
67
  fileName: 'package.json',
44
68
  manifest: convertManifestAfterRead(data),
45
69
  writeProjectManifest: createManifestWriter({
46
70
  ...detectFileFormatting(text),
71
+ emptyDependencyFields,
47
72
  initialManifest: data,
48
73
  manifestPath,
49
74
  }),
@@ -56,11 +81,13 @@ export async function tryReadProjectManifest(projectDir) {
56
81
  try {
57
82
  const manifestPath = path.join(projectDir, 'package.json5');
58
83
  const { data, text } = await readJson5File(manifestPath);
84
+ const emptyDependencyFields = findEmptyDependencyFields(data);
59
85
  return {
60
86
  fileName: 'package.json5',
61
87
  manifest: convertManifestAfterRead(data),
62
88
  writeProjectManifest: createManifestWriter({
63
89
  ...detectFileFormattingAndComments(text),
90
+ emptyDependencyFields,
64
91
  initialManifest: data,
65
92
  manifestPath,
66
93
  }),
@@ -73,10 +100,11 @@ export async function tryReadProjectManifest(projectDir) {
73
100
  try {
74
101
  const manifestPath = path.join(projectDir, 'package.yaml');
75
102
  const manifest = await readPackageYaml(manifestPath);
103
+ const emptyDependencyFields = findEmptyDependencyFields(manifest);
76
104
  return {
77
105
  fileName: 'package.yaml',
78
106
  manifest: convertManifestAfterRead(manifest),
79
- writeProjectManifest: createManifestWriter({ initialManifest: manifest, manifestPath }),
107
+ writeProjectManifest: createManifestWriter({ emptyDependencyFields, initialManifest: manifest, manifestPath }),
80
108
  };
81
109
  }
82
110
  catch (err) { // eslint-disable-line
@@ -110,12 +138,14 @@ function detectFileFormattingAndComments(text) {
110
138
  const { comments, text: newText, hasFinalNewline } = extractComments(text);
111
139
  return {
112
140
  comments,
141
+ crlf: text.includes('\r\n'),
113
142
  indent: detectIndent(newText).indent,
114
143
  insertFinalNewline: hasFinalNewline,
115
144
  };
116
145
  }
117
146
  function detectFileFormatting(text) {
118
147
  return {
148
+ crlf: text.includes('\r\n'),
119
149
  indent: detectIndent(text).indent,
120
150
  insertFinalNewline: text.endsWith('\n'),
121
151
  };
@@ -125,10 +155,12 @@ export async function readExactProjectManifest(manifestPath) {
125
155
  switch (base) {
126
156
  case 'package.json': {
127
157
  const { data, text } = await readJsonFile(manifestPath);
158
+ const emptyDependencyFields = findEmptyDependencyFields(data);
128
159
  return {
129
160
  manifest: convertManifestAfterRead(data),
130
161
  writeProjectManifest: createManifestWriter({
131
162
  ...detectFileFormatting(text),
163
+ emptyDependencyFields,
132
164
  initialManifest: data,
133
165
  manifestPath,
134
166
  }),
@@ -136,10 +168,12 @@ export async function readExactProjectManifest(manifestPath) {
136
168
  }
137
169
  case 'package.json5': {
138
170
  const { data, text } = await readJson5File(manifestPath);
171
+ const emptyDependencyFields = findEmptyDependencyFields(data);
139
172
  return {
140
173
  manifest: convertManifestAfterRead(data),
141
174
  writeProjectManifest: createManifestWriter({
142
175
  ...detectFileFormattingAndComments(text),
176
+ emptyDependencyFields,
143
177
  initialManifest: data,
144
178
  manifestPath,
145
179
  }),
@@ -147,9 +181,50 @@ export async function readExactProjectManifest(manifestPath) {
147
181
  }
148
182
  case 'package.yaml': {
149
183
  const manifest = await readPackageYaml(manifestPath);
184
+ const emptyDependencyFields = findEmptyDependencyFields(manifest);
150
185
  return {
151
186
  manifest: convertManifestAfterRead(manifest),
152
- writeProjectManifest: createManifestWriter({ initialManifest: manifest, manifestPath }),
187
+ writeProjectManifest: createManifestWriter({ emptyDependencyFields, initialManifest: manifest, manifestPath }),
188
+ };
189
+ }
190
+ }
191
+ throw new Error(`Not supported manifest name "${base}"`);
192
+ }
193
+ export function readExactProjectManifestSync(manifestPath) {
194
+ const base = path.basename(manifestPath).toLowerCase();
195
+ switch (base) {
196
+ case 'package.json': {
197
+ const { data, text } = readJsonFileSync(manifestPath);
198
+ const emptyDependencyFields = findEmptyDependencyFields(data);
199
+ return {
200
+ manifest: convertManifestAfterRead(data),
201
+ writeProjectManifest: createManifestWriter({
202
+ ...detectFileFormatting(text),
203
+ emptyDependencyFields,
204
+ initialManifest: data,
205
+ manifestPath,
206
+ }),
207
+ };
208
+ }
209
+ case 'package.json5': {
210
+ const { data, text } = readJson5FileSync(manifestPath);
211
+ const emptyDependencyFields = findEmptyDependencyFields(data);
212
+ return {
213
+ manifest: convertManifestAfterRead(data),
214
+ writeProjectManifest: createManifestWriter({
215
+ ...detectFileFormattingAndComments(text),
216
+ emptyDependencyFields,
217
+ initialManifest: data,
218
+ manifestPath,
219
+ }),
220
+ };
221
+ }
222
+ case 'package.yaml': {
223
+ const manifest = readPackageYamlSync(manifestPath);
224
+ const emptyDependencyFields = findEmptyDependencyFields(manifest);
225
+ return {
226
+ manifest: convertManifestAfterRead(manifest),
227
+ writeProjectManifest: createManifestWriter({ emptyDependencyFields, initialManifest: manifest, manifestPath }),
153
228
  };
154
229
  }
155
230
  }
@@ -167,31 +242,64 @@ async function readPackageYaml(filePath) {
167
242
  throw err;
168
243
  }
169
244
  }
245
+ function readPackageYamlSync(filePath) {
246
+ try {
247
+ return readYamlFileSync(filePath);
248
+ }
249
+ catch (err) { // eslint-disable-line
250
+ if (err.name !== 'YAMLException')
251
+ throw err;
252
+ err.message = `${err.message}\nin ${filePath}`;
253
+ err.code = 'ERR_PNPM_YAML_PARSE';
254
+ throw err;
255
+ }
256
+ }
170
257
  function createManifestWriter(opts) {
171
- let initialManifest = normalize(opts.initialManifest);
258
+ let emptyDependencyFields = opts.emptyDependencyFields ?? findEmptyDependencyFields(opts.initialManifest);
259
+ let initialManifest = normalize(opts.initialManifest, emptyDependencyFields);
172
260
  return async (updatedManifest, force) => {
173
- updatedManifest = convertManifestBeforeWrite(normalize(updatedManifest));
261
+ updatedManifest = convertManifestBeforeWrite(normalize(updatedManifest, emptyDependencyFields));
174
262
  if (force === true || !equal(initialManifest, updatedManifest)) {
175
263
  await writeProjectManifest(opts.manifestPath, updatedManifest, {
176
264
  comments: opts.comments,
265
+ crlf: opts.crlf,
177
266
  indent: opts.indent,
178
267
  insertFinalNewline: opts.insertFinalNewline,
179
268
  });
180
- initialManifest = normalize(updatedManifest);
269
+ emptyDependencyFields = findEmptyDependencyFields(updatedManifest);
270
+ initialManifest = normalize(updatedManifest, emptyDependencyFields);
181
271
  return Promise.resolve(undefined);
182
272
  }
183
273
  return Promise.resolve(undefined);
184
274
  };
185
275
  }
186
276
  function convertManifestAfterRead(manifest) {
187
- convertEnginesRuntimeToDependencies(manifest, 'devEngines', 'devDependencies');
188
- convertEnginesRuntimeToDependencies(manifest, 'engines', 'dependencies');
189
- return manifest;
277
+ const cloned = cloneManifestForRuntimeConversion(manifest);
278
+ convertEnginesRuntimeToDependencies(cloned, 'devEngines', 'devDependencies');
279
+ convertEnginesRuntimeToDependencies(cloned, 'engines', 'dependencies');
280
+ return cloned;
190
281
  }
191
282
  function convertManifestBeforeWrite(manifest) {
192
- convertDependenciesToEnginesRuntime(manifest, 'devDependencies', 'devEngines');
193
- convertDependenciesToEnginesRuntime(manifest, 'dependencies', 'engines');
194
- return manifest;
283
+ const cloned = cloneManifestForRuntimeConversion(manifest);
284
+ convertDependenciesToEnginesRuntime(cloned, 'devDependencies', 'devEngines');
285
+ convertDependenciesToEnginesRuntime(cloned, 'dependencies', 'engines');
286
+ return cloned;
287
+ }
288
+ function cloneManifestForRuntimeConversion(manifest) {
289
+ const cloned = { ...manifest };
290
+ if (manifest.dependencies != null && typeof manifest.dependencies === 'object' && !Array.isArray(manifest.dependencies)) {
291
+ cloned.dependencies = { ...manifest.dependencies };
292
+ }
293
+ if (manifest.devDependencies != null && typeof manifest.devDependencies === 'object' && !Array.isArray(manifest.devDependencies)) {
294
+ cloned.devDependencies = { ...manifest.devDependencies };
295
+ }
296
+ if (manifest.engines != null && typeof manifest.engines === 'object' && !Array.isArray(manifest.engines)) {
297
+ cloned.engines = { ...manifest.engines };
298
+ }
299
+ if (manifest.devEngines != null && typeof manifest.devEngines === 'object' && !Array.isArray(manifest.devEngines)) {
300
+ cloned.devEngines = { ...manifest.devEngines };
301
+ }
302
+ return cloned;
195
303
  }
196
304
  function convertDependenciesToEnginesRuntime(manifest, dependenciesFieldName, enginesFieldName) {
197
305
  const dependencies = readDependenciesField(manifest, dependenciesFieldName);
@@ -229,7 +337,7 @@ function convertDependenciesToEnginesRuntime(manifest, dependenciesFieldName, en
229
337
  }
230
338
  delete dependencies[runtimeName];
231
339
  }
232
- else {
340
+ else if (dep === undefined) {
233
341
  removeManagedRuntimeEntry(manifest[enginesFieldName], runtimeName);
234
342
  }
235
343
  }
@@ -270,7 +378,26 @@ const dependencyKeys = new Set([
270
378
  'optionalDependencies',
271
379
  'peerDependencies',
272
380
  ]);
273
- function normalize(manifest) {
381
+ /**
382
+ * The dependency fields the manifest declares as empty objects. A write
383
+ * keeps these in place; it only drops a field that pnpm itself emptied.
384
+ */
385
+ function findEmptyDependencyFields(manifest) {
386
+ const fields = new Set();
387
+ for (const key of dependencyKeys) {
388
+ if (isEmptyDependencyObject(manifest[key])) {
389
+ fields.add(key);
390
+ }
391
+ }
392
+ return fields;
393
+ }
394
+ function isEmptyDependencyObject(value) {
395
+ return typeof value === 'object' &&
396
+ value !== null &&
397
+ !Array.isArray(value) &&
398
+ Object.keys(value).length === 0;
399
+ }
400
+ function normalize(manifest, keepEmptyDependencyFields) {
274
401
  const result = {};
275
402
  for (const key in manifest) {
276
403
  if (Object.hasOwn(manifest, key)) {
@@ -292,6 +419,9 @@ function normalize(manifest) {
292
419
  }
293
420
  result[key] = sortedValue;
294
421
  }
422
+ else if (keepEmptyDependencyFields.has(key)) {
423
+ result[key] = {};
424
+ }
295
425
  }
296
426
  }
297
427
  }
package/lib/readFile.d.ts CHANGED
@@ -3,7 +3,15 @@ export declare function readJson5File(filePath: string): Promise<{
3
3
  data: ProjectManifest;
4
4
  text: string;
5
5
  }>;
6
+ export declare function readJson5FileSync(filePath: string): {
7
+ data: ProjectManifest;
8
+ text: string;
9
+ };
6
10
  export declare function readJsonFile(filePath: string): Promise<{
7
11
  data: ProjectManifest;
8
12
  text: string;
9
13
  }>;
14
+ export declare function readJsonFileSync(filePath: string): {
15
+ data: ProjectManifest;
16
+ text: string;
17
+ };
package/lib/readFile.js CHANGED
@@ -16,6 +16,20 @@ export async function readJson5File(filePath) {
16
16
  throw err;
17
17
  }
18
18
  }
19
+ export function readJson5FileSync(filePath) {
20
+ const text = readFileWithoutBomSync(filePath);
21
+ try {
22
+ return {
23
+ data: JSON5.parse(text),
24
+ text,
25
+ };
26
+ }
27
+ catch (err) { // eslint-disable-line
28
+ err.message = `${err.message} in ${filePath}`;
29
+ err['code'] = 'ERR_PNPM_JSON5_PARSE';
30
+ throw err;
31
+ }
32
+ }
19
33
  export async function readJsonFile(filePath) {
20
34
  const text = await readFileWithoutBom(filePath);
21
35
  try {
@@ -29,7 +43,23 @@ export async function readJsonFile(filePath) {
29
43
  throw err;
30
44
  }
31
45
  }
46
+ export function readJsonFileSync(filePath) {
47
+ const text = readFileWithoutBomSync(filePath);
48
+ try {
49
+ return {
50
+ data: parseJson(text, filePath),
51
+ text,
52
+ };
53
+ }
54
+ catch (err) { // eslint-disable-line
55
+ err['code'] = 'ERR_PNPM_JSON_PARSE';
56
+ throw err;
57
+ }
58
+ }
32
59
  async function readFileWithoutBom(path) {
33
60
  return stripBom(await gfs.readFile(path, 'utf8'));
34
61
  }
62
+ function readFileWithoutBomSync(path) {
63
+ return stripBom(gfs.readFileSync(path, 'utf8'));
64
+ }
35
65
  //# sourceMappingURL=readFile.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/workspace.project-manifest-reader",
3
- "version": "1100.0.29",
3
+ "version": "1100.1.0",
4
4
  "description": "Read a project manifest (called package.json in most cases)",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -27,12 +27,12 @@
27
27
  "!*.map"
28
28
  ],
29
29
  "dependencies": {
30
- "@pnpm/error": "1100.1.4",
31
- "@pnpm/fs.graceful-fs": "1100.2.2",
32
- "@pnpm/pkg-manifest.utils": "1100.4.5",
30
+ "@pnpm/error": "1100.2.0",
31
+ "@pnpm/fs.graceful-fs": "1100.2.3",
32
+ "@pnpm/pkg-manifest.utils": "1100.4.6",
33
33
  "@pnpm/text.comments-parser": "1100.0.1",
34
- "@pnpm/types": "1102.1.0",
35
- "@pnpm/workspace.project-manifest-writer": "1100.0.17",
34
+ "@pnpm/types": "1102.1.1",
35
+ "@pnpm/workspace.project-manifest-writer": "1100.0.18",
36
36
  "detect-indent": "7.0.2",
37
37
  "fast-deep-equal": "^3.1.3",
38
38
  "is-windows": "^1.0.2",
@@ -48,7 +48,7 @@
48
48
  "devDependencies": {
49
49
  "@jest/globals": "30.4.1",
50
50
  "@pnpm/test-fixtures": "1100.0.1",
51
- "@pnpm/workspace.project-manifest-reader": "1100.0.29",
51
+ "@pnpm/workspace.project-manifest-reader": "1100.1.0",
52
52
  "@types/is-windows": "^1.0.2",
53
53
  "@types/parse-json": "^7.0.0",
54
54
  "tempy": "3.0.0"