@pnpm/releasing.exportable-manifest 1100.3.5 → 1100.3.6
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 +7 -0
- package/lib/index.d.ts +3 -28
- package/lib/index.js +86 -191
- package/lib/jsrProtocol.d.ts +1 -0
- package/lib/jsrProtocol.js +16 -0
- package/lib/readme.d.ts +25 -0
- package/lib/readme.js +70 -0
- package/lib/transform/bin.d.ts +0 -1
- package/lib/transform/bin.js +0 -1
- package/lib/workspaceLookup.d.ts +7 -0
- package/lib/workspaceLookup.js +59 -0
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -1,35 +1,11 @@
|
|
|
1
1
|
import type { Catalogs } from '@pnpm/catalogs.types';
|
|
2
2
|
import type { Hooks } from '@pnpm/hooks.pnpmfile';
|
|
3
3
|
import type { ProjectManifest } from '@pnpm/types';
|
|
4
|
+
import { getReadmeRank, isPreferredReadme, type ReadmeCandidate, type ReadmeRank, readReadmeFile } from './readme.js';
|
|
4
5
|
import { type ExportedManifest } from './transform/index.js';
|
|
6
|
+
import { type WorkspacePackageContainer, type WorkspacePackageLookup } from './workspaceLookup.js';
|
|
5
7
|
export { type ExportedManifest };
|
|
6
|
-
|
|
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;
|
|
29
|
-
export type WorkspacePackageLookup = Array<{
|
|
30
|
-
manifest: ProjectManifest;
|
|
31
|
-
rootDir?: string;
|
|
32
|
-
}> | Map<string, unknown> | Record<string, unknown> | ((depName: string) => ProjectManifest | undefined);
|
|
8
|
+
export { getReadmeRank, isPreferredReadme, type ReadmeCandidate, type ReadmeRank, readReadmeFile, type WorkspacePackageContainer, type WorkspacePackageLookup, };
|
|
33
9
|
export interface MakePublishManifestOptions {
|
|
34
10
|
catalogs: Catalogs;
|
|
35
11
|
hooks?: Hooks;
|
|
@@ -39,7 +15,6 @@ export interface MakePublishManifestOptions {
|
|
|
39
15
|
workspacePackages?: WorkspacePackageLookup;
|
|
40
16
|
}
|
|
41
17
|
export declare function createExportableManifest(dir: string, originalManifest: ProjectManifest, opts: MakePublishManifestOptions): Promise<ExportedManifest>;
|
|
42
|
-
export declare function readReadmeFile(projectDir: string): Promise<string | undefined>;
|
|
43
18
|
export type PublishDependencyConverter = (depName: string, depSpec: string, context: PublishDependencyConverterContext) => Promise<string> | string;
|
|
44
19
|
export interface PublishDependencyConverterContext {
|
|
45
20
|
dir: string;
|
package/lib/index.js
CHANGED
|
@@ -1,43 +1,16 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { resolveFromCatalog } from '@pnpm/catalogs.resolver';
|
|
4
|
-
import {
|
|
5
|
-
import { parseJsrSpecifier } from '@pnpm/resolving.jsr-specifier-parser';
|
|
3
|
+
import { PnpmError } from '@pnpm/error';
|
|
6
4
|
import { tryReadProjectManifest } from '@pnpm/workspace.project-manifest-reader';
|
|
7
5
|
import { WorkspaceSpec } from '@pnpm/workspace.spec-parser';
|
|
8
6
|
import { clone, omit } from 'ramda';
|
|
7
|
+
import { replaceJsrProtocol } from './jsrProtocol.js';
|
|
9
8
|
import { overridePublishConfig } from './overridePublishConfig.js';
|
|
9
|
+
import { getReadmeRank, isPreferredReadme, readReadmeFile, } from './readme.js';
|
|
10
10
|
import { transform } from './transform/index.js';
|
|
11
|
+
import { buildWorkspaceManifestGetter, } from './workspaceLookup.js';
|
|
11
12
|
export {};
|
|
12
|
-
|
|
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
|
-
}
|
|
13
|
+
export { getReadmeRank, isPreferredReadme, readReadmeFile, };
|
|
41
14
|
const PREPUBLISH_SCRIPTS = [
|
|
42
15
|
'prepublishOnly',
|
|
43
16
|
'prepack',
|
|
@@ -47,24 +20,44 @@ const PREPUBLISH_SCRIPTS = [
|
|
|
47
20
|
'postpublish',
|
|
48
21
|
];
|
|
49
22
|
export async function createExportableManifest(dir, originalManifest, opts) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
23
|
+
const publishManifest = initPublishManifest(originalManifest, opts.skipManifestObfuscation);
|
|
24
|
+
const getWorkspaceManifest = buildWorkspaceManifestGetter(opts.workspacePackages);
|
|
25
|
+
await convertManifestDependencies({
|
|
26
|
+
catalogs: opts.catalogs,
|
|
27
|
+
dir,
|
|
28
|
+
getWorkspaceManifest,
|
|
29
|
+
modulesDir: opts.modulesDir,
|
|
30
|
+
originalManifest,
|
|
31
|
+
publishManifest,
|
|
32
|
+
});
|
|
33
|
+
overridePublishConfig(publishManifest);
|
|
34
|
+
if (publishManifest.readme == null && opts.embedReadme) {
|
|
35
|
+
const readme = await readReadmeFile(dir);
|
|
36
|
+
if (readme != null) {
|
|
37
|
+
publishManifest.readme = readme;
|
|
58
38
|
}
|
|
59
39
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
const finalManifest = await applyPublishHooks(publishManifest, dir, opts.hooks?.beforePacking);
|
|
41
|
+
return transform(finalManifest);
|
|
42
|
+
}
|
|
43
|
+
function initPublishManifest(originalManifest, skipObfuscation) {
|
|
44
|
+
if (skipObfuscation) {
|
|
45
|
+
return omit(['pnpm'], clone(originalManifest));
|
|
46
|
+
}
|
|
47
|
+
const publishManifest = omit(['scripts', 'packageManager', 'pnpm'], originalManifest);
|
|
48
|
+
if (originalManifest.scripts != null) {
|
|
49
|
+
publishManifest.scripts = omit(PREPUBLISH_SCRIPTS, originalManifest.scripts);
|
|
50
|
+
}
|
|
51
|
+
return publishManifest;
|
|
52
|
+
}
|
|
53
|
+
async function convertManifestDependencies(params) {
|
|
54
|
+
const { catalogs, dir, getWorkspaceManifest, modulesDir, originalManifest, publishManifest } = params;
|
|
55
|
+
const replaceCatalogProtocol = resolveCatalogProtocol.bind(null, resolveFromCatalog.bind(null, catalogs));
|
|
63
56
|
const convertDependencyForPublish = combineConverters(replaceCatalogProtocol, replaceWorkspaceProtocol, replaceJsrProtocol);
|
|
64
57
|
await Promise.all(['dependencies', 'devDependencies', 'optionalDependencies'].map(async (depsField) => {
|
|
65
58
|
const deps = await makePublishDependencies(dir, originalManifest[depsField], {
|
|
66
|
-
modulesDir: opts?.modulesDir,
|
|
67
59
|
convertDependencyForPublish,
|
|
60
|
+
modulesDir,
|
|
68
61
|
workspacePackages: getWorkspaceManifest,
|
|
69
62
|
});
|
|
70
63
|
if (deps != null) {
|
|
@@ -75,57 +68,19 @@ export async function createExportableManifest(dir, originalManifest, opts) {
|
|
|
75
68
|
if (peerDependencies) {
|
|
76
69
|
const convertPeersForPublish = combineConverters(replaceCatalogProtocol, replaceWorkspaceProtocolPeerDependency, replaceJsrProtocol);
|
|
77
70
|
publishManifest.peerDependencies = await makePublishDependencies(dir, peerDependencies, {
|
|
78
|
-
modulesDir: opts?.modulesDir,
|
|
79
71
|
convertDependencyForPublish: convertPeersForPublish,
|
|
72
|
+
modulesDir,
|
|
80
73
|
workspacePackages: getWorkspaceManifest,
|
|
81
74
|
});
|
|
82
75
|
}
|
|
83
|
-
overridePublishConfig(publishManifest);
|
|
84
|
-
if (publishManifest.readme == null && opts?.embedReadme) {
|
|
85
|
-
const readme = await readReadmeFile(dir);
|
|
86
|
-
if (readme != null) {
|
|
87
|
-
publishManifest.readme = readme;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
for (const hook of opts?.hooks?.beforePacking ?? []) {
|
|
91
|
-
// eslint-disable-next-line no-await-in-loop -- each hook receives the manifest returned by the previous one
|
|
92
|
-
publishManifest = await hook(publishManifest, dir) ?? publishManifest;
|
|
93
|
-
}
|
|
94
|
-
return transform(publishManifest);
|
|
95
76
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
const README_READ_FLAGS = fs.constants.O_RDONLY | (process.platform === 'win32' ? 0 : fs.constants.O_NOFOLLOW);
|
|
102
|
-
export async function readReadmeFile(projectDir) {
|
|
103
|
-
const entries = await fs.promises.readdir(projectDir, { withFileTypes: true });
|
|
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)
|
|
114
|
-
return undefined;
|
|
115
|
-
let handle;
|
|
116
|
-
try {
|
|
117
|
-
handle = await fs.promises.open(path.join(projectDir, readme.fileName), README_READ_FLAGS);
|
|
118
|
-
return await handle.readFile('utf8');
|
|
119
|
-
}
|
|
120
|
-
catch (err) {
|
|
121
|
-
// ELOOP: the entry is a symlink after all (a concurrent swap) — skip it, as the isFile() check intended.
|
|
122
|
-
if (isError(err) && 'code' in err && err.code === 'ELOOP')
|
|
123
|
-
return undefined;
|
|
124
|
-
throw err;
|
|
125
|
-
}
|
|
126
|
-
finally {
|
|
127
|
-
await handle?.close();
|
|
77
|
+
async function applyPublishHooks(manifest, dir, hooks) {
|
|
78
|
+
let result = manifest;
|
|
79
|
+
for (const hook of hooks ?? []) {
|
|
80
|
+
// eslint-disable-next-line no-await-in-loop -- each hook receives the manifest returned by the previous one
|
|
81
|
+
result = await hook(result, dir) ?? result;
|
|
128
82
|
}
|
|
83
|
+
return result;
|
|
129
84
|
}
|
|
130
85
|
function combineConverters(...converters) {
|
|
131
86
|
return async (depName, depSpec, context) => {
|
|
@@ -143,60 +98,6 @@ async function makePublishDependencies(dir, dependencies, { modulesDir, convertD
|
|
|
143
98
|
const publishDependencies = await Promise.all(Object.entries(dependencies).map(async ([depName, depSpec]) => [depName, await convertDependencyForPublish(depName, depSpec, { dir, modulesDir, workspacePackages })]));
|
|
144
99
|
return Object.fromEntries(publishDependencies);
|
|
145
100
|
}
|
|
146
|
-
function extractManifest(val) {
|
|
147
|
-
if (!val || typeof val !== 'object')
|
|
148
|
-
return undefined;
|
|
149
|
-
if ('manifest' in val && val.manifest && typeof val.manifest === 'object') {
|
|
150
|
-
return val.manifest;
|
|
151
|
-
}
|
|
152
|
-
if ('package' in val && val.package && typeof val.package === 'object' && 'manifest' in val.package && val.package.manifest && typeof val.package.manifest === 'object') {
|
|
153
|
-
return val.package.manifest;
|
|
154
|
-
}
|
|
155
|
-
if ('name' in val && typeof val.name === 'string') {
|
|
156
|
-
return val;
|
|
157
|
-
}
|
|
158
|
-
return undefined;
|
|
159
|
-
}
|
|
160
|
-
function buildWorkspaceManifestGetter(workspacePackages) {
|
|
161
|
-
if (!workspacePackages)
|
|
162
|
-
return undefined;
|
|
163
|
-
if (typeof workspacePackages === 'function') {
|
|
164
|
-
return workspacePackages;
|
|
165
|
-
}
|
|
166
|
-
const byName = new Map();
|
|
167
|
-
const addManifest = (val) => {
|
|
168
|
-
const manifest = extractManifest(val);
|
|
169
|
-
if (!manifest?.name)
|
|
170
|
-
return;
|
|
171
|
-
const existing = byName.get(manifest.name);
|
|
172
|
-
if (!existing || (!existing.version && manifest.version)) {
|
|
173
|
-
byName.set(manifest.name, manifest);
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
if (Array.isArray(workspacePackages)) {
|
|
177
|
-
for (const pkg of workspacePackages) {
|
|
178
|
-
addManifest(pkg);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
else if (workspacePackages instanceof Map) {
|
|
182
|
-
for (const val of workspacePackages.values()) {
|
|
183
|
-
if (val instanceof Map) {
|
|
184
|
-
for (const subVal of val.values()) {
|
|
185
|
-
addManifest(subVal);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
addManifest(val);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else if (typeof workspacePackages === 'object') {
|
|
194
|
-
for (const val of Object.values(workspacePackages)) {
|
|
195
|
-
addManifest(val);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return (depName) => byName.get(depName);
|
|
199
|
-
}
|
|
200
101
|
async function readAndCheckManifest(depName, dependencyDir, getWorkspaceManifest, targetPkgName) {
|
|
201
102
|
const { manifest: dirManifest } = await tryReadProjectManifest(dependencyDir);
|
|
202
103
|
if (dirManifest?.name && dirManifest?.version) {
|
|
@@ -207,6 +108,9 @@ async function readAndCheckManifest(depName, dependencyDir, getWorkspaceManifest
|
|
|
207
108
|
if (workspaceManifest?.name && workspaceManifest?.version) {
|
|
208
109
|
return workspaceManifest;
|
|
209
110
|
}
|
|
111
|
+
return handleMissingOrInvalidManifest(depName, dirManifest ?? undefined, workspaceManifest);
|
|
112
|
+
}
|
|
113
|
+
function handleMissingOrInvalidManifest(depName, dirManifest, workspaceManifest) {
|
|
210
114
|
const found = dirManifest?.name || dirManifest?.version
|
|
211
115
|
? dirManifest
|
|
212
116
|
: workspaceManifest?.name || workspaceManifest?.version
|
|
@@ -231,36 +135,39 @@ function resolveCatalogProtocol(catalogResolver, alias, bareSpecifier) {
|
|
|
231
135
|
case 'misconfiguration': throw result.error;
|
|
232
136
|
}
|
|
233
137
|
}
|
|
234
|
-
async function replaceWorkspaceProtocol(depName, depSpec,
|
|
138
|
+
async function replaceWorkspaceProtocol(depName, depSpec, context) {
|
|
235
139
|
if (!depSpec.startsWith('workspace:')) {
|
|
236
140
|
return depSpec;
|
|
237
141
|
}
|
|
238
142
|
// Dependencies with bare "*", "^", "~" versions, or no version (workspace:)
|
|
239
143
|
const versionAliasSpecParts = /^workspace:(?:(.+)@)?([\^~*])?$/.exec(depSpec);
|
|
240
144
|
if (versionAliasSpecParts != null) {
|
|
241
|
-
|
|
242
|
-
const targetPkgName = versionAliasSpecParts[1];
|
|
243
|
-
const manifest = await readAndCheckManifest(depName, path.join(modulesDir, depName), workspacePackages, targetPkgName);
|
|
244
|
-
const specifierSuffix = versionAliasSpecParts[2];
|
|
245
|
-
const semverRangeToken = specifierSuffix === '^' || specifierSuffix === '~' ? specifierSuffix : '';
|
|
246
|
-
if (depName !== manifest.name) {
|
|
247
|
-
return `npm:${manifest.name}@${semverRangeToken}${manifest.version}`;
|
|
248
|
-
}
|
|
249
|
-
return `${semverRangeToken}${manifest.version}`;
|
|
145
|
+
return resolveVersionAliasWorkspaceSpec(depName, versionAliasSpecParts, context);
|
|
250
146
|
}
|
|
251
147
|
if (depSpec.startsWith('workspace:./') || depSpec.startsWith('workspace:../')) {
|
|
252
|
-
|
|
253
|
-
if (manifest.name === depName)
|
|
254
|
-
return `${manifest.version}`;
|
|
255
|
-
return `npm:${manifest.name}@${manifest.version}`;
|
|
256
|
-
}
|
|
257
|
-
depSpec = depSpec.slice(10);
|
|
258
|
-
if (depSpec.includes('@')) {
|
|
259
|
-
return `npm:${depSpec}`;
|
|
148
|
+
return resolveRelativeWorkspaceSpec(depName, depSpec.slice(10), context);
|
|
260
149
|
}
|
|
261
|
-
|
|
150
|
+
const spec = depSpec.slice(10);
|
|
151
|
+
return spec.includes('@') ? `npm:${spec}` : spec;
|
|
262
152
|
}
|
|
263
|
-
async function
|
|
153
|
+
async function resolveVersionAliasWorkspaceSpec(depName, parts, context) {
|
|
154
|
+
const modulesDir = context.modulesDir ?? path.join(context.dir, 'node_modules');
|
|
155
|
+
const targetPkgName = parts[1];
|
|
156
|
+
const manifest = await readAndCheckManifest(depName, path.join(modulesDir, depName), context.workspacePackages, targetPkgName);
|
|
157
|
+
const specifierSuffix = parts[2];
|
|
158
|
+
const semverRangeToken = specifierSuffix === '^' || specifierSuffix === '~' ? specifierSuffix : '';
|
|
159
|
+
if (depName !== manifest.name) {
|
|
160
|
+
return `npm:${manifest.name}@${semverRangeToken}${manifest.version}`;
|
|
161
|
+
}
|
|
162
|
+
return `${semverRangeToken}${manifest.version}`;
|
|
163
|
+
}
|
|
164
|
+
async function resolveRelativeWorkspaceSpec(depName, relPath, context) {
|
|
165
|
+
const manifest = await readAndCheckManifest(depName, path.join(context.dir, relPath), context.workspacePackages);
|
|
166
|
+
if (manifest.name === depName)
|
|
167
|
+
return `${manifest.version}`;
|
|
168
|
+
return `npm:${manifest.name}@${manifest.version}`;
|
|
169
|
+
}
|
|
170
|
+
async function replaceWorkspaceProtocolPeerDependency(depName, depSpec, context) {
|
|
264
171
|
if (!depSpec.includes('workspace:')) {
|
|
265
172
|
return depSpec;
|
|
266
173
|
}
|
|
@@ -272,35 +179,23 @@ async function replaceWorkspaceProtocolPeerDependency(depName, depSpec, { dir, m
|
|
|
272
179
|
return `npm:${workspaceSpec.alias}@${version}`;
|
|
273
180
|
}
|
|
274
181
|
if (workspaceSpec?.version.startsWith('./') || workspaceSpec?.version.startsWith('../')) {
|
|
275
|
-
return replaceWorkspaceProtocol(depName, depSpec,
|
|
276
|
-
}
|
|
277
|
-
// Dependencies with bare "*", "^", "~",">=",">","<=", "<", version
|
|
278
|
-
const workspaceSemverRegex = /workspace:([\^~*]|>=|>|<=|<)?((\d+|[xX*])(\.(\d+|[xX*])){0,2})?/;
|
|
279
|
-
const versionAliasSpecParts = workspaceSemverRegex.exec(depSpec);
|
|
280
|
-
if (versionAliasSpecParts != null) {
|
|
281
|
-
const [, semverRangGroup = '', version] = versionAliasSpecParts;
|
|
282
|
-
if (version) {
|
|
283
|
-
return depSpec.replace('workspace:', '');
|
|
284
|
-
}
|
|
285
|
-
modulesDir = modulesDir ?? path.join(dir, 'node_modules');
|
|
286
|
-
const manifest = await readAndCheckManifest(depName, path.join(modulesDir, depName), workspacePackages);
|
|
287
|
-
const semverRangeToken = semverRangGroup !== '*' ? semverRangGroup : '';
|
|
288
|
-
return depSpec.replace(workspaceSemverRegex, `${semverRangeToken}${manifest.version}`);
|
|
289
|
-
}
|
|
290
|
-
return depSpec.replace('workspace:', '');
|
|
291
|
-
}
|
|
292
|
-
async function replaceJsrProtocol(depName, depSpec) {
|
|
293
|
-
const spec = parseJsrSpecifier(depSpec, depName);
|
|
294
|
-
if (spec == null) {
|
|
295
|
-
return depSpec;
|
|
182
|
+
return replaceWorkspaceProtocol(depName, depSpec, context);
|
|
296
183
|
}
|
|
297
|
-
return
|
|
184
|
+
return resolvePeerWorkspaceSemver(depName, depSpec, context);
|
|
298
185
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
186
|
+
const WORKSPACE_SEMVER_REGEX = /workspace:([\^~*]|>=|>|<=|<)?((\d+|[xX*])(\.(\d+|[xX*])){0,2})?/;
|
|
187
|
+
async function resolvePeerWorkspaceSemver(depName, depSpec, context) {
|
|
188
|
+
const versionAliasSpecParts = WORKSPACE_SEMVER_REGEX.exec(depSpec);
|
|
189
|
+
if (versionAliasSpecParts == null) {
|
|
190
|
+
return depSpec.replace('workspace:', '');
|
|
191
|
+
}
|
|
192
|
+
const [, semverRangeGroup = '', version] = versionAliasSpecParts;
|
|
193
|
+
if (version) {
|
|
194
|
+
return depSpec.replace('workspace:', '');
|
|
195
|
+
}
|
|
196
|
+
const modulesDir = context.modulesDir ?? path.join(context.dir, 'node_modules');
|
|
197
|
+
const manifest = await readAndCheckManifest(depName, path.join(modulesDir, depName), context.workspacePackages);
|
|
198
|
+
const semverRangeToken = semverRangeGroup !== '*' ? semverRangeGroup : '';
|
|
199
|
+
return depSpec.replace(WORKSPACE_SEMVER_REGEX, `${semverRangeToken}${manifest.version}`);
|
|
305
200
|
}
|
|
306
201
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function replaceJsrProtocol(depName: string, depSpec: string): Promise<string>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { parseJsrSpecifier } from '@pnpm/resolving.jsr-specifier-parser';
|
|
2
|
+
export async function replaceJsrProtocol(depName, depSpec) {
|
|
3
|
+
const spec = parseJsrSpecifier(depSpec, depName);
|
|
4
|
+
if (spec == null) {
|
|
5
|
+
return depSpec;
|
|
6
|
+
}
|
|
7
|
+
return createNpmAliasedSpecifier(spec.npmPkgName, spec.versionSelector);
|
|
8
|
+
}
|
|
9
|
+
function createNpmAliasedSpecifier(npmPkgName, versionSelector) {
|
|
10
|
+
const npmPkgSpecifier = `npm:${npmPkgName}`;
|
|
11
|
+
if (!versionSelector) {
|
|
12
|
+
return npmPkgSpecifier;
|
|
13
|
+
}
|
|
14
|
+
return `${npmPkgSpecifier}@${versionSelector}`;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=jsrProtocol.js.map
|
package/lib/readme.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
declare const README_RANK: {
|
|
2
|
+
readonly bare: 1;
|
|
3
|
+
readonly markdown: 2;
|
|
4
|
+
readonly readmeMd: 3;
|
|
5
|
+
};
|
|
6
|
+
export interface ReadmeCandidate {
|
|
7
|
+
fileName: string;
|
|
8
|
+
rank: ReadmeRank;
|
|
9
|
+
}
|
|
10
|
+
/** How npm ranks a package-root file as the package's README. A higher rank wins. */
|
|
11
|
+
export type ReadmeRank = typeof README_RANK[keyof typeof README_RANK];
|
|
12
|
+
/**
|
|
13
|
+
* Rank a filename as a README candidate, or return `undefined` if npm would not use it as the
|
|
14
|
+
* package's README. npm matches the bare `README` case-sensitively and accepts `README.*` whose
|
|
15
|
+
* extension matches `/.m?a?r?k?d?o?w?n?$/i`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getReadmeRank(fileName: string): ReadmeRank | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Whether README `candidate` should replace the `current` selection. A higher rank wins. Equal
|
|
20
|
+
* ranks keep the lower filename, so the choice does not depend on directory or archive order, and
|
|
21
|
+
* an equal filename replaces, so the last duplicate archive entry wins as it would on extraction.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isPreferredReadme(candidate: ReadmeCandidate, current: ReadmeCandidate | undefined): boolean;
|
|
24
|
+
export declare function readReadmeFile(projectDir: string): Promise<string | undefined>;
|
|
25
|
+
export {};
|
package/lib/readme.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isError } from '@pnpm/error';
|
|
4
|
+
const README_RANK = {
|
|
5
|
+
bare: 1,
|
|
6
|
+
markdown: 2,
|
|
7
|
+
readmeMd: 3,
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Rank a filename as a README candidate, or return `undefined` if npm would not use it as the
|
|
11
|
+
* package's README. npm matches the bare `README` case-sensitively and accepts `README.*` whose
|
|
12
|
+
* extension matches `/.m?a?r?k?d?o?w?n?$/i`.
|
|
13
|
+
*/
|
|
14
|
+
export function getReadmeRank(fileName) {
|
|
15
|
+
if (/^readme\.md$/i.test(fileName))
|
|
16
|
+
return README_RANK.readmeMd;
|
|
17
|
+
if (fileName === 'README')
|
|
18
|
+
return README_RANK.bare;
|
|
19
|
+
if (/^readme\./i.test(fileName) && /\.m?a?r?k?d?o?w?n?$/i.test(fileName))
|
|
20
|
+
return README_RANK.markdown;
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
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 function isPreferredReadme(candidate, current) {
|
|
29
|
+
if (current == null || candidate.rank > current.rank)
|
|
30
|
+
return true;
|
|
31
|
+
return candidate.rank === current.rank && candidate.fileName <= current.fileName;
|
|
32
|
+
}
|
|
33
|
+
// `O_NOFOLLOW` makes the open itself refuse a symlink at the final path component, closing the
|
|
34
|
+
// TOCTOU window between the `readdir` type check and the read: a symlink swapped in for the
|
|
35
|
+
// selected README after the check can't redirect the read outside the project and leak its target
|
|
36
|
+
// into the published manifest. Windows lacks the flag (and requires privileges to create symlinks),
|
|
37
|
+
// so it falls back to a plain read.
|
|
38
|
+
const README_READ_FLAGS = fs.constants.O_RDONLY | (process.platform === 'win32' ? 0 : fs.constants.O_NOFOLLOW);
|
|
39
|
+
export async function readReadmeFile(projectDir) {
|
|
40
|
+
const entries = await fs.promises.readdir(projectDir, { withFileTypes: true });
|
|
41
|
+
let readme;
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const rank = getReadmeRank(entry.name);
|
|
44
|
+
if (rank == null || !entry.isFile())
|
|
45
|
+
continue;
|
|
46
|
+
const candidate = { fileName: entry.name, rank };
|
|
47
|
+
if (isPreferredReadme(candidate, readme))
|
|
48
|
+
readme = candidate;
|
|
49
|
+
}
|
|
50
|
+
if (readme == null)
|
|
51
|
+
return undefined;
|
|
52
|
+
return readReadmeContent(projectDir, readme.fileName);
|
|
53
|
+
}
|
|
54
|
+
async function readReadmeContent(projectDir, fileName) {
|
|
55
|
+
let handle;
|
|
56
|
+
try {
|
|
57
|
+
handle = await fs.promises.open(path.join(projectDir, fileName), README_READ_FLAGS);
|
|
58
|
+
return await handle.readFile('utf8');
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
// ELOOP: the entry is a symlink after all (a concurrent swap) — skip it, as the isFile() check intended.
|
|
62
|
+
if (isError(err) && 'code' in err && err.code === 'ELOOP')
|
|
63
|
+
return undefined;
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await handle?.close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=readme.js.map
|
package/lib/transform/bin.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ type Output<Manifest extends Input> = Omit<Manifest, 'bin'> & Pick<ExportedManif
|
|
|
6
6
|
export declare function transformBin<Manifest extends Input>(manifest: Manifest): Output<Manifest>;
|
|
7
7
|
/**
|
|
8
8
|
* The property `"bin"` of a `package.json` could be either an object or a string.
|
|
9
|
-
* This function normalizes either forms into an object.
|
|
10
9
|
*/
|
|
11
10
|
export declare function normalizeBinObject(pkgName: string, bin: string | Record<string, string>): Record<string, string>;
|
|
12
11
|
export declare class InvalidScopedPackageNameError extends PnpmError {
|
package/lib/transform/bin.js
CHANGED
|
@@ -10,7 +10,6 @@ export function transformBin(manifest) {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* The property `"bin"` of a `package.json` could be either an object or a string.
|
|
13
|
-
* This function normalizes either forms into an object.
|
|
14
13
|
*/
|
|
15
14
|
export function normalizeBinObject(pkgName, bin) {
|
|
16
15
|
if (typeof bin === 'object')
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProjectManifest } from '@pnpm/types';
|
|
2
|
+
export type WorkspacePackageContainer = Array<{
|
|
3
|
+
manifest: ProjectManifest;
|
|
4
|
+
rootDir?: string;
|
|
5
|
+
}> | Map<string, unknown> | Record<string, unknown>;
|
|
6
|
+
export type WorkspacePackageLookup = WorkspacePackageContainer | ((depName: string) => ProjectManifest | undefined);
|
|
7
|
+
export declare function buildWorkspaceManifestGetter(workspacePackages?: WorkspacePackageLookup): ((depName: string) => ProjectManifest | undefined) | undefined;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function buildWorkspaceManifestGetter(workspacePackages) {
|
|
2
|
+
if (!workspacePackages)
|
|
3
|
+
return undefined;
|
|
4
|
+
if (typeof workspacePackages === 'function') {
|
|
5
|
+
return workspacePackages;
|
|
6
|
+
}
|
|
7
|
+
const byName = new Map();
|
|
8
|
+
collectWorkspaceManifests(workspacePackages, byName);
|
|
9
|
+
return (depName) => byName.get(depName);
|
|
10
|
+
}
|
|
11
|
+
function collectWorkspaceManifests(workspacePackages, byName) {
|
|
12
|
+
const add = (val) => addManifestEntry(val, byName);
|
|
13
|
+
if (Array.isArray(workspacePackages)) {
|
|
14
|
+
for (const pkg of workspacePackages)
|
|
15
|
+
add(pkg);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (workspacePackages instanceof Map) {
|
|
19
|
+
collectFromMap(workspacePackages, add);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
for (const val of Object.values(workspacePackages))
|
|
23
|
+
add(val);
|
|
24
|
+
}
|
|
25
|
+
function collectFromMap(map, add) {
|
|
26
|
+
for (const val of map.values()) {
|
|
27
|
+
if (val instanceof Map) {
|
|
28
|
+
for (const subVal of val.values())
|
|
29
|
+
add(subVal);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
add(val);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function addManifestEntry(val, byName) {
|
|
37
|
+
const manifest = extractManifest(val);
|
|
38
|
+
if (!manifest?.name)
|
|
39
|
+
return;
|
|
40
|
+
const existing = byName.get(manifest.name);
|
|
41
|
+
if (!existing || (!existing.version && manifest.version)) {
|
|
42
|
+
byName.set(manifest.name, manifest);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function extractManifest(val) {
|
|
46
|
+
if (!val || typeof val !== 'object')
|
|
47
|
+
return undefined;
|
|
48
|
+
if ('manifest' in val && val.manifest && typeof val.manifest === 'object') {
|
|
49
|
+
return val.manifest;
|
|
50
|
+
}
|
|
51
|
+
if ('package' in val && val.package && typeof val.package === 'object' && 'manifest' in val.package && val.package.manifest && typeof val.package.manifest === 'object') {
|
|
52
|
+
return val.package.manifest;
|
|
53
|
+
}
|
|
54
|
+
if ('name' in val && typeof val.name === 'string') {
|
|
55
|
+
return val;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=workspaceLookup.js.map
|
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.6",
|
|
4
4
|
"description": "Creates an exportable manifest",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@pnpm/error": "1100.2.1",
|
|
34
34
|
"@pnpm/resolving.jsr-specifier-parser": "1100.0.9",
|
|
35
35
|
"@pnpm/types": "1102.1.1",
|
|
36
|
-
"@pnpm/workspace.project-manifest-reader": "1100.1.
|
|
36
|
+
"@pnpm/workspace.project-manifest-reader": "1100.1.2",
|
|
37
37
|
"@pnpm/workspace.spec-parser": "1100.0.1",
|
|
38
38
|
"ramda": "npm:@pnpm/ramda@0.28.1"
|
|
39
39
|
},
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"@jest/globals": "30.4.1",
|
|
42
42
|
"@pnpm/catalogs.config": "1100.0.9",
|
|
43
43
|
"@pnpm/catalogs.types": "1100.0.1",
|
|
44
|
-
"@pnpm/hooks.pnpmfile": "1100.0.
|
|
45
|
-
"@pnpm/prepare": "1100.0.
|
|
46
|
-
"@pnpm/releasing.exportable-manifest": "1100.3.
|
|
44
|
+
"@pnpm/hooks.pnpmfile": "1100.0.35",
|
|
45
|
+
"@pnpm/prepare": "1100.0.35",
|
|
46
|
+
"@pnpm/releasing.exportable-manifest": "1100.3.6",
|
|
47
47
|
"@types/cross-spawn": "^6.0.6",
|
|
48
48
|
"@types/ramda": "0.32.0",
|
|
49
49
|
"cross-spawn": "^7.0.6",
|