@pnpm/workspace.project-manifest-reader 1100.1.1 → 1100.1.2

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,12 @@
1
1
  # @pnpm/read-project-manifest
2
2
 
3
+ ## 1100.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies:
8
+ - @pnpm/pkg-manifest.utils@1100.4.8
9
+
3
10
  ## 1100.1.1
4
11
 
5
12
  ### Patch Changes
package/lib/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { ProjectManifest } from '@pnpm/types';
2
- export type WriteProjectManifest = (manifest: ProjectManifest, force?: boolean) => Promise<void>;
2
+ import { readExactProjectManifest, type ReadExactProjectManifestResult, readExactProjectManifestSync, type WriteProjectManifest } from './manifestExactReader.js';
3
+ export type { ReadExactProjectManifestResult, WriteProjectManifest };
4
+ export { readExactProjectManifest, readExactProjectManifestSync };
3
5
  export declare function safeReadProjectManifestOnly(projectDir: string): Promise<ProjectManifest | null>;
4
6
  export declare function safeReadPublishManifest(projectDir: string): Promise<ProjectManifest | null>;
5
7
  /**
@@ -18,10 +20,3 @@ export declare function tryReadProjectManifest(projectDir: string): Promise<{
18
20
  manifest: ProjectManifest | null;
19
21
  writeProjectManifest: WriteProjectManifest;
20
22
  }>;
21
- interface ReadExactProjectManifestResult {
22
- manifest: ProjectManifest;
23
- writeProjectManifest: WriteProjectManifest;
24
- }
25
- export declare function readExactProjectManifest(manifestPath: string): Promise<ReadExactProjectManifestResult>;
26
- export declare function readExactProjectManifestSync(manifestPath: string): ReadExactProjectManifestResult;
27
- export {};
package/lib/index.js CHANGED
@@ -1,15 +1,11 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { PnpmError } from '@pnpm/error';
4
- import { convertEnginesRuntimeToDependencies } from '@pnpm/pkg-manifest.utils';
5
- import { extractComments } from '@pnpm/text.comments-parser';
6
4
  import { writeProjectManifest } from '@pnpm/workspace.project-manifest-writer';
7
- import detectIndent from 'detect-indent';
8
- import equal from 'fast-deep-equal';
9
5
  import isWindows from 'is-windows';
10
6
  import pLimit from 'p-limit';
11
- import { readYamlFile, readYamlFileSync } from 'read-yaml-file';
12
- import { readJson5File, readJson5FileSync, readJsonFile, readJsonFileSync, } from './readFile.js';
7
+ import { readExactProjectManifest, readExactProjectManifestSync, tryReadProjectManifestFromDir, } from './manifestExactReader.js';
8
+ export { readExactProjectManifest, readExactProjectManifestSync };
13
9
  const limitProjectManifestReads = pLimit(4);
14
10
  export async function safeReadProjectManifestOnly(projectDir) {
15
11
  return limitProjectManifestReads(async () => {
@@ -35,7 +31,7 @@ export async function safeReadParentPublishManifest(publishDir) {
35
31
  const normalizedTarget = path.resolve(publishDir);
36
32
  let searchDir = path.dirname(normalizedTarget);
37
33
  while (true) {
38
- // eslint-disable-next-line no-await-in-loop
34
+ // eslint-disable-next-line no-await-in-loop -- the search climbs one ancestor at a time and stops at the first match
39
35
  const parentManifest = await safeReadProjectManifestOnly(searchDir);
40
36
  if (parentManifest?.publishConfig?.directory &&
41
37
  path.resolve(searchDir, parentManifest.publishConfig.directory) === normalizedTarget) {
@@ -59,74 +55,10 @@ export async function readProjectManifestOnly(projectDir) {
59
55
  return manifest;
60
56
  }
61
57
  export async function tryReadProjectManifest(projectDir) {
62
- try {
63
- const manifestPath = path.join(projectDir, 'package.json');
64
- const { data, text } = await readJsonFile(manifestPath);
65
- const emptyDependencyFields = findEmptyDependencyFields(data);
66
- return {
67
- fileName: 'package.json',
68
- manifest: convertManifestAfterRead(data),
69
- writeProjectManifest: createManifestWriter({
70
- ...detectFileFormatting(text),
71
- emptyDependencyFields,
72
- initialManifest: data,
73
- manifestPath,
74
- }),
75
- };
76
- }
77
- catch (err) { // eslint-disable-line
78
- if (err.code !== 'ENOENT')
79
- throw err;
80
- }
81
- try {
82
- const manifestPath = path.join(projectDir, 'package.json5');
83
- const { data, text } = await readJson5File(manifestPath);
84
- const emptyDependencyFields = findEmptyDependencyFields(data);
85
- return {
86
- fileName: 'package.json5',
87
- manifest: convertManifestAfterRead(data),
88
- writeProjectManifest: createManifestWriter({
89
- ...detectFileFormattingAndComments(text),
90
- emptyDependencyFields,
91
- initialManifest: data,
92
- manifestPath,
93
- }),
94
- };
95
- }
96
- catch (err) { // eslint-disable-line
97
- if (err.code !== 'ENOENT')
98
- throw err;
99
- }
100
- try {
101
- const manifestPath = path.join(projectDir, 'package.yaml');
102
- const manifest = await readPackageYaml(manifestPath);
103
- const emptyDependencyFields = findEmptyDependencyFields(manifest);
104
- return {
105
- fileName: 'package.yaml',
106
- manifest: convertManifestAfterRead(manifest),
107
- writeProjectManifest: createManifestWriter({ emptyDependencyFields, initialManifest: manifest, manifestPath }),
108
- };
109
- }
110
- catch (err) { // eslint-disable-line
111
- if (err.code !== 'ENOENT')
112
- throw err;
113
- }
114
- if (isWindows()) {
115
- // ENOTDIR isn't used on Windows, but pnpm expects it.
116
- let s;
117
- try {
118
- s = await fs.stat(projectDir);
119
- }
120
- catch (err) { // eslint-disable-line
121
- // Ignore
122
- }
123
- if ((s != null) && !s.isDirectory()) {
124
- const err = new Error(`"${projectDir}" is not a directory`);
125
- // @ts-expect-error
126
- err['code'] = 'ENOTDIR';
127
- throw err;
128
- }
129
- }
58
+ const found = await tryReadProjectManifestFromDir(projectDir);
59
+ if (found)
60
+ return found;
61
+ await assertDirectoryExistsOnWindows(projectDir);
130
62
  const filePath = path.join(projectDir, 'package.json');
131
63
  return {
132
64
  fileName: 'package.json',
@@ -134,297 +66,19 @@ export async function tryReadProjectManifest(projectDir) {
134
66
  writeProjectManifest: async (manifest) => writeProjectManifest(filePath, manifest),
135
67
  };
136
68
  }
137
- function detectFileFormattingAndComments(text) {
138
- const { comments, text: newText, hasFinalNewline } = extractComments(text);
139
- return {
140
- comments,
141
- crlf: text.includes('\r\n'),
142
- indent: detectIndent(newText).indent,
143
- insertFinalNewline: hasFinalNewline,
144
- };
145
- }
146
- function detectFileFormatting(text) {
147
- return {
148
- crlf: text.includes('\r\n'),
149
- indent: detectIndent(text).indent,
150
- insertFinalNewline: text.endsWith('\n'),
151
- };
152
- }
153
- export async function readExactProjectManifest(manifestPath) {
154
- const base = path.basename(manifestPath).toLowerCase();
155
- switch (base) {
156
- case 'package.json': {
157
- const { data, text } = await readJsonFile(manifestPath);
158
- const emptyDependencyFields = findEmptyDependencyFields(data);
159
- return {
160
- manifest: convertManifestAfterRead(data),
161
- writeProjectManifest: createManifestWriter({
162
- ...detectFileFormatting(text),
163
- emptyDependencyFields,
164
- initialManifest: data,
165
- manifestPath,
166
- }),
167
- };
168
- }
169
- case 'package.json5': {
170
- const { data, text } = await readJson5File(manifestPath);
171
- const emptyDependencyFields = findEmptyDependencyFields(data);
172
- return {
173
- manifest: convertManifestAfterRead(data),
174
- writeProjectManifest: createManifestWriter({
175
- ...detectFileFormattingAndComments(text),
176
- emptyDependencyFields,
177
- initialManifest: data,
178
- manifestPath,
179
- }),
180
- };
181
- }
182
- case 'package.yaml': {
183
- const manifest = await readPackageYaml(manifestPath);
184
- const emptyDependencyFields = findEmptyDependencyFields(manifest);
185
- return {
186
- manifest: convertManifestAfterRead(manifest),
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 }),
228
- };
229
- }
230
- }
231
- throw new Error(`Not supported manifest name "${base}"`);
232
- }
233
- async function readPackageYaml(filePath) {
234
- try {
235
- return await readYamlFile(filePath);
236
- }
237
- catch (err) { // eslint-disable-line
238
- if (err.name !== 'YAMLException')
239
- throw err;
240
- err.message = `${err.message}\nin ${filePath}`;
241
- err.code = 'ERR_PNPM_YAML_PARSE';
242
- throw err;
243
- }
244
- }
245
- function readPackageYamlSync(filePath) {
69
+ async function assertDirectoryExistsOnWindows(projectDir) {
70
+ if (!isWindows())
71
+ return;
72
+ // ENOTDIR isn't used on Windows, but pnpm expects it.
73
+ let projectDirStats;
246
74
  try {
247
- return readYamlFileSync(filePath);
75
+ projectDirStats = await fs.stat(projectDir);
248
76
  }
249
77
  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
- }
257
- function createManifestWriter(opts) {
258
- let emptyDependencyFields = opts.emptyDependencyFields ?? findEmptyDependencyFields(opts.initialManifest);
259
- let initialManifest = normalize(opts.initialManifest, emptyDependencyFields);
260
- return async (updatedManifest, force) => {
261
- updatedManifest = convertManifestBeforeWrite(normalize(updatedManifest, emptyDependencyFields));
262
- if (force === true || !equal(initialManifest, updatedManifest)) {
263
- await writeProjectManifest(opts.manifestPath, updatedManifest, {
264
- comments: opts.comments,
265
- crlf: opts.crlf,
266
- indent: opts.indent,
267
- insertFinalNewline: opts.insertFinalNewline,
268
- });
269
- emptyDependencyFields = findEmptyDependencyFields(updatedManifest);
270
- initialManifest = normalize(updatedManifest, emptyDependencyFields);
271
- return Promise.resolve(undefined);
272
- }
273
- return Promise.resolve(undefined);
274
- };
275
- }
276
- function convertManifestAfterRead(manifest) {
277
- const cloned = cloneManifestForRuntimeConversion(manifest);
278
- convertEnginesRuntimeToDependencies(cloned, 'devEngines', 'devDependencies');
279
- convertEnginesRuntimeToDependencies(cloned, 'engines', 'dependencies');
280
- return cloned;
281
- }
282
- function convertManifestBeforeWrite(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 };
78
+ // Ignore
292
79
  }
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;
303
- }
304
- function convertDependenciesToEnginesRuntime(manifest, dependenciesFieldName, enginesFieldName) {
305
- const dependencies = readDependenciesField(manifest, dependenciesFieldName);
306
- for (const runtimeName of ['node', 'deno', 'bun']) {
307
- const dep = dependencies?.[runtimeName];
308
- if (dependencies != null && typeof dep === 'string' && dep.startsWith('runtime:')) {
309
- const version = dep.slice('runtime:'.length).trim();
310
- manifest[enginesFieldName] ??= {};
311
- const runtimeEntry = {
312
- name: runtimeName,
313
- version,
314
- onFail: 'download',
315
- };
316
- const enginesField = manifest[enginesFieldName];
317
- if (!enginesField.runtime) {
318
- enginesField.runtime = runtimeEntry;
319
- }
320
- else if (Array.isArray(enginesField.runtime)) {
321
- const existing = enginesField.runtime.find(({ name }) => name === runtimeName);
322
- if (existing) {
323
- Object.assign(existing, runtimeEntry);
324
- }
325
- else {
326
- enginesField.runtime.push(runtimeEntry);
327
- }
328
- }
329
- else if (enginesField.runtime.name === runtimeName) {
330
- Object.assign(enginesField.runtime, runtimeEntry);
331
- }
332
- else {
333
- enginesField.runtime = [
334
- enginesField.runtime,
335
- runtimeEntry,
336
- ];
337
- }
338
- delete dependencies[runtimeName];
339
- }
340
- else if (dep === undefined) {
341
- removeManagedRuntimeEntry(manifest[enginesFieldName], runtimeName);
342
- }
343
- }
344
- }
345
- function readDependenciesField(manifest, dependenciesFieldName) {
346
- const dependencies = manifest[dependenciesFieldName];
347
- if (dependencies === undefined)
348
- return undefined;
349
- if (dependencies === null || typeof dependencies !== 'object' || Array.isArray(dependencies)) {
350
- throw new PnpmError('INVALID_DEPENDENCIES_FIELD', `The "${dependenciesFieldName}" field must be an object.`);
351
- }
352
- return dependencies;
353
- }
354
- function removeManagedRuntimeEntry(enginesField, runtimeName) {
355
- if (!enginesField?.runtime)
356
- return;
357
- if (Array.isArray(enginesField.runtime)) {
358
- const runtimes = enginesField.runtime.filter((runtime) => !isManagedRuntimeEntry(runtime, runtimeName));
359
- if (runtimes.length === 0) {
360
- delete enginesField.runtime;
361
- }
362
- else {
363
- enginesField.runtime = runtimes;
364
- }
365
- }
366
- else if (isManagedRuntimeEntry(enginesField.runtime, runtimeName)) {
367
- delete enginesField.runtime;
368
- }
369
- }
370
- function isManagedRuntimeEntry(runtime, runtimeName) {
371
- return runtime.name === runtimeName &&
372
- runtime.onFail === 'download' &&
373
- typeof runtime.version === 'string';
374
- }
375
- const dependencyKeys = new Set([
376
- 'dependencies',
377
- 'devDependencies',
378
- 'optionalDependencies',
379
- 'peerDependencies',
380
- ]);
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) {
401
- const result = {};
402
- for (const key in manifest) {
403
- if (Object.hasOwn(manifest, key)) {
404
- const value = manifest[key];
405
- if (typeof value !== 'object' ||
406
- value === null ||
407
- !dependencyKeys.has(key) ||
408
- Array.isArray(value)) {
409
- result[key] = structuredClone(value);
410
- }
411
- else {
412
- const keys = Object.keys(value);
413
- if (keys.length !== 0) {
414
- keys.sort();
415
- const sortedValue = {};
416
- for (const k of keys) {
417
- // @ts-expect-error this is fine
418
- sortedValue[k] = value[k];
419
- }
420
- result[key] = sortedValue;
421
- }
422
- else if (keepEmptyDependencyFields.has(key)) {
423
- result[key] = {};
424
- }
425
- }
426
- }
80
+ if ((projectDirStats != null) && !projectDirStats.isDirectory()) {
81
+ throw Object.assign(new Error(`"${projectDir}" is not a directory`), { code: 'ENOTDIR' });
427
82
  }
428
- return result;
429
83
  }
430
84
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,24 @@
1
+ import { type CommentSpecifier } from '@pnpm/text.comments-parser';
2
+ import type { ProjectManifest } from '@pnpm/types';
3
+ export type WriteProjectManifest = (manifest: ProjectManifest, force?: boolean) => Promise<void>;
4
+ export interface ReadExactProjectManifestResult {
5
+ manifest: ProjectManifest;
6
+ writeProjectManifest: WriteProjectManifest;
7
+ }
8
+ export declare function readExactProjectManifest(manifestPath: string): Promise<ReadExactProjectManifestResult>;
9
+ export declare function readExactProjectManifestSync(manifestPath: string): ReadExactProjectManifestResult;
10
+ export declare function tryReadProjectManifestFromDir(projectDir: string): Promise<{
11
+ fileName: string;
12
+ manifest: ProjectManifest;
13
+ writeProjectManifest: WriteProjectManifest;
14
+ } | undefined>;
15
+ export declare function createManifestWriter(opts: {
16
+ initialManifest: ProjectManifest;
17
+ emptyDependencyFields?: ReadonlySet<string>;
18
+ comments?: CommentSpecifier[];
19
+ crlf?: boolean;
20
+ indent?: string | number | undefined;
21
+ insertFinalNewline?: boolean;
22
+ manifestPath: string;
23
+ }): WriteProjectManifest;
24
+ export declare function findEmptyDependencyFields(manifest: ProjectManifest): Set<string>;
@@ -0,0 +1,224 @@
1
+ import path from 'node:path';
2
+ import { extractComments } from '@pnpm/text.comments-parser';
3
+ import { writeProjectManifest } from '@pnpm/workspace.project-manifest-writer';
4
+ import detectIndent from 'detect-indent';
5
+ import equal from 'fast-deep-equal';
6
+ import { readYamlFile, readYamlFileSync } from 'read-yaml-file';
7
+ import { convertManifestAfterRead, convertManifestBeforeWrite } from './manifestRuntime.js';
8
+ import { readJson5File, readJson5FileSync, readJsonFile, readJsonFileSync, } from './readFile.js';
9
+ export async function readExactProjectManifest(manifestPath) {
10
+ const base = path.basename(manifestPath).toLowerCase();
11
+ switch (base) {
12
+ case 'package.json':
13
+ return readExactPackageJson(manifestPath);
14
+ case 'package.json5':
15
+ return readExactPackageJson5(manifestPath);
16
+ case 'package.yaml':
17
+ return readExactPackageYaml(manifestPath);
18
+ }
19
+ throw new Error(`Not supported manifest name "${base}"`);
20
+ }
21
+ export function readExactProjectManifestSync(manifestPath) {
22
+ const base = path.basename(manifestPath).toLowerCase();
23
+ switch (base) {
24
+ case 'package.json':
25
+ return readExactPackageJsonSync(manifestPath);
26
+ case 'package.json5':
27
+ return readExactPackageJson5Sync(manifestPath);
28
+ case 'package.yaml':
29
+ return readExactPackageYamlSync(manifestPath);
30
+ }
31
+ throw new Error(`Not supported manifest name "${base}"`);
32
+ }
33
+ export async function tryReadProjectManifestFromDir(projectDir) {
34
+ const jsonResult = await tryReadPackageJson(projectDir);
35
+ if (jsonResult)
36
+ return jsonResult;
37
+ const json5Result = await tryReadPackageJson5(projectDir);
38
+ if (json5Result)
39
+ return json5Result;
40
+ return tryReadPackageYaml(projectDir);
41
+ }
42
+ async function tryReadPackageJson(projectDir) {
43
+ try {
44
+ const manifestPath = path.join(projectDir, 'package.json');
45
+ const result = await readExactPackageJson(manifestPath);
46
+ return { fileName: 'package.json', ...result };
47
+ }
48
+ catch (err) { // eslint-disable-line
49
+ if (err.code !== 'ENOENT')
50
+ throw err;
51
+ return undefined;
52
+ }
53
+ }
54
+ async function tryReadPackageJson5(projectDir) {
55
+ try {
56
+ const manifestPath = path.join(projectDir, 'package.json5');
57
+ const result = await readExactPackageJson5(manifestPath);
58
+ return { fileName: 'package.json5', ...result };
59
+ }
60
+ catch (err) { // eslint-disable-line
61
+ if (err.code !== 'ENOENT')
62
+ throw err;
63
+ return undefined;
64
+ }
65
+ }
66
+ async function tryReadPackageYaml(projectDir) {
67
+ try {
68
+ const manifestPath = path.join(projectDir, 'package.yaml');
69
+ const result = await readExactPackageYaml(manifestPath);
70
+ return { fileName: 'package.yaml', ...result };
71
+ }
72
+ catch (err) { // eslint-disable-line
73
+ if (err.code !== 'ENOENT')
74
+ throw err;
75
+ return undefined;
76
+ }
77
+ }
78
+ async function readExactPackageJson(manifestPath) {
79
+ const { data, text } = await readJsonFile(manifestPath);
80
+ return createManifestResult(data, manifestPath, detectFileFormatting(text));
81
+ }
82
+ function readExactPackageJsonSync(manifestPath) {
83
+ const { data, text } = readJsonFileSync(manifestPath);
84
+ return createManifestResult(data, manifestPath, detectFileFormatting(text));
85
+ }
86
+ async function readExactPackageJson5(manifestPath) {
87
+ const { data, text } = await readJson5File(manifestPath);
88
+ return createManifestResult(data, manifestPath, detectFileFormattingAndComments(text));
89
+ }
90
+ function readExactPackageJson5Sync(manifestPath) {
91
+ const { data, text } = readJson5FileSync(manifestPath);
92
+ return createManifestResult(data, manifestPath, detectFileFormattingAndComments(text));
93
+ }
94
+ async function readExactPackageYaml(manifestPath) {
95
+ const manifest = await readPackageYaml(manifestPath);
96
+ return createManifestResult(manifest, manifestPath);
97
+ }
98
+ function readExactPackageYamlSync(manifestPath) {
99
+ const manifest = readPackageYamlSync(manifestPath);
100
+ return createManifestResult(manifest, manifestPath);
101
+ }
102
+ function createManifestResult(data, manifestPath, formatting) {
103
+ const emptyDependencyFields = findEmptyDependencyFields(data);
104
+ return {
105
+ manifest: convertManifestAfterRead(data),
106
+ writeProjectManifest: createManifestWriter({
107
+ ...formatting,
108
+ emptyDependencyFields,
109
+ initialManifest: data,
110
+ manifestPath,
111
+ }),
112
+ };
113
+ }
114
+ function detectFileFormatting(text) {
115
+ return {
116
+ crlf: text.includes('\r\n'),
117
+ indent: detectIndent(text).indent,
118
+ insertFinalNewline: text.endsWith('\n'),
119
+ };
120
+ }
121
+ function detectFileFormattingAndComments(text) {
122
+ const { comments, text: newText, hasFinalNewline } = extractComments(text);
123
+ return {
124
+ comments,
125
+ crlf: text.includes('\r\n'),
126
+ indent: detectIndent(newText).indent,
127
+ insertFinalNewline: hasFinalNewline,
128
+ };
129
+ }
130
+ async function readPackageYaml(filePath) {
131
+ try {
132
+ return await readYamlFile(filePath);
133
+ }
134
+ catch (err) { // eslint-disable-line
135
+ if (err.name !== 'YAMLException')
136
+ throw err;
137
+ err.message = `${err.message}\nin ${filePath}`;
138
+ err.code = 'ERR_PNPM_YAML_PARSE';
139
+ throw err;
140
+ }
141
+ }
142
+ function readPackageYamlSync(filePath) {
143
+ try {
144
+ return readYamlFileSync(filePath);
145
+ }
146
+ catch (err) { // eslint-disable-line
147
+ if (err.name !== 'YAMLException')
148
+ throw err;
149
+ err.message = `${err.message}\nin ${filePath}`;
150
+ err.code = 'ERR_PNPM_YAML_PARSE';
151
+ throw err;
152
+ }
153
+ }
154
+ export function createManifestWriter(opts) {
155
+ let emptyDependencyFields = opts.emptyDependencyFields ?? findEmptyDependencyFields(opts.initialManifest);
156
+ let initialManifest = normalize(opts.initialManifest, emptyDependencyFields);
157
+ return async (updatedManifest, force) => {
158
+ updatedManifest = convertManifestBeforeWrite(normalize(updatedManifest, emptyDependencyFields));
159
+ if (force === true || !equal(initialManifest, updatedManifest)) {
160
+ await writeProjectManifest(opts.manifestPath, updatedManifest, {
161
+ comments: opts.comments,
162
+ crlf: opts.crlf,
163
+ indent: opts.indent,
164
+ insertFinalNewline: opts.insertFinalNewline,
165
+ });
166
+ emptyDependencyFields = findEmptyDependencyFields(updatedManifest);
167
+ initialManifest = normalize(updatedManifest, emptyDependencyFields);
168
+ }
169
+ };
170
+ }
171
+ const DEPENDENCY_KEYS = new Set([
172
+ 'dependencies',
173
+ 'devDependencies',
174
+ 'optionalDependencies',
175
+ 'peerDependencies',
176
+ ]);
177
+ export function findEmptyDependencyFields(manifest) {
178
+ const fields = new Set();
179
+ for (const key of DEPENDENCY_KEYS) {
180
+ if (isEmptyDependencyObject(manifest[key])) {
181
+ fields.add(key);
182
+ }
183
+ }
184
+ return fields;
185
+ }
186
+ function isEmptyDependencyObject(value) {
187
+ return typeof value === 'object' &&
188
+ value !== null &&
189
+ !Array.isArray(value) &&
190
+ Object.keys(value).length === 0;
191
+ }
192
+ function normalize(manifest, keepEmptyDependencyFields) {
193
+ const result = {};
194
+ for (const key of Object.keys(manifest)) {
195
+ const value = manifest[key];
196
+ if (isDependencyObject(value, key)) {
197
+ const keys = Object.keys(value);
198
+ if (keys.length !== 0) {
199
+ result[key] = sortObjectKeys(value, keys.sort());
200
+ }
201
+ else if (keepEmptyDependencyFields.has(key)) {
202
+ result[key] = {};
203
+ }
204
+ }
205
+ else {
206
+ result[key] = structuredClone(value);
207
+ }
208
+ }
209
+ return result;
210
+ }
211
+ function isDependencyObject(value, key) {
212
+ return typeof value === 'object' &&
213
+ value !== null &&
214
+ DEPENDENCY_KEYS.has(key) &&
215
+ !Array.isArray(value);
216
+ }
217
+ function sortObjectKeys(source, sortedKeys) {
218
+ const sorted = {};
219
+ for (const fieldName of sortedKeys) {
220
+ sorted[fieldName] = source[fieldName];
221
+ }
222
+ return sorted;
223
+ }
224
+ //# sourceMappingURL=manifestExactReader.js.map
@@ -0,0 +1,3 @@
1
+ import type { ProjectManifest } from '@pnpm/types';
2
+ export declare function convertManifestAfterRead(manifest: ProjectManifest): ProjectManifest;
3
+ export declare function convertManifestBeforeWrite(manifest: ProjectManifest): ProjectManifest;
@@ -0,0 +1,107 @@
1
+ import { PnpmError } from '@pnpm/error';
2
+ import { convertEnginesRuntimeToDependencies } from '@pnpm/pkg-manifest.utils';
3
+ export function convertManifestAfterRead(manifest) {
4
+ const cloned = cloneManifestForRuntimeConversion(manifest);
5
+ convertEnginesRuntimeToDependencies(cloned, 'devEngines', 'devDependencies');
6
+ convertEnginesRuntimeToDependencies(cloned, 'engines', 'dependencies');
7
+ return cloned;
8
+ }
9
+ export function convertManifestBeforeWrite(manifest) {
10
+ const cloned = cloneManifestForRuntimeConversion(manifest);
11
+ convertDependenciesToEnginesRuntime(cloned, 'devDependencies', 'devEngines');
12
+ convertDependenciesToEnginesRuntime(cloned, 'dependencies', 'engines');
13
+ return cloned;
14
+ }
15
+ function cloneManifestForRuntimeConversion(manifest) {
16
+ const cloned = { ...manifest };
17
+ if (manifest.dependencies != null && typeof manifest.dependencies === 'object' && !Array.isArray(manifest.dependencies)) {
18
+ cloned.dependencies = { ...manifest.dependencies };
19
+ }
20
+ if (manifest.devDependencies != null && typeof manifest.devDependencies === 'object' && !Array.isArray(manifest.devDependencies)) {
21
+ cloned.devDependencies = { ...manifest.devDependencies };
22
+ }
23
+ if (manifest.engines != null && typeof manifest.engines === 'object' && !Array.isArray(manifest.engines)) {
24
+ cloned.engines = { ...manifest.engines };
25
+ }
26
+ if (manifest.devEngines != null && typeof manifest.devEngines === 'object' && !Array.isArray(manifest.devEngines)) {
27
+ cloned.devEngines = { ...manifest.devEngines };
28
+ }
29
+ return cloned;
30
+ }
31
+ function convertDependenciesToEnginesRuntime(manifest, dependenciesFieldName, enginesFieldName) {
32
+ const dependencies = readDependenciesField(manifest, dependenciesFieldName);
33
+ for (const runtimeName of ['node', 'deno', 'bun']) {
34
+ applyRuntimeConversion(manifest, enginesFieldName, dependencies, runtimeName);
35
+ }
36
+ }
37
+ function applyRuntimeConversion(manifest, enginesFieldName, dependencies, runtimeName) {
38
+ const dep = dependencies?.[runtimeName];
39
+ if (dependencies != null && typeof dep === 'string' && dep.startsWith('runtime:')) {
40
+ setManagedRuntimeEntry(manifest, enginesFieldName, runtimeName, dep.slice('runtime:'.length).trim());
41
+ delete dependencies[runtimeName];
42
+ }
43
+ else if (dep === undefined) {
44
+ removeManagedRuntimeEntry(manifest[enginesFieldName], runtimeName);
45
+ }
46
+ }
47
+ function setManagedRuntimeEntry(manifest, enginesFieldName, runtimeName, version) {
48
+ manifest[enginesFieldName] ??= {};
49
+ const runtimeEntry = {
50
+ name: runtimeName,
51
+ version,
52
+ onFail: 'download',
53
+ };
54
+ const enginesField = manifest[enginesFieldName];
55
+ if (!enginesField.runtime) {
56
+ enginesField.runtime = runtimeEntry;
57
+ }
58
+ else if (Array.isArray(enginesField.runtime)) {
59
+ updateRuntimeInList(enginesField.runtime, runtimeEntry);
60
+ }
61
+ else if (enginesField.runtime.name === runtimeName) {
62
+ Object.assign(enginesField.runtime, runtimeEntry);
63
+ }
64
+ else {
65
+ enginesField.runtime = [enginesField.runtime, runtimeEntry];
66
+ }
67
+ }
68
+ function updateRuntimeInList(runtimes, runtimeEntry) {
69
+ const existing = runtimes.find(({ name }) => name === runtimeEntry.name);
70
+ if (existing) {
71
+ Object.assign(existing, runtimeEntry);
72
+ }
73
+ else {
74
+ runtimes.push(runtimeEntry);
75
+ }
76
+ }
77
+ function readDependenciesField(manifest, dependenciesFieldName) {
78
+ const dependencies = manifest[dependenciesFieldName];
79
+ if (dependencies === undefined)
80
+ return undefined;
81
+ if (dependencies === null || typeof dependencies !== 'object' || Array.isArray(dependencies)) {
82
+ throw new PnpmError('INVALID_DEPENDENCIES_FIELD', `The "${dependenciesFieldName}" field must be an object.`);
83
+ }
84
+ return dependencies;
85
+ }
86
+ function removeManagedRuntimeEntry(enginesField, runtimeName) {
87
+ if (!enginesField?.runtime)
88
+ return;
89
+ if (Array.isArray(enginesField.runtime)) {
90
+ const runtimes = enginesField.runtime.filter((runtime) => !isManagedRuntimeEntry(runtime, runtimeName));
91
+ if (runtimes.length === 0) {
92
+ delete enginesField.runtime;
93
+ }
94
+ else {
95
+ enginesField.runtime = runtimes;
96
+ }
97
+ }
98
+ else if (isManagedRuntimeEntry(enginesField.runtime, runtimeName)) {
99
+ delete enginesField.runtime;
100
+ }
101
+ }
102
+ function isManagedRuntimeEntry(runtime, runtimeName) {
103
+ return runtime.name === runtimeName &&
104
+ runtime.onFail === 'download' &&
105
+ typeof runtime.version === 'string';
106
+ }
107
+ //# sourceMappingURL=manifestRuntime.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/workspace.project-manifest-reader",
3
- "version": "1100.1.1",
3
+ "version": "1100.1.2",
4
4
  "description": "Read a project manifest (called package.json in most cases)",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -29,7 +29,7 @@
29
29
  "dependencies": {
30
30
  "@pnpm/error": "1100.2.1",
31
31
  "@pnpm/fs.graceful-fs": "1100.2.4",
32
- "@pnpm/pkg-manifest.utils": "1100.4.7",
32
+ "@pnpm/pkg-manifest.utils": "1100.4.8",
33
33
  "@pnpm/text.comments-parser": "1100.0.1",
34
34
  "@pnpm/types": "1102.1.1",
35
35
  "@pnpm/workspace.project-manifest-writer": "1100.0.19",
@@ -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.1.1",
51
+ "@pnpm/workspace.project-manifest-reader": "1100.1.2",
52
52
  "@types/is-windows": "^1.0.2",
53
53
  "@types/parse-json": "^7.0.0",
54
54
  "tempy": "3.0.0"