@pnpm/releasing.exportable-manifest 1100.3.4 → 1100.3.5
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 +6 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.js +46 -10
- package/lib/overridePublishConfig.js +2 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @pnpm/exportable-manifest
|
|
2
2
|
|
|
3
|
+
## 1100.3.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- `pnpm publish` now includes bare `README` files and README files with Markdown extensions such as `readme.markdown` in registry metadata [#12704](https://github.com/pnpm/pnpm/issues/12704).
|
|
8
|
+
|
|
3
9
|
## 1100.3.4
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/lib/index.d.ts
CHANGED
|
@@ -3,6 +3,29 @@ import type { Hooks } from '@pnpm/hooks.pnpmfile';
|
|
|
3
3
|
import type { ProjectManifest } from '@pnpm/types';
|
|
4
4
|
import { type ExportedManifest } from './transform/index.js';
|
|
5
5
|
export { type ExportedManifest };
|
|
6
|
+
declare const README_RANK: {
|
|
7
|
+
readonly bare: 1;
|
|
8
|
+
readonly markdown: 2;
|
|
9
|
+
readonly readmeMd: 3;
|
|
10
|
+
};
|
|
11
|
+
export interface ReadmeCandidate {
|
|
12
|
+
fileName: string;
|
|
13
|
+
rank: ReadmeRank;
|
|
14
|
+
}
|
|
15
|
+
/** How npm ranks a package-root file as the package's README. A higher rank wins. */
|
|
16
|
+
export type ReadmeRank = typeof README_RANK[keyof typeof README_RANK];
|
|
17
|
+
/**
|
|
18
|
+
* Rank a filename as a README candidate, or return `undefined` if npm would not use it as the
|
|
19
|
+
* package's README. npm matches the bare `README` case-sensitively and accepts `README.*` whose
|
|
20
|
+
* extension matches `/.m?a?r?k?d?o?w?n?$/i`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getReadmeRank(fileName: string): ReadmeRank | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Whether README `candidate` should replace the `current` selection. A higher rank wins. Equal
|
|
25
|
+
* ranks keep the lower filename, so the choice does not depend on directory or archive order, and
|
|
26
|
+
* an equal filename replaces, so the last duplicate archive entry wins as it would on extraction.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isPreferredReadme(candidate: ReadmeCandidate, current: ReadmeCandidate | undefined): boolean;
|
|
6
29
|
export type WorkspacePackageLookup = Array<{
|
|
7
30
|
manifest: ProjectManifest;
|
|
8
31
|
rootDir?: string;
|
package/lib/index.js
CHANGED
|
@@ -9,6 +9,35 @@ import { clone, omit } from 'ramda';
|
|
|
9
9
|
import { overridePublishConfig } from './overridePublishConfig.js';
|
|
10
10
|
import { transform } from './transform/index.js';
|
|
11
11
|
export {};
|
|
12
|
+
const README_RANK = {
|
|
13
|
+
bare: 1,
|
|
14
|
+
markdown: 2,
|
|
15
|
+
readmeMd: 3,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Rank a filename as a README candidate, or return `undefined` if npm would not use it as the
|
|
19
|
+
* package's README. npm matches the bare `README` case-sensitively and accepts `README.*` whose
|
|
20
|
+
* extension matches `/.m?a?r?k?d?o?w?n?$/i`.
|
|
21
|
+
*/
|
|
22
|
+
export function getReadmeRank(fileName) {
|
|
23
|
+
if (/^readme\.md$/i.test(fileName))
|
|
24
|
+
return README_RANK.readmeMd;
|
|
25
|
+
if (fileName === 'README')
|
|
26
|
+
return README_RANK.bare;
|
|
27
|
+
if (/^readme\./i.test(fileName) && /\.m?a?r?k?d?o?w?n?$/i.test(fileName))
|
|
28
|
+
return README_RANK.markdown;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether README `candidate` should replace the `current` selection. A higher rank wins. Equal
|
|
33
|
+
* ranks keep the lower filename, so the choice does not depend on directory or archive order, and
|
|
34
|
+
* an equal filename replaces, so the last duplicate archive entry wins as it would on extraction.
|
|
35
|
+
*/
|
|
36
|
+
export function isPreferredReadme(candidate, current) {
|
|
37
|
+
if (current == null || candidate.rank > current.rank)
|
|
38
|
+
return true;
|
|
39
|
+
return candidate.rank === current.rank && candidate.fileName <= current.fileName;
|
|
40
|
+
}
|
|
12
41
|
const PREPUBLISH_SCRIPTS = [
|
|
13
42
|
'prepublishOnly',
|
|
14
43
|
'prepack',
|
|
@@ -59,26 +88,33 @@ export async function createExportableManifest(dir, originalManifest, opts) {
|
|
|
59
88
|
}
|
|
60
89
|
}
|
|
61
90
|
for (const hook of opts?.hooks?.beforePacking ?? []) {
|
|
62
|
-
// eslint-disable-next-line no-await-in-loop
|
|
91
|
+
// eslint-disable-next-line no-await-in-loop -- each hook receives the manifest returned by the previous one
|
|
63
92
|
publishManifest = await hook(publishManifest, dir) ?? publishManifest;
|
|
64
93
|
}
|
|
65
94
|
return transform(publishManifest);
|
|
66
95
|
}
|
|
67
96
|
// `O_NOFOLLOW` makes the open itself refuse a symlink at the final path component, closing the
|
|
68
|
-
// TOCTOU window between the `readdir` type check and the read: a symlink swapped in for
|
|
69
|
-
// after the check can't redirect the read outside the project and leak its target
|
|
70
|
-
// published manifest. Windows lacks the flag (and requires privileges to create symlinks),
|
|
71
|
-
// falls back to a plain read.
|
|
97
|
+
// TOCTOU window between the `readdir` type check and the read: a symlink swapped in for the
|
|
98
|
+
// selected README after the check can't redirect the read outside the project and leak its target
|
|
99
|
+
// into the published manifest. Windows lacks the flag (and requires privileges to create symlinks),
|
|
100
|
+
// so it falls back to a plain read.
|
|
72
101
|
const README_READ_FLAGS = fs.constants.O_RDONLY | (process.platform === 'win32' ? 0 : fs.constants.O_NOFOLLOW);
|
|
73
102
|
export async function readReadmeFile(projectDir) {
|
|
74
103
|
const entries = await fs.promises.readdir(projectDir, { withFileTypes: true });
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
104
|
+
let readme;
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const rank = getReadmeRank(entry.name);
|
|
107
|
+
if (rank == null || !entry.isFile())
|
|
108
|
+
continue;
|
|
109
|
+
const candidate = { fileName: entry.name, rank };
|
|
110
|
+
if (isPreferredReadme(candidate, readme))
|
|
111
|
+
readme = candidate;
|
|
112
|
+
}
|
|
113
|
+
if (readme == null)
|
|
78
114
|
return undefined;
|
|
79
115
|
let handle;
|
|
80
116
|
try {
|
|
81
|
-
handle = await fs.promises.open(path.join(projectDir,
|
|
117
|
+
handle = await fs.promises.open(path.join(projectDir, readme.fileName), README_READ_FLAGS);
|
|
82
118
|
return await handle.readFile('utf8');
|
|
83
119
|
}
|
|
84
120
|
catch (err) {
|
|
@@ -95,7 +131,7 @@ function combineConverters(...converters) {
|
|
|
95
131
|
return async (depName, depSpec, context) => {
|
|
96
132
|
let bareSpecifier = depSpec;
|
|
97
133
|
for (const converter of converters) {
|
|
98
|
-
// eslint-disable-next-line no-await-in-loop
|
|
134
|
+
// eslint-disable-next-line no-await-in-loop -- each converter receives the specifier returned by the previous one
|
|
99
135
|
bareSpecifier = await converter(depName, bareSpecifier, context);
|
|
100
136
|
}
|
|
101
137
|
return bareSpecifier;
|
|
@@ -33,11 +33,11 @@ export function overridePublishConfig(publishManifest) {
|
|
|
33
33
|
if (!publishManifest.publishConfig)
|
|
34
34
|
return;
|
|
35
35
|
const publishConfig = { ...publishManifest.publishConfig };
|
|
36
|
+
const manifestFields = publishManifest;
|
|
36
37
|
for (const key in publishConfig) {
|
|
37
38
|
if (!PUBLISH_CONFIG_WHITELIST.has(key))
|
|
38
39
|
continue;
|
|
39
|
-
|
|
40
|
-
publishManifest[key] = value; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
40
|
+
manifestFields[key] = publishConfig[key];
|
|
41
41
|
delete publishConfig[key];
|
|
42
42
|
}
|
|
43
43
|
if (isEmpty(publishConfig)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/releasing.exportable-manifest",
|
|
3
|
-
"version": "1100.3.
|
|
3
|
+
"version": "1100.3.5",
|
|
4
4
|
"description": "Creates an exportable manifest",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"@pnpm/catalogs.config": "1100.0.9",
|
|
43
43
|
"@pnpm/catalogs.types": "1100.0.1",
|
|
44
44
|
"@pnpm/hooks.pnpmfile": "1100.0.34",
|
|
45
|
-
"@pnpm/prepare": "1100.0.
|
|
46
|
-
"@pnpm/releasing.exportable-manifest": "1100.3.
|
|
45
|
+
"@pnpm/prepare": "1100.0.34",
|
|
46
|
+
"@pnpm/releasing.exportable-manifest": "1100.3.5",
|
|
47
47
|
"@types/cross-spawn": "^6.0.6",
|
|
48
48
|
"@types/ramda": "0.32.0",
|
|
49
49
|
"cross-spawn": "^7.0.6",
|