@pnpm/releasing.commands 1100.7.0 → 1100.7.1
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 +45 -0
- package/lib/change/index.d.ts +42 -0
- package/lib/change/index.js +248 -0
- package/lib/deploy/createDeployFiles.d.ts +26 -0
- package/lib/deploy/createDeployFiles.js +255 -0
- package/lib/deploy/deploy.d.ts +12 -0
- package/lib/deploy/deploy.js +398 -0
- package/lib/deploy/deployHook.d.ts +2 -0
- package/lib/deploy/deployHook.js +15 -0
- package/lib/deploy/index.d.ts +2 -0
- package/lib/deploy/index.js +3 -0
- package/lib/index.d.ts +7 -0
- package/lib/lane/index.d.ts +24 -0
- package/lib/lane/index.js +157 -0
- package/lib/pack-app/index.d.ts +2 -0
- package/lib/pack-app/index.js +3 -0
- package/lib/pack-app/packApp.d.ts +22 -0
- package/lib/pack-app/packApp.js +558 -0
- package/lib/publish/FailedToPublishError.d.ts +22 -0
- package/lib/publish/FailedToPublishError.js +40 -0
- package/lib/publish/batchPublish.d.ts +20 -0
- package/lib/publish/batchPublish.js +201 -0
- package/lib/publish/displayError.d.ts +1 -0
- package/lib/publish/displayError.js +23 -0
- package/lib/publish/executeTokenHelper.d.ts +4 -0
- package/lib/publish/executeTokenHelper.js +17 -0
- package/lib/publish/extractManifestFromPacked.d.ts +19 -0
- package/lib/publish/extractManifestFromPacked.js +110 -0
- package/lib/publish/index.d.ts +3 -0
- package/lib/publish/index.js +4 -0
- package/lib/publish/oidc/authToken.d.ts +73 -0
- package/lib/publish/oidc/authToken.js +97 -0
- package/lib/publish/oidc/idToken.d.ts +76 -0
- package/lib/publish/oidc/idToken.js +90 -0
- package/lib/publish/oidc/provenance.d.ts +73 -0
- package/lib/publish/oidc/provenance.js +91 -0
- package/lib/publish/otp.d.ts +38 -0
- package/lib/publish/otp.js +41 -0
- package/lib/publish/otpEnv.d.ts +7 -0
- package/lib/publish/otpEnv.js +5 -0
- package/lib/publish/pack.d.ts +34 -0
- package/lib/publish/pack.js +408 -0
- package/lib/publish/previousChangelog.d.ts +25 -0
- package/lib/publish/previousChangelog.js +194 -0
- package/lib/publish/publish.d.ts +40 -0
- package/lib/publish/publish.js +256 -0
- package/lib/publish/publishPackedPkg.d.ts +79 -0
- package/lib/publish/publishPackedPkg.js +298 -0
- package/lib/publish/recursivePublish.d.ts +18 -0
- package/lib/publish/recursivePublish.js +132 -0
- package/lib/publish/registryConfigKeys.d.ts +30 -0
- package/lib/publish/registryConfigKeys.js +50 -0
- package/lib/publish/utils/shared-context.d.ts +7 -0
- package/lib/publish/utils/shared-context.js +20 -0
- package/lib/resolveUnpublishedDirs.d.ts +10 -0
- package/lib/resolveUnpublishedDirs.js +14 -0
- package/lib/stage/approve.d.ts +2 -0
- package/lib/stage/approve.js +14 -0
- package/lib/stage/context.d.ts +14 -0
- package/lib/stage/context.js +25 -0
- package/lib/stage/download.d.ts +2 -0
- package/lib/stage/download.js +32 -0
- package/lib/stage/errors.d.ts +15 -0
- package/lib/stage/errors.js +17 -0
- package/lib/stage/help.d.ts +1 -0
- package/lib/stage/help.js +84 -0
- package/lib/stage/index.d.ts +13 -0
- package/lib/stage/index.js +57 -0
- package/lib/stage/list.d.ts +2 -0
- package/lib/stage/list.js +49 -0
- package/lib/stage/parsing.d.ts +6 -0
- package/lib/stage/parsing.js +27 -0
- package/lib/stage/publish.d.ts +7 -0
- package/lib/stage/publish.js +38 -0
- package/lib/stage/reject.d.ts +2 -0
- package/lib/stage/reject.js +16 -0
- package/lib/stage/rendering.d.ts +11 -0
- package/lib/stage/rendering.js +50 -0
- package/lib/stage/request.d.ts +29 -0
- package/lib/stage/request.js +110 -0
- package/lib/stage/types.d.ts +38 -0
- package/lib/stage/types.js +3 -0
- package/lib/stage/view.d.ts +2 -0
- package/lib/stage/view.js +14 -0
- package/lib/tarball/index.d.ts +2 -0
- package/lib/tarball/index.js +3 -0
- package/lib/tarball/publishSummary.d.ts +47 -0
- package/lib/tarball/publishSummary.js +36 -0
- package/lib/tarball/safeTarballFilename.d.ts +8 -0
- package/lib/tarball/safeTarballFilename.js +21 -0
- package/lib/tarball/summarizeTarball.d.ts +12 -0
- package/lib/tarball/summarizeTarball.js +84 -0
- package/lib/version/index.d.ts +35 -0
- package/lib/version/index.js +360 -0
- package/package.json +42 -42
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { PnpmError } from '@pnpm/error';
|
|
5
|
+
import { globalInfo, globalWarn } from '@pnpm/logger';
|
|
6
|
+
import { withOtpHandling } from '@pnpm/network.web-auth';
|
|
7
|
+
import { rimraf } from '@zkochan/rimraf';
|
|
8
|
+
import npmFetch from 'npm-registry-fetch';
|
|
9
|
+
import { realpathMissing } from 'realpath-missing';
|
|
10
|
+
import semver from 'semver';
|
|
11
|
+
import { temporaryDirectory } from 'tempy';
|
|
12
|
+
import { createPublishSummary } from '../tarball/publishSummary.js';
|
|
13
|
+
import * as pack from './pack.js';
|
|
14
|
+
import { runScriptsIfPresent } from './publish.js';
|
|
15
|
+
import { createPublishContext, createPublishOptions, findRegistryInfo, isPublishAccess, } from './publishPackedPkg.js';
|
|
16
|
+
export const BATCH_PUBLISH_ENDPOINT = '/-/pnpm/v1/publish';
|
|
17
|
+
/**
|
|
18
|
+
* Publish all {@link pkgs} by sending a single `PUT /-/pnpm/v1/publish` request per target
|
|
19
|
+
* registry, instead of one request per package. The endpoint is not part of the standard npm
|
|
20
|
+
* registry API — the registry has to implement it explicitly (pnpr does), so this whole code
|
|
21
|
+
* path is opt-in via the `--batch` flag.
|
|
22
|
+
*
|
|
23
|
+
* Every package is packed (and its `prepublishOnly`/`prepublish` scripts run) before anything
|
|
24
|
+
* is sent, so a package that fails to pack aborts the publish before any package reaches the
|
|
25
|
+
* registry. The `publish`/`postpublish` scripts run only after the requests succeeded.
|
|
26
|
+
*
|
|
27
|
+
* @param pkgs packages to publish, already filtered (no private packages, no already-published
|
|
28
|
+
* versions) and in dependency order.
|
|
29
|
+
*/
|
|
30
|
+
export async function batchPublishPackages(pkgs, opts) {
|
|
31
|
+
if (opts.stage) {
|
|
32
|
+
throw new PnpmError('BATCH_PUBLISH_NO_STAGE', 'Staged publishing cannot be combined with --batch');
|
|
33
|
+
}
|
|
34
|
+
if (opts.provenance) {
|
|
35
|
+
throw new PnpmError('BATCH_PUBLISH_NO_PROVENANCE', 'Provenance statements cannot be generated when publishing with --batch', {
|
|
36
|
+
hint: 'Provenance is bound to a single package, but --batch sends many packages in one request. Publish without --batch to attach provenance.',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const packedByRegistry = new Map();
|
|
40
|
+
const packedPkgs = [];
|
|
41
|
+
for (const project of pkgs) {
|
|
42
|
+
// eslint-disable-next-line no-await-in-loop
|
|
43
|
+
const packedPkg = await packPkgForBatch(project, opts);
|
|
44
|
+
const publishConfigRegistry = typeof packedPkg.publishedManifest.publishConfig?.registry === 'string'
|
|
45
|
+
? packedPkg.publishedManifest.publishConfig.registry
|
|
46
|
+
: undefined;
|
|
47
|
+
const { registry } = findRegistryInfo(packedPkg.publishedManifest, opts, publishConfigRegistry);
|
|
48
|
+
let group = packedByRegistry.get(registry);
|
|
49
|
+
if (!group) {
|
|
50
|
+
group = [];
|
|
51
|
+
packedByRegistry.set(registry, group);
|
|
52
|
+
}
|
|
53
|
+
group.push(packedPkg);
|
|
54
|
+
packedPkgs.push(packedPkg);
|
|
55
|
+
}
|
|
56
|
+
for (const [registry, group] of packedByRegistry.entries()) {
|
|
57
|
+
for (const { summary } of group) {
|
|
58
|
+
globalInfo(`📦 ${summary.id} → ${registry}`);
|
|
59
|
+
}
|
|
60
|
+
if (opts.dryRun) {
|
|
61
|
+
globalWarn(`Skip publishing ${group.length} package(s) to ${registry} (dry run)`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// eslint-disable-next-line no-await-in-loop
|
|
65
|
+
await multiPublishToRegistry(registry, group, opts);
|
|
66
|
+
globalInfo(`✅ Published ${group.length} package(s) to ${registry} in a single request`);
|
|
67
|
+
}
|
|
68
|
+
if (!opts.ignoreScripts) {
|
|
69
|
+
for (const { project } of packedPkgs) {
|
|
70
|
+
// eslint-disable-next-line no-await-in-loop
|
|
71
|
+
await runScriptsIfPresent(await lifecycleOpts(project.rootDir, opts), ['publish', 'postpublish'], project.manifest);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return packedPkgs.map(({ summary }) => summary);
|
|
75
|
+
}
|
|
76
|
+
async function packPkgForBatch(project, opts) {
|
|
77
|
+
if (!opts.ignoreScripts) {
|
|
78
|
+
await runScriptsIfPresent(await lifecycleOpts(project.rootDir, opts), ['prepublishOnly', 'prepublish'], project.manifest);
|
|
79
|
+
}
|
|
80
|
+
// The tarball is packed into a temporary directory and read into memory right away — the
|
|
81
|
+
// request body carries it base64-encoded, so nothing needs to stay on disk.
|
|
82
|
+
const packDestination = temporaryDirectory();
|
|
83
|
+
try {
|
|
84
|
+
const packResult = await pack.api({
|
|
85
|
+
...opts,
|
|
86
|
+
dir: project.rootDir,
|
|
87
|
+
packDestination,
|
|
88
|
+
dryRun: false,
|
|
89
|
+
});
|
|
90
|
+
const tarballData = await fs.readFile(packResult.tarballPath);
|
|
91
|
+
return {
|
|
92
|
+
project,
|
|
93
|
+
publishedManifest: packResult.publishedManifest,
|
|
94
|
+
tarballData,
|
|
95
|
+
summary: createPublishSummary(packResult, tarballData),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await rimraf(packDestination);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function lifecycleOpts(pkgRoot, opts) {
|
|
103
|
+
return {
|
|
104
|
+
depPath: pkgRoot,
|
|
105
|
+
extraBinPaths: opts.extraBinPaths,
|
|
106
|
+
extraEnv: opts.extraEnv,
|
|
107
|
+
pkgRoot,
|
|
108
|
+
rootModulesDir: await realpathMissing(path.join(pkgRoot, 'node_modules')),
|
|
109
|
+
stdio: 'inherit',
|
|
110
|
+
unsafePerm: true, // when running scripts explicitly, assume that they're trusted.
|
|
111
|
+
userAgent: opts.userAgent,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function multiPublishToRegistry(registry, group, opts) {
|
|
115
|
+
// A package-scoped OIDC token cannot authorize a request that publishes many packages, so the
|
|
116
|
+
// OIDC exchange is skipped and only statically configured credentials are used.
|
|
117
|
+
const publishOptions = await createPublishOptions(group[0].publishedManifest, opts, { oidc: false });
|
|
118
|
+
const body = {
|
|
119
|
+
packages: group.map((packedPkg) => createPublishDocument(packedPkg, registry, opts)),
|
|
120
|
+
};
|
|
121
|
+
const fetchOptions = {
|
|
122
|
+
method: 'GET',
|
|
123
|
+
retry: {
|
|
124
|
+
factor: opts.fetchRetryFactor,
|
|
125
|
+
maxTimeout: opts.fetchRetryMaxtimeout,
|
|
126
|
+
minTimeout: opts.fetchRetryMintimeout,
|
|
127
|
+
retries: opts.fetchRetries,
|
|
128
|
+
},
|
|
129
|
+
timeout: opts.fetchTimeout,
|
|
130
|
+
};
|
|
131
|
+
try {
|
|
132
|
+
await withOtpHandling({
|
|
133
|
+
context: createPublishContext(opts),
|
|
134
|
+
fetchOptions,
|
|
135
|
+
operation: async (otp) => npmFetch(BATCH_PUBLISH_ENDPOINT, {
|
|
136
|
+
...publishOptions,
|
|
137
|
+
access: publishOptions.access ?? undefined,
|
|
138
|
+
otp: otp ?? publishOptions.otp,
|
|
139
|
+
method: 'PUT',
|
|
140
|
+
body,
|
|
141
|
+
ignoreBody: true,
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
const statusCode = err.statusCode;
|
|
147
|
+
if (statusCode === 404 || statusCode === 405) {
|
|
148
|
+
throw new PnpmError('BATCH_PUBLISH_UNSUPPORTED', `The registry at ${registry} does not support publishing multiple packages in a single request`, {
|
|
149
|
+
hint: `Retry without the --batch flag, or publish to a registry that implements "PUT ${BATCH_PUBLISH_ENDPOINT}" (for example, pnpr).`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Build the npm publish document for one package — the same JSON body `libnpmpublish` would
|
|
157
|
+
* send as the whole `PUT /:pkg` request. The batch publish request body is an array of these,
|
|
158
|
+
* which lets the registry reuse its single-package publish pipeline per entry.
|
|
159
|
+
*/
|
|
160
|
+
function createPublishDocument({ publishedManifest, tarballData }, registry, opts) {
|
|
161
|
+
const name = publishedManifest.name;
|
|
162
|
+
const version = semver.clean(publishedManifest.version);
|
|
163
|
+
if (!version) {
|
|
164
|
+
throw new PnpmError('BAD_SEMVER', `Invalid semver: ${publishedManifest.version}`);
|
|
165
|
+
}
|
|
166
|
+
const publishConfigAccess = publishedManifest.publishConfig?.access;
|
|
167
|
+
const access = opts.access ?? (isPublishAccess(publishConfigAccess) ? publishConfigAccess : null);
|
|
168
|
+
if (!name.startsWith('@') && access === 'restricted') {
|
|
169
|
+
throw new PnpmError('UNSCOPED_RESTRICTED', `Can't restrict access to the unscoped package ${name}`);
|
|
170
|
+
}
|
|
171
|
+
const tarballName = `${name}-${version}.tgz`;
|
|
172
|
+
const manifest = {
|
|
173
|
+
...publishedManifest,
|
|
174
|
+
_id: `${name}@${version}`,
|
|
175
|
+
version,
|
|
176
|
+
_nodeVersion: process.versions.node,
|
|
177
|
+
dist: {
|
|
178
|
+
integrity: `sha512-${createHash('sha512').update(tarballData).digest('base64')}`,
|
|
179
|
+
shasum: createHash('sha1').update(tarballData).digest('hex'),
|
|
180
|
+
// libnpmpublish stores an http:// URL on purpose (clients fetch via HTTPS regardless when
|
|
181
|
+
// the registry is HTTPS); keep the wire format identical to a single-package publish.
|
|
182
|
+
tarball: new URL(`${name}/-/${tarballName}`, registry).href.replace(/^https:\/\//, 'http://'),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
_id: name,
|
|
187
|
+
name,
|
|
188
|
+
description: publishedManifest.description,
|
|
189
|
+
'dist-tags': { [opts.tag ?? 'latest']: version },
|
|
190
|
+
versions: { [version]: manifest },
|
|
191
|
+
access,
|
|
192
|
+
_attachments: {
|
|
193
|
+
[tarballName]: {
|
|
194
|
+
content_type: 'application/octet-stream',
|
|
195
|
+
data: tarballData.toString('base64'),
|
|
196
|
+
length: tarballData.length,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=batchPublish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function displayError(error: unknown): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function displayError(error) {
|
|
2
|
+
if (typeof error !== 'object' || !error)
|
|
3
|
+
return JSON.stringify(error);
|
|
4
|
+
let code;
|
|
5
|
+
let body;
|
|
6
|
+
if ('code' in error && typeof error.code === 'string') {
|
|
7
|
+
code = error.code;
|
|
8
|
+
}
|
|
9
|
+
else if ('name' in error && typeof error.name === 'string') {
|
|
10
|
+
code = error.name;
|
|
11
|
+
}
|
|
12
|
+
if ('message' in error && typeof error.message === 'string') {
|
|
13
|
+
body = error.message;
|
|
14
|
+
}
|
|
15
|
+
if (code && body)
|
|
16
|
+
return `${code}: ${body}`;
|
|
17
|
+
if (code)
|
|
18
|
+
return code;
|
|
19
|
+
if (body)
|
|
20
|
+
return body;
|
|
21
|
+
return JSON.stringify(error);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=displayError.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { sync as execa } from 'execa';
|
|
2
|
+
export function executeTokenHelper([cmd, ...args], opts) {
|
|
3
|
+
const execResult = execa(cmd, args, {
|
|
4
|
+
stdio: 'pipe',
|
|
5
|
+
});
|
|
6
|
+
const stderr = execResult.stderr?.toString() ?? '';
|
|
7
|
+
if (stderr.trim()) {
|
|
8
|
+
for (const line of stderr.trimEnd().split('\n')) {
|
|
9
|
+
opts.globalWarn(`(tokenHelper stderr) ${line}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const token = (execResult.stdout?.toString() ?? '').trim();
|
|
13
|
+
// If the token helper output includes an auth scheme prefix (e.g. "Bearer ..."),
|
|
14
|
+
// strip it since libnpmpublish adds the scheme itself.
|
|
15
|
+
return token.replace(/^Bearer\s+/i, '');
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=executeTokenHelper.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import type { ExportedManifest } from '@pnpm/releasing.exportable-manifest';
|
|
3
|
+
declare const TARBALL_SUFFIXES: readonly ['.tar.gz', '.tgz'];
|
|
4
|
+
export type TarballSuffix = typeof TARBALL_SUFFIXES[number];
|
|
5
|
+
export type TarballPath = `${string}${TarballSuffix}`;
|
|
6
|
+
export declare const isTarballPath: (path: string) => path is TarballPath;
|
|
7
|
+
export declare function extractManifestFromPacked<Output = ExportedManifest>(tarballPath: TarballPath): Promise<Output>;
|
|
8
|
+
/**
|
|
9
|
+
* Read the publish manifest from a pre-built tarball, filling in its `readme` from the tarball's
|
|
10
|
+
* root README file when the manifest doesn't already declare one. This mirrors the npm CLI, which
|
|
11
|
+
* reads the readme out of the tarball (via pacote's `fullReadJson`) so the registry gets it as
|
|
12
|
+
* metadata even though it isn't stored in the packed `package.json`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractPublishManifestFromPacked(tarballPath: TarballPath): Promise<ExportedManifest>;
|
|
15
|
+
export declare class PublishArchiveMissingManifestError extends PnpmError {
|
|
16
|
+
readonly tarballPath: string;
|
|
17
|
+
constructor(tarballPath: string);
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createGunzip } from 'node:zlib';
|
|
4
|
+
import { PnpmError } from '@pnpm/error';
|
|
5
|
+
import tar from 'tar-stream';
|
|
6
|
+
const TARBALL_SUFFIXES = ['.tar.gz', '.tgz'];
|
|
7
|
+
export const isTarballPath = (path) => TARBALL_SUFFIXES.some(suffix => path.endsWith(suffix));
|
|
8
|
+
export async function extractManifestFromPacked(tarballPath) {
|
|
9
|
+
const { manifest } = await extractEntriesFromPacked(tarballPath, false);
|
|
10
|
+
return JSON.parse(manifest);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Read the publish manifest from a pre-built tarball, filling in its `readme` from the tarball's
|
|
14
|
+
* root README file when the manifest doesn't already declare one. This mirrors the npm CLI, which
|
|
15
|
+
* reads the readme out of the tarball (via pacote's `fullReadJson`) so the registry gets it as
|
|
16
|
+
* metadata even though it isn't stored in the packed `package.json`.
|
|
17
|
+
*/
|
|
18
|
+
export async function extractPublishManifestFromPacked(tarballPath) {
|
|
19
|
+
const { manifest, readme } = await extractEntriesFromPacked(tarballPath, true);
|
|
20
|
+
const parsed = JSON.parse(manifest);
|
|
21
|
+
if (parsed.readme == null && readme != null) {
|
|
22
|
+
parsed.readme = readme;
|
|
23
|
+
}
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Scan the tarball for `package/package.json` and, when `wantReadme` is set, the root
|
|
28
|
+
* `README.md`. The manifest-only path (`wantReadme` false) resolves as soon as the manifest
|
|
29
|
+
* entry is read and stops decompressing the rest of the archive; the publish path scans on
|
|
30
|
+
* because a README can appear after the manifest.
|
|
31
|
+
*/
|
|
32
|
+
async function extractEntriesFromPacked(tarballPath, wantReadme) {
|
|
33
|
+
const extract = tar.extract();
|
|
34
|
+
const gunzip = createGunzip();
|
|
35
|
+
const tarballStream = fs.createReadStream(tarballPath);
|
|
36
|
+
let cleanedUp = false;
|
|
37
|
+
function cleanup() {
|
|
38
|
+
if (cleanedUp)
|
|
39
|
+
return;
|
|
40
|
+
cleanedUp = true;
|
|
41
|
+
extract.destroy();
|
|
42
|
+
gunzip.destroy();
|
|
43
|
+
tarballStream.destroy();
|
|
44
|
+
}
|
|
45
|
+
const promise = new Promise((resolve, reject) => {
|
|
46
|
+
let settled = false;
|
|
47
|
+
let manifest;
|
|
48
|
+
let readme;
|
|
49
|
+
function handleError(error) {
|
|
50
|
+
cleanup();
|
|
51
|
+
reject(error);
|
|
52
|
+
}
|
|
53
|
+
function settle() {
|
|
54
|
+
if (settled)
|
|
55
|
+
return;
|
|
56
|
+
settled = true;
|
|
57
|
+
cleanup();
|
|
58
|
+
if (manifest == null) {
|
|
59
|
+
reject(new PublishArchiveMissingManifestError(tarballPath));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
resolve({ manifest, readme });
|
|
63
|
+
}
|
|
64
|
+
tarballStream.once('error', handleError);
|
|
65
|
+
gunzip.once('error', handleError);
|
|
66
|
+
extract.on('entry', (header, stream, next) => {
|
|
67
|
+
const normalizedPath = path.normalize(header.name).replaceAll('\\', '/');
|
|
68
|
+
const isManifest = normalizedPath === 'package/package.json';
|
|
69
|
+
const isReadme = wantReadme && /^package\/readme\.md$/i.test(normalizedPath);
|
|
70
|
+
if (!isManifest && !isReadme) {
|
|
71
|
+
stream.once('end', next);
|
|
72
|
+
stream.resume();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const chunks = [];
|
|
76
|
+
stream.on('data', (chunk) => {
|
|
77
|
+
chunks.push(chunk);
|
|
78
|
+
});
|
|
79
|
+
stream.once('end', () => {
|
|
80
|
+
const text = Buffer.concat(chunks).toString();
|
|
81
|
+
if (isManifest) {
|
|
82
|
+
manifest = text;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
readme = text;
|
|
86
|
+
}
|
|
87
|
+
// Stop early once every wanted entry has been captured, so the manifest-only
|
|
88
|
+
// path doesn't stream and decompress the remainder of the tarball.
|
|
89
|
+
if (manifest != null && (!wantReadme || readme != null)) {
|
|
90
|
+
settle();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
next();
|
|
94
|
+
});
|
|
95
|
+
stream.once('error', handleError);
|
|
96
|
+
});
|
|
97
|
+
extract.once('finish', settle);
|
|
98
|
+
extract.once('error', handleError);
|
|
99
|
+
});
|
|
100
|
+
tarballStream.pipe(gunzip).pipe(extract);
|
|
101
|
+
return promise;
|
|
102
|
+
}
|
|
103
|
+
export class PublishArchiveMissingManifestError extends PnpmError {
|
|
104
|
+
tarballPath;
|
|
105
|
+
constructor(tarballPath) {
|
|
106
|
+
super('PUBLISH_ARCHIVE_MISSING_MANIFEST', `The archive ${tarballPath} does not contain package/package.json`);
|
|
107
|
+
this.tarballPath = tarballPath;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=extractManifestFromPacked.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import type { PublishPackedPkgOptions } from '../publishPackedPkg.js';
|
|
3
|
+
export interface AuthTokenFetchOptions {
|
|
4
|
+
body?: '';
|
|
5
|
+
headers: {
|
|
6
|
+
Accept: 'application/json';
|
|
7
|
+
Authorization: `Bearer ${string}`;
|
|
8
|
+
'Content-Length': '0';
|
|
9
|
+
};
|
|
10
|
+
method?: 'POST';
|
|
11
|
+
retry?: {
|
|
12
|
+
factor?: number;
|
|
13
|
+
maxTimeout?: number;
|
|
14
|
+
minTimeout?: number;
|
|
15
|
+
randomize?: boolean;
|
|
16
|
+
retries?: number;
|
|
17
|
+
};
|
|
18
|
+
timeout?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface AuthTokenFetchResponse {
|
|
21
|
+
readonly json: (this: this) => Promise<unknown>;
|
|
22
|
+
readonly ok: boolean;
|
|
23
|
+
readonly status: number;
|
|
24
|
+
}
|
|
25
|
+
export interface AuthTokenContext {
|
|
26
|
+
fetch: (url: string, options: AuthTokenFetchOptions) => Promise<AuthTokenFetchResponse>;
|
|
27
|
+
}
|
|
28
|
+
export type AuthTokenOptions = Pick<PublishPackedPkgOptions, 'fetchRetries' | 'fetchRetryFactor' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'fetchTimeout'>;
|
|
29
|
+
export interface AuthTokenParams {
|
|
30
|
+
context?: AuthTokenContext;
|
|
31
|
+
idToken: string;
|
|
32
|
+
options?: AuthTokenOptions;
|
|
33
|
+
packageName: string;
|
|
34
|
+
registry: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Retrieve an `authToken` from the registry.
|
|
38
|
+
*
|
|
39
|
+
* @throws instances of subclasses of {@link AuthTokenError} which can be converted into warnings and skipped.
|
|
40
|
+
*
|
|
41
|
+
* @see https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect for GitHub Actions OIDC.
|
|
42
|
+
* @see https://api-docs.npmjs.com/#tag/OIDC/operation/exchangeOidcToken for NPM Registry OIDC.
|
|
43
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js#L112-L142 for npm's implementation.
|
|
44
|
+
* @see https://github.com/yarnpkg/berry/blob/bafbef55/packages/plugin-npm/sources/npmHttpUtils.ts#L626-L641 for yarn's implementation.
|
|
45
|
+
*/
|
|
46
|
+
export declare function fetchAuthToken({ context: { fetch, }, options, idToken, packageName, registry, }: AuthTokenParams): Promise<string>;
|
|
47
|
+
export declare abstract class AuthTokenError extends PnpmError {
|
|
48
|
+
}
|
|
49
|
+
export declare class AuthTokenFetchError extends AuthTokenError {
|
|
50
|
+
readonly errorSource: unknown;
|
|
51
|
+
readonly packageName: string;
|
|
52
|
+
readonly registry: string;
|
|
53
|
+
constructor(error: unknown, packageName: string, registry: string);
|
|
54
|
+
}
|
|
55
|
+
export declare class AuthTokenExchangeError extends AuthTokenError {
|
|
56
|
+
readonly errorResponse?: {
|
|
57
|
+
body?: {
|
|
58
|
+
message?: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
readonly httpStatus: number;
|
|
62
|
+
constructor(errorResponse: AuthTokenExchangeError['errorResponse'], httpStatus: number);
|
|
63
|
+
}
|
|
64
|
+
export declare class AuthTokenJsonInterruptedError extends AuthTokenError {
|
|
65
|
+
readonly errorSource: unknown;
|
|
66
|
+
constructor(error: unknown);
|
|
67
|
+
}
|
|
68
|
+
export declare class AuthTokenMalformedJsonError extends AuthTokenError {
|
|
69
|
+
readonly malformedJsonResponse: unknown;
|
|
70
|
+
readonly packageName: string;
|
|
71
|
+
readonly registry: string;
|
|
72
|
+
constructor(malformedJsonResponse: unknown, packageName: string, registry: string);
|
|
73
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { PnpmError, redactUrlCredentials } from '@pnpm/error';
|
|
2
|
+
import npa from '@pnpm/npm-package-arg';
|
|
3
|
+
import { displayError } from '../displayError.js';
|
|
4
|
+
import { SHARED_CONTEXT } from '../utils/shared-context.js';
|
|
5
|
+
/**
|
|
6
|
+
* Retrieve an `authToken` from the registry.
|
|
7
|
+
*
|
|
8
|
+
* @throws instances of subclasses of {@link AuthTokenError} which can be converted into warnings and skipped.
|
|
9
|
+
*
|
|
10
|
+
* @see https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect for GitHub Actions OIDC.
|
|
11
|
+
* @see https://api-docs.npmjs.com/#tag/OIDC/operation/exchangeOidcToken for NPM Registry OIDC.
|
|
12
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js#L112-L142 for npm's implementation.
|
|
13
|
+
* @see https://github.com/yarnpkg/berry/blob/bafbef55/packages/plugin-npm/sources/npmHttpUtils.ts#L626-L641 for yarn's implementation.
|
|
14
|
+
*/
|
|
15
|
+
export async function fetchAuthToken({ context: { fetch, } = SHARED_CONTEXT, options, idToken, packageName, registry, }) {
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await fetch(new URL(`/-/npm/v1/oidc/token/exchange/package/${npa(packageName).escapedName}`, registry).href, {
|
|
19
|
+
body: '',
|
|
20
|
+
headers: {
|
|
21
|
+
Accept: 'application/json',
|
|
22
|
+
Authorization: `Bearer ${idToken}`,
|
|
23
|
+
'Content-Length': '0',
|
|
24
|
+
},
|
|
25
|
+
method: 'POST',
|
|
26
|
+
retry: {
|
|
27
|
+
factor: options?.fetchRetryFactor,
|
|
28
|
+
maxTimeout: options?.fetchRetryMaxtimeout,
|
|
29
|
+
minTimeout: options?.fetchRetryMintimeout,
|
|
30
|
+
retries: options?.fetchRetries,
|
|
31
|
+
},
|
|
32
|
+
timeout: options?.fetchTimeout,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new AuthTokenFetchError(error, packageName, registry);
|
|
37
|
+
}
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const error = await response.json().catch(() => undefined);
|
|
40
|
+
throw new AuthTokenExchangeError(error, response.status);
|
|
41
|
+
}
|
|
42
|
+
let json;
|
|
43
|
+
try {
|
|
44
|
+
json = await response.json();
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new AuthTokenJsonInterruptedError(error);
|
|
48
|
+
}
|
|
49
|
+
if (!json || typeof json !== 'object' || !('token' in json) || typeof json.token !== 'string') {
|
|
50
|
+
throw new AuthTokenMalformedJsonError(json, packageName, registry);
|
|
51
|
+
}
|
|
52
|
+
return json.token;
|
|
53
|
+
}
|
|
54
|
+
export class AuthTokenError extends PnpmError {
|
|
55
|
+
}
|
|
56
|
+
export class AuthTokenFetchError extends AuthTokenError {
|
|
57
|
+
errorSource;
|
|
58
|
+
packageName;
|
|
59
|
+
registry;
|
|
60
|
+
constructor(error, packageName, registry) {
|
|
61
|
+
const redactedRegistry = redactUrlCredentials(registry);
|
|
62
|
+
super('AUTH_TOKEN_FETCH', `Failed to fetch authToken for package ${packageName} from registry ${redactedRegistry}: ${displayError(error)}`);
|
|
63
|
+
this.errorSource = error;
|
|
64
|
+
this.packageName = packageName;
|
|
65
|
+
this.registry = redactedRegistry;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export class AuthTokenExchangeError extends AuthTokenError {
|
|
69
|
+
errorResponse;
|
|
70
|
+
httpStatus;
|
|
71
|
+
constructor(errorResponse, httpStatus) {
|
|
72
|
+
const message = errorResponse?.body?.message ?? 'Unknown error';
|
|
73
|
+
super('AUTH_TOKEN_EXCHANGE', `Failed token exchange request with body message: ${message} (status code ${httpStatus})`);
|
|
74
|
+
this.errorResponse = errorResponse;
|
|
75
|
+
this.httpStatus = httpStatus;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export class AuthTokenJsonInterruptedError extends AuthTokenError {
|
|
79
|
+
errorSource;
|
|
80
|
+
constructor(error) {
|
|
81
|
+
super('AUTH_TOKEN_JSON_INTERRUPTED', `Fetching of authToken JSON interrupted: ${displayError(error)}`);
|
|
82
|
+
this.errorSource = error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export class AuthTokenMalformedJsonError extends AuthTokenError {
|
|
86
|
+
malformedJsonResponse;
|
|
87
|
+
packageName;
|
|
88
|
+
registry;
|
|
89
|
+
constructor(malformedJsonResponse, packageName, registry) {
|
|
90
|
+
const redactedRegistry = redactUrlCredentials(registry);
|
|
91
|
+
super('AUTH_TOKEN_MALFORMED_JSON', `Failed to fetch authToken for package ${packageName} from registry ${redactedRegistry} due to malformed JSON response`);
|
|
92
|
+
this.malformedJsonResponse = malformedJsonResponse;
|
|
93
|
+
this.packageName = packageName;
|
|
94
|
+
this.registry = redactedRegistry;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=authToken.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import type { PublishPackedPkgOptions } from '../publishPackedPkg.js';
|
|
3
|
+
export interface IdTokenDate {
|
|
4
|
+
now: (this: this) => number;
|
|
5
|
+
}
|
|
6
|
+
export interface IdTokenCIInfo {
|
|
7
|
+
GITHUB_ACTIONS?: boolean;
|
|
8
|
+
GITLAB?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface IdTokenEnv extends NodeJS.ProcessEnv {
|
|
11
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN?: string;
|
|
12
|
+
ACTIONS_ID_TOKEN_REQUEST_URL?: string;
|
|
13
|
+
NPM_ID_TOKEN?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface IdTokenFetchOptions {
|
|
16
|
+
body?: null;
|
|
17
|
+
headers: {
|
|
18
|
+
Accept: 'application/json';
|
|
19
|
+
Authorization: `Bearer ${string}`;
|
|
20
|
+
};
|
|
21
|
+
method?: 'GET';
|
|
22
|
+
retry?: {
|
|
23
|
+
factor?: number;
|
|
24
|
+
maxTimeout?: number;
|
|
25
|
+
minTimeout?: number;
|
|
26
|
+
randomize?: boolean;
|
|
27
|
+
retries?: number;
|
|
28
|
+
};
|
|
29
|
+
timeout?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface IdTokenFetchResponse {
|
|
32
|
+
readonly json: (this: this) => Promise<unknown>;
|
|
33
|
+
readonly ok: boolean;
|
|
34
|
+
readonly status: number;
|
|
35
|
+
}
|
|
36
|
+
export interface IdTokenContext {
|
|
37
|
+
Date: IdTokenDate;
|
|
38
|
+
ciInfo: IdTokenCIInfo;
|
|
39
|
+
fetch: (url: string, options: IdTokenFetchOptions) => Promise<IdTokenFetchResponse>;
|
|
40
|
+
globalInfo: (message: string) => void;
|
|
41
|
+
process: {
|
|
42
|
+
env?: IdTokenEnv;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export type IdTokenOptions = Pick<PublishPackedPkgOptions, 'fetchRetries' | 'fetchRetryFactor' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'fetchTimeout'>;
|
|
46
|
+
export interface IdTokenParams {
|
|
47
|
+
context?: IdTokenContext;
|
|
48
|
+
options?: IdTokenOptions;
|
|
49
|
+
registry: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Retrieve an `idToken` from the CI environment.
|
|
53
|
+
*
|
|
54
|
+
* @throws instances of subclasses of {@link IdTokenError} which can be converted into warnings and skipped.
|
|
55
|
+
*
|
|
56
|
+
* @see https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect for GitHub Actions OIDC.
|
|
57
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js#L37-L110 for npm's implementation
|
|
58
|
+
* @see https://github.com/yarnpkg/berry/blob/bafbef55/packages/plugin-npm/sources/npmHttpUtils.ts#L594-L624 for yarn's implementation
|
|
59
|
+
*/
|
|
60
|
+
export declare function getIdToken({ context: { Date, ciInfo: { GITHUB_ACTIONS }, fetch, globalInfo, process: { env }, }, options, registry, }: IdTokenParams): Promise<string | undefined>;
|
|
61
|
+
export declare abstract class IdTokenError extends PnpmError {
|
|
62
|
+
}
|
|
63
|
+
export declare class IdTokenGitHubWorkflowIncorrectPermissionsError extends IdTokenError {
|
|
64
|
+
constructor();
|
|
65
|
+
}
|
|
66
|
+
export declare class IdTokenGitHubInvalidResponseError extends IdTokenError {
|
|
67
|
+
constructor();
|
|
68
|
+
}
|
|
69
|
+
export declare class IdTokenGitHubJsonInterruptedError extends IdTokenError {
|
|
70
|
+
readonly errorSource: unknown;
|
|
71
|
+
constructor(error: unknown);
|
|
72
|
+
}
|
|
73
|
+
export declare class IdTokenGitHubJsonInvalidValueError extends IdTokenError {
|
|
74
|
+
readonly jsonResponse: unknown;
|
|
75
|
+
constructor(jsonResponse: unknown);
|
|
76
|
+
}
|