@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.
Files changed (95) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/lib/change/index.d.ts +42 -0
  3. package/lib/change/index.js +248 -0
  4. package/lib/deploy/createDeployFiles.d.ts +26 -0
  5. package/lib/deploy/createDeployFiles.js +255 -0
  6. package/lib/deploy/deploy.d.ts +12 -0
  7. package/lib/deploy/deploy.js +398 -0
  8. package/lib/deploy/deployHook.d.ts +2 -0
  9. package/lib/deploy/deployHook.js +15 -0
  10. package/lib/deploy/index.d.ts +2 -0
  11. package/lib/deploy/index.js +3 -0
  12. package/lib/index.d.ts +7 -0
  13. package/lib/lane/index.d.ts +24 -0
  14. package/lib/lane/index.js +157 -0
  15. package/lib/pack-app/index.d.ts +2 -0
  16. package/lib/pack-app/index.js +3 -0
  17. package/lib/pack-app/packApp.d.ts +22 -0
  18. package/lib/pack-app/packApp.js +558 -0
  19. package/lib/publish/FailedToPublishError.d.ts +22 -0
  20. package/lib/publish/FailedToPublishError.js +40 -0
  21. package/lib/publish/batchPublish.d.ts +20 -0
  22. package/lib/publish/batchPublish.js +201 -0
  23. package/lib/publish/displayError.d.ts +1 -0
  24. package/lib/publish/displayError.js +23 -0
  25. package/lib/publish/executeTokenHelper.d.ts +4 -0
  26. package/lib/publish/executeTokenHelper.js +17 -0
  27. package/lib/publish/extractManifestFromPacked.d.ts +19 -0
  28. package/lib/publish/extractManifestFromPacked.js +110 -0
  29. package/lib/publish/index.d.ts +3 -0
  30. package/lib/publish/index.js +4 -0
  31. package/lib/publish/oidc/authToken.d.ts +73 -0
  32. package/lib/publish/oidc/authToken.js +97 -0
  33. package/lib/publish/oidc/idToken.d.ts +76 -0
  34. package/lib/publish/oidc/idToken.js +90 -0
  35. package/lib/publish/oidc/provenance.d.ts +73 -0
  36. package/lib/publish/oidc/provenance.js +91 -0
  37. package/lib/publish/otp.d.ts +38 -0
  38. package/lib/publish/otp.js +41 -0
  39. package/lib/publish/otpEnv.d.ts +7 -0
  40. package/lib/publish/otpEnv.js +5 -0
  41. package/lib/publish/pack.d.ts +34 -0
  42. package/lib/publish/pack.js +408 -0
  43. package/lib/publish/previousChangelog.d.ts +25 -0
  44. package/lib/publish/previousChangelog.js +194 -0
  45. package/lib/publish/publish.d.ts +40 -0
  46. package/lib/publish/publish.js +256 -0
  47. package/lib/publish/publishPackedPkg.d.ts +79 -0
  48. package/lib/publish/publishPackedPkg.js +298 -0
  49. package/lib/publish/recursivePublish.d.ts +18 -0
  50. package/lib/publish/recursivePublish.js +132 -0
  51. package/lib/publish/registryConfigKeys.d.ts +30 -0
  52. package/lib/publish/registryConfigKeys.js +50 -0
  53. package/lib/publish/utils/shared-context.d.ts +7 -0
  54. package/lib/publish/utils/shared-context.js +20 -0
  55. package/lib/resolveUnpublishedDirs.d.ts +10 -0
  56. package/lib/resolveUnpublishedDirs.js +14 -0
  57. package/lib/stage/approve.d.ts +2 -0
  58. package/lib/stage/approve.js +14 -0
  59. package/lib/stage/context.d.ts +14 -0
  60. package/lib/stage/context.js +25 -0
  61. package/lib/stage/download.d.ts +2 -0
  62. package/lib/stage/download.js +32 -0
  63. package/lib/stage/errors.d.ts +15 -0
  64. package/lib/stage/errors.js +17 -0
  65. package/lib/stage/help.d.ts +1 -0
  66. package/lib/stage/help.js +84 -0
  67. package/lib/stage/index.d.ts +13 -0
  68. package/lib/stage/index.js +57 -0
  69. package/lib/stage/list.d.ts +2 -0
  70. package/lib/stage/list.js +49 -0
  71. package/lib/stage/parsing.d.ts +6 -0
  72. package/lib/stage/parsing.js +27 -0
  73. package/lib/stage/publish.d.ts +7 -0
  74. package/lib/stage/publish.js +38 -0
  75. package/lib/stage/reject.d.ts +2 -0
  76. package/lib/stage/reject.js +16 -0
  77. package/lib/stage/rendering.d.ts +11 -0
  78. package/lib/stage/rendering.js +50 -0
  79. package/lib/stage/request.d.ts +29 -0
  80. package/lib/stage/request.js +110 -0
  81. package/lib/stage/types.d.ts +38 -0
  82. package/lib/stage/types.js +3 -0
  83. package/lib/stage/view.d.ts +2 -0
  84. package/lib/stage/view.js +14 -0
  85. package/lib/tarball/index.d.ts +2 -0
  86. package/lib/tarball/index.js +3 -0
  87. package/lib/tarball/publishSummary.d.ts +47 -0
  88. package/lib/tarball/publishSummary.js +36 -0
  89. package/lib/tarball/safeTarballFilename.d.ts +8 -0
  90. package/lib/tarball/safeTarballFilename.js +21 -0
  91. package/lib/tarball/summarizeTarball.d.ts +12 -0
  92. package/lib/tarball/summarizeTarball.js +84 -0
  93. package/lib/version/index.d.ts +35 -0
  94. package/lib/version/index.js +360 -0
  95. package/package.json +42 -42
@@ -0,0 +1,256 @@
1
+ import path from 'node:path';
2
+ import { confirm } from '@inquirer/prompts';
3
+ import { FILTERING } from '@pnpm/cli.common-cli-options-help';
4
+ import { docsUrl, readProjectManifest } from '@pnpm/cli.utils';
5
+ import { types as allTypes } from '@pnpm/config.reader';
6
+ import { PnpmError } from '@pnpm/error';
7
+ import { runLifecycleHook } from '@pnpm/exec.lifecycle';
8
+ import { getCurrentBranch, isGitRepo, isRemoteHistoryClean, isWorkingTreeClean } from '@pnpm/network.git-utils';
9
+ import { rimraf } from '@zkochan/rimraf';
10
+ import { pick } from 'ramda';
11
+ import { realpathMissing } from 'realpath-missing';
12
+ import { renderHelp } from 'render-help';
13
+ import { temporaryDirectory } from 'tempy';
14
+ import { extractPublishManifestFromPacked, isTarballPath } from './extractManifestFromPacked.js';
15
+ import { optionsWithOtpEnv } from './otpEnv.js';
16
+ import * as pack from './pack.js';
17
+ import { publishPackedPkg } from './publishPackedPkg.js';
18
+ import { recursivePublish } from './recursivePublish.js';
19
+ export function rcOptionsTypes() {
20
+ return pick([
21
+ 'access',
22
+ 'git-checks',
23
+ 'ignore-scripts',
24
+ 'skip-manifest-obfuscation',
25
+ 'provenance',
26
+ 'npm-path',
27
+ 'otp',
28
+ 'publish-branch',
29
+ 'registry',
30
+ 'tag',
31
+ 'unsafe-perm',
32
+ 'embed-readme',
33
+ ], allTypes);
34
+ }
35
+ export function cliOptionsTypes() {
36
+ return {
37
+ ...rcOptionsTypes(),
38
+ batch: Boolean,
39
+ 'dry-run': Boolean,
40
+ force: Boolean,
41
+ json: Boolean,
42
+ otp: String,
43
+ recursive: Boolean,
44
+ 'report-summary': Boolean,
45
+ };
46
+ }
47
+ export const commandNames = ['publish'];
48
+ export function help() {
49
+ return renderHelp({
50
+ description: 'Publishes a package to the npm registry.',
51
+ descriptionLists: [
52
+ {
53
+ title: 'Options',
54
+ list: [
55
+ {
56
+ description: "Don't check if current branch is your publish branch, clean, and up to date",
57
+ name: '--no-git-checks',
58
+ },
59
+ {
60
+ description: 'Sets branch name to publish. Default is master',
61
+ name: '--publish-branch',
62
+ },
63
+ {
64
+ description: 'Does everything a publish would do except actually publishing to the registry',
65
+ name: '--dry-run',
66
+ },
67
+ {
68
+ description: 'Show information in JSON format',
69
+ name: '--json',
70
+ },
71
+ {
72
+ description: 'Registers the published package with the given tag. By default, the "latest" tag is used.',
73
+ name: '--tag <tag>',
74
+ },
75
+ {
76
+ description: 'Tells the registry whether this package should be published as public or restricted',
77
+ name: '--access <public|restricted>',
78
+ },
79
+ {
80
+ description: 'Ignores any publish related lifecycle scripts (prepublishOnly, postpublish, and the like)',
81
+ name: '--ignore-scripts',
82
+ },
83
+ {
84
+ description: 'Skip pnpm\'s manifest obfuscation: keep the original `packageManager` field and publish lifecycle scripts in the published manifest instead of stripping them. The pnpm-specific `pnpm` field is still omitted.',
85
+ name: '--skip-manifest-obfuscation',
86
+ },
87
+ {
88
+ description: 'Packages are proceeded to be published even if their current version is already in the registry. This is useful when a "prepublishOnly" script bumps the version of the package before it is published',
89
+ name: '--force',
90
+ },
91
+ {
92
+ description: 'Save the list of the newly published packages to "pnpm-publish-summary.json". Useful when some other tooling is used to report the list of published packages.',
93
+ name: '--report-summary',
94
+ },
95
+ {
96
+ description: 'When publishing packages that require two-factor authentication, this option can specify a one-time password',
97
+ name: '--otp',
98
+ },
99
+ {
100
+ description: 'Publish all packages from the workspace',
101
+ name: '--recursive',
102
+ shortAlias: '-r',
103
+ },
104
+ {
105
+ description: 'Send all packages to the registry in a single request instead of one request per package. Requires --recursive and a registry that implements the "/-/pnpm/v1/publish" endpoint (for example, pnpr)',
106
+ name: '--batch',
107
+ },
108
+ ],
109
+ },
110
+ FILTERING,
111
+ ],
112
+ url: docsUrl('publish'),
113
+ usages: ['pnpm publish [<tarball>|<dir>] [--tag <tag>] [--access <public|restricted>] [options]'],
114
+ });
115
+ }
116
+ const GIT_CHECKS_HINT = 'If you want to disable Git checks on publish, set the "git-checks" setting to "false", or run again with "--no-git-checks".';
117
+ export async function handler(opts, params) {
118
+ const result = await publish(opts, params);
119
+ // Emit per-package summaries on stdout when --json is set: single object for a single-package
120
+ // publish, array for recursive publish. Mirrors `pnpm pack --json`'s shape choice.
121
+ if (opts.json) {
122
+ if (result?.publishSummary) {
123
+ return { output: JSON.stringify(result.publishSummary, null, 2), exitCode: 0 };
124
+ }
125
+ if (result?.publishedPackages) {
126
+ return { output: JSON.stringify(result.publishedPackages, null, 2), exitCode: result.exitCode ?? 0 };
127
+ }
128
+ }
129
+ if (result?.manifest)
130
+ return;
131
+ return result;
132
+ }
133
+ export async function publish(opts, params) {
134
+ if (opts.batch && !opts.recursive) {
135
+ throw new PnpmError('BATCH_PUBLISH_REQUIRES_RECURSIVE', '--batch can only be used together with --recursive', {
136
+ hint: 'Run "pnpm publish -r --batch" to publish all workspace packages in a single request.',
137
+ });
138
+ }
139
+ if (opts.gitChecks !== false && await isGitRepo()) {
140
+ if (!(await isWorkingTreeClean())) {
141
+ throw new PnpmError('GIT_UNCLEAN', 'Unclean working tree. Commit or stash changes first.', {
142
+ hint: GIT_CHECKS_HINT,
143
+ });
144
+ }
145
+ const branches = opts.publishBranch ? [opts.publishBranch] : ['master', 'main'];
146
+ const currentBranch = await getCurrentBranch();
147
+ if (currentBranch === null) {
148
+ throw new PnpmError('GIT_UNKNOWN_BRANCH', `The Git HEAD may not attached to any branch, but your "publish-branch" is set to "${branches.join('|')}".`, {
149
+ hint: GIT_CHECKS_HINT,
150
+ });
151
+ }
152
+ if (!branches.includes(currentBranch)) {
153
+ let isConfirmed;
154
+ try {
155
+ isConfirmed = await confirm({
156
+ message: `You're on branch "${currentBranch}" but your "publish-branch" is set to "${branches.join('|')}". Do you want to continue?`,
157
+ });
158
+ }
159
+ catch (err) {
160
+ if (err instanceof Error && err.name === 'ExitPromptError') {
161
+ isConfirmed = false;
162
+ }
163
+ else {
164
+ throw err;
165
+ }
166
+ }
167
+ if (!isConfirmed) {
168
+ throw new PnpmError('GIT_NOT_CORRECT_BRANCH', `Branch is not on '${branches.join('|')}'.`, {
169
+ hint: GIT_CHECKS_HINT,
170
+ });
171
+ }
172
+ }
173
+ if (!(await isRemoteHistoryClean())) {
174
+ throw new PnpmError('GIT_NOT_LATEST', 'Remote history differs. Please pull changes.', {
175
+ hint: GIT_CHECKS_HINT,
176
+ });
177
+ }
178
+ }
179
+ if (opts.recursive && (opts.selectedProjectsGraph != null)) {
180
+ const { exitCode, publishedPackages } = await recursivePublish({
181
+ ...opts,
182
+ selectedProjectsGraph: opts.selectedProjectsGraph,
183
+ workspaceDir: opts.workspaceDir ?? process.cwd(),
184
+ });
185
+ return { exitCode, publishedPackages };
186
+ }
187
+ opts = optionsWithOtpEnv(opts, process.env);
188
+ const dirInParams = (params.length > 0) ? params[0] : undefined;
189
+ if (dirInParams != null && isTarballPath(dirInParams)) {
190
+ const tarballPath = dirInParams;
191
+ const publishedManifest = await extractPublishManifestFromPacked(tarballPath);
192
+ // Publishing a pre-built tarball bypasses `pack.api()`, so we don't have the file listing
193
+ // or unpacked size — those summary fields are reported as empty/zero.
194
+ const publishSummary = await publishPackedPkg({
195
+ tarballPath,
196
+ publishedManifest,
197
+ contents: [],
198
+ unpackedSize: 0,
199
+ }, opts);
200
+ return { exitCode: 0, publishSummary };
201
+ }
202
+ const dir = dirInParams ?? opts.dir ?? process.cwd();
203
+ const _runScriptsIfPresent = runScriptsIfPresent.bind(null, {
204
+ depPath: dir,
205
+ extraBinPaths: opts.extraBinPaths,
206
+ extraEnv: opts.extraEnv,
207
+ pkgRoot: dir,
208
+ rootModulesDir: await realpathMissing(path.join(dir, 'node_modules')),
209
+ stdio: 'inherit',
210
+ unsafePerm: true, // when running scripts explicitly, assume that they're trusted.
211
+ userAgent: opts.userAgent,
212
+ });
213
+ const { manifest } = await readProjectManifest(dir, opts);
214
+ // Unfortunately, we cannot support postpack at the moment
215
+ if (!opts.ignoreScripts) {
216
+ await _runScriptsIfPresent([
217
+ 'prepublishOnly',
218
+ 'prepublish',
219
+ ], manifest);
220
+ }
221
+ // We have to publish the tarball from another location.
222
+ // Otherwise, npm would publish the package with the package.json file
223
+ // from the current working directory, ignoring the package.json file
224
+ // that was generated and packed to the tarball.
225
+ const packDestination = temporaryDirectory();
226
+ let publishedManifest;
227
+ let publishSummary;
228
+ try {
229
+ const packResult = await pack.api({
230
+ ...opts,
231
+ dir,
232
+ packDestination,
233
+ dryRun: false,
234
+ });
235
+ publishSummary = await publishPackedPkg(packResult, opts);
236
+ publishedManifest = packResult.publishedManifest;
237
+ }
238
+ finally {
239
+ await rimraf(packDestination);
240
+ }
241
+ if (!opts.ignoreScripts) {
242
+ await _runScriptsIfPresent([
243
+ 'publish',
244
+ 'postpublish',
245
+ ], manifest);
246
+ }
247
+ return { manifest, publishedManifest, publishSummary };
248
+ }
249
+ export async function runScriptsIfPresent(opts, scriptNames, manifest) {
250
+ for (const scriptName of scriptNames) {
251
+ if (!manifest.scripts?.[scriptName])
252
+ continue;
253
+ await runLifecycleHook(scriptName, manifest, opts); // eslint-disable-line no-await-in-loop
254
+ }
255
+ }
256
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1,79 @@
1
+ import type { Config } from '@pnpm/config.reader';
2
+ import { PnpmError } from '@pnpm/error';
3
+ import type { ExportedManifest } from '@pnpm/releasing.exportable-manifest';
4
+ import { type RegistryConfig } from '@pnpm/types';
5
+ import { type PublishSummary } from '../tarball/publishSummary.js';
6
+ import { type OtpContext, type PublishOptionsWithDefaultAccess } from './otp.js';
7
+ import type { PackResult } from './pack.js';
8
+ import { type NormalizedRegistryUrl } from './registryConfigKeys.js';
9
+ export type { PublishSummary };
10
+ export type PublishPackedPkgOptions = Pick<Config, 'configByUri' | 'dryRun' | 'fetchRetries' | 'fetchRetryFactor' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'fetchTimeout' | 'registries' | 'tag' | 'userAgent'> & Partial<Pick<Config, 'ca' | 'cert' | 'httpProxy' | 'httpsProxy' | 'key' | 'localAddress' | 'noProxy' | 'strictSsl'>> & {
11
+ access?: 'public' | 'restricted';
12
+ ci?: boolean;
13
+ otp?: string;
14
+ provenance?: boolean;
15
+ provenanceFile?: string;
16
+ stage?: boolean;
17
+ };
18
+ export declare function publishPackedPkg(packResult: Pick<PackResult, 'publishedManifest' | 'tarballPath' | 'contents' | 'unpackedSize'>, opts: PublishPackedPkgOptions): Promise<PublishSummary>;
19
+ /**
20
+ * Builds the {@link OtpContext} used to drive the publish. The default fetch
21
+ * is replaced by one that respects proxy / TLS / local-address settings, so
22
+ * the `doneUrl` polling in the web-based authentication flow goes through
23
+ * the same network configuration as the initial publish request (see
24
+ * https://github.com/pnpm/pnpm/issues/11561).
25
+ */
26
+ export declare function createPublishContext(opts: PublishPackedPkgOptions): OtpContext;
27
+ type StagePublishOptions = PublishOptionsWithDefaultAccess & {
28
+ command?: string;
29
+ stage?: boolean;
30
+ };
31
+ /**
32
+ * Build the libnpmpublish / npm-registry-fetch options (registry, auth, headers) for publishing
33
+ * {@link manifest}. When `oidc` is `false`, the per-package OIDC token exchange is skipped and only
34
+ * statically configured credentials are used — batch publish sends many packages in one request,
35
+ * which a package-scoped OIDC token cannot authorize.
36
+ *
37
+ * @internal Exported for unit testing of the access / registry / auth fallback rules and for batch
38
+ * publish. Not part of the package's public API.
39
+ */
40
+ export declare function createPublishOptions(manifest: ExportedManifest, options: PublishPackedPkgOptions, { oidc }?: {
41
+ oidc?: boolean;
42
+ }): Promise<StagePublishOptions>;
43
+ export declare function isPublishAccess(access: unknown): access is 'public' | 'restricted';
44
+ interface RegistryInfo {
45
+ registry: NormalizedRegistryUrl;
46
+ config: RegistryConfig;
47
+ }
48
+ /**
49
+ * Find credentials and SSL info for a package's registry.
50
+ * Follows {@link https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#auth-related-configuration}.
51
+ *
52
+ * The manifest's `publishConfig.registry`, when set, takes precedence over `registries`.
53
+ *
54
+ * @internal Exported for batch publish, which groups packages by their target registry.
55
+ */
56
+ export declare function findRegistryInfo({ name }: ExportedManifest, { configByUri, registries }: Pick<Config, 'configByUri' | 'registries'>, publishConfigRegistry?: string): Partial<RegistryInfo>;
57
+ export declare class PublishUnsupportedRegistryProtocolError extends PnpmError {
58
+ readonly registryUrl: string;
59
+ constructor(registryUrl: string);
60
+ }
61
+ interface OidcTokenProvenanceResult {
62
+ authToken: string;
63
+ provenance?: boolean;
64
+ }
65
+ /**
66
+ * Try fetching an authentication token and provenance by OpenID Connect.
67
+ *
68
+ * The result, when defined, is intended to take precedence over any statically configured
69
+ * authentication. This mirrors the npm CLI's OIDC flow, which always attempts the exchange
70
+ * in supported CI environments and overwrites a configured `_authToken` on success.
71
+ *
72
+ * @returns the OIDC-derived authToken (and provenance flag) on success, or `undefined` when
73
+ * OIDC is not applicable / not configured on the registry — in which case callers should
74
+ * fall back to whatever static authentication they already have.
75
+ *
76
+ * @internal Exported for unit testing of the precedence rules. Not part of the package's
77
+ * public API.
78
+ */
79
+ export declare function fetchTokenAndProvenanceByOidc(packageName: string, registry: string, options: PublishPackedPkgOptions): Promise<OidcTokenProvenanceResult | undefined>;
@@ -0,0 +1,298 @@
1
+ import fs from 'node:fs/promises';
2
+ import { PnpmError, redactUrlCredentials } from '@pnpm/error';
3
+ import { globalInfo, globalWarn } from '@pnpm/logger';
4
+ import { createDispatchedFetch } from '@pnpm/network.fetch';
5
+ import { DEFAULT_REGISTRY_SCOPE } from '@pnpm/types';
6
+ import { createPublishSummary } from '../tarball/publishSummary.js';
7
+ import { displayError } from './displayError.js';
8
+ import { executeTokenHelper } from './executeTokenHelper.js';
9
+ import { createFailedToPublishError } from './FailedToPublishError.js';
10
+ import { AuthTokenError, fetchAuthToken } from './oidc/authToken.js';
11
+ import { getIdToken, IdTokenError } from './oidc/idToken.js';
12
+ import { determineProvenance, ProvenanceError } from './oidc/provenance.js';
13
+ import { publishWithOtpHandling } from './otp.js';
14
+ import { allRegistryConfigKeys, parseSupportedRegistryUrl } from './registryConfigKeys.js';
15
+ import { SHARED_CONTEXT } from './utils/shared-context.js';
16
+ export async function publishPackedPkg(packResult, opts) {
17
+ const { publishedManifest, tarballPath, contents, unpackedSize } = packResult;
18
+ const tarballData = await fs.readFile(tarballPath);
19
+ const publishOptions = await createPublishOptions(publishedManifest, opts);
20
+ const { name, version } = publishedManifest;
21
+ const { registry } = publishOptions;
22
+ const isStage = opts.stage === true;
23
+ // Redact any `user:pass@` credentials a registry= URL may carry so they don't leak into logs.
24
+ globalInfo(`📦 ${name}@${version} → ${registry != null ? redactUrlCredentials(registry) : 'the default registry'}`);
25
+ const summary = createPublishSummary({ publishedManifest, tarballPath, contents, unpackedSize }, tarballData);
26
+ if (opts.dryRun) {
27
+ globalWarn(`Skip ${isStage ? 'staging' : 'publishing'} ${name}@${version} (dry run)`);
28
+ return summary;
29
+ }
30
+ const context = createPublishContext(opts);
31
+ const response = await publishWithOtpHandling({
32
+ context,
33
+ manifest: publishedManifest,
34
+ publishOptions,
35
+ tarballData,
36
+ });
37
+ if (response.ok) {
38
+ if (isStage && response.stageId) {
39
+ summary.stageId = response.stageId;
40
+ }
41
+ globalInfo(`✅ ${isStage ? 'Staged' : 'Published'} package ${name}@${version}`);
42
+ return summary;
43
+ }
44
+ throw await createFailedToPublishError(packResult, response);
45
+ }
46
+ /**
47
+ * Builds the {@link OtpContext} used to drive the publish. The default fetch
48
+ * is replaced by one that respects proxy / TLS / local-address settings, so
49
+ * the `doneUrl` polling in the web-based authentication flow goes through
50
+ * the same network configuration as the initial publish request (see
51
+ * https://github.com/pnpm/pnpm/issues/11561).
52
+ */
53
+ export function createPublishContext(opts) {
54
+ return {
55
+ ...SHARED_CONTEXT,
56
+ fetch: createDispatchedFetch({ ...opts, timeout: opts.fetchTimeout }),
57
+ };
58
+ }
59
+ /**
60
+ * Build the libnpmpublish / npm-registry-fetch options (registry, auth, headers) for publishing
61
+ * {@link manifest}. When `oidc` is `false`, the per-package OIDC token exchange is skipped and only
62
+ * statically configured credentials are used — batch publish sends many packages in one request,
63
+ * which a package-scoped OIDC token cannot authorize.
64
+ *
65
+ * @internal Exported for unit testing of the access / registry / auth fallback rules and for batch
66
+ * publish. Not part of the package's public API.
67
+ */
68
+ export async function createPublishOptions(manifest, options, { oidc = true } = {}) {
69
+ const publishConfigRegistry = typeof manifest.publishConfig?.registry === 'string'
70
+ ? manifest.publishConfig.registry
71
+ : undefined;
72
+ const { registry, config } = findRegistryInfo(manifest, options, publishConfigRegistry);
73
+ const tls = config?.tls;
74
+ const creds = config?.[DEFAULT_REGISTRY_SCOPE];
75
+ const publishConfigAccess = manifest.publishConfig?.access;
76
+ const access = options.access ?? (isPublishAccess(publishConfigAccess) ? publishConfigAccess : null);
77
+ const { ci: isFromCI, fetchRetries, fetchRetryFactor, fetchRetryMaxtimeout, fetchRetryMintimeout, fetchTimeout: timeout, otp, provenance, provenanceFile, tag: defaultTag, userAgent, } = options;
78
+ const npmCommand = options.stage === true ? 'stage' : 'publish';
79
+ const headers = {
80
+ 'npm-auth-type': 'web',
81
+ 'npm-command': npmCommand,
82
+ };
83
+ const publishOptions = {
84
+ access,
85
+ defaultTag,
86
+ fetchRetries,
87
+ fetchRetryFactor,
88
+ fetchRetryMaxtimeout,
89
+ fetchRetryMintimeout,
90
+ headers,
91
+ isFromCI,
92
+ otp,
93
+ timeout,
94
+ provenance,
95
+ provenanceFile,
96
+ registry,
97
+ strictSSL: options.strictSsl, // npm-registry-fetch defaults to true; must be set explicitly to honour strictSsl: false
98
+ userAgent,
99
+ // Signal to the registry that the client supports web-based authentication.
100
+ // Without this, the registry would never offer the web auth flow and would
101
+ // always fall back to prompting the user for an OTP code, even when the user
102
+ // has no OTP set up.
103
+ authType: 'web',
104
+ ca: tls?.ca,
105
+ cert: tls?.cert,
106
+ key: tls?.key,
107
+ npmCommand,
108
+ token: creds && extractToken(creds),
109
+ username: creds?.basicAuth?.username,
110
+ password: creds?.basicAuth?.password,
111
+ };
112
+ if (options.stage === true) {
113
+ publishOptions.command = 'stage';
114
+ publishOptions.stage = true;
115
+ }
116
+ if (registry) {
117
+ if (oidc) {
118
+ // OIDC takes precedence over a configured static `_authToken`, mirroring the npm CLI's
119
+ // behavior (see https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js). Trusted
120
+ // publishing wins whenever the registry has it configured for the package; the static
121
+ // token is used only as a fallback when OIDC is not applicable.
122
+ const oidcTokenProvenance = await fetchTokenAndProvenanceByOidc(manifest.name, registry, options);
123
+ if (oidcTokenProvenance?.authToken) {
124
+ publishOptions.token = oidcTokenProvenance.authToken;
125
+ }
126
+ publishOptions.provenance ??= oidcTokenProvenance?.provenance;
127
+ }
128
+ appendAuthOptionsForRegistry(publishOptions, registry);
129
+ }
130
+ pruneUndefined(publishOptions);
131
+ return publishOptions;
132
+ }
133
+ export function isPublishAccess(access) {
134
+ return access === 'public' || access === 'restricted';
135
+ }
136
+ /**
137
+ * Find credentials and SSL info for a package's registry.
138
+ * Follows {@link https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#auth-related-configuration}.
139
+ *
140
+ * The manifest's `publishConfig.registry`, when set, takes precedence over `registries`.
141
+ *
142
+ * @internal Exported for batch publish, which groups packages by their target registry.
143
+ */
144
+ export function findRegistryInfo({ name }, { configByUri, registries }, publishConfigRegistry) {
145
+ // eslint-disable-next-line regexp/no-unused-capturing-group
146
+ const scopedMatches = /@(?<scope>[^/]+)\/(?<slug>[^/]+)/.exec(name);
147
+ const registryName = scopedMatches?.groups ? `@${scopedMatches.groups.scope}` : 'default';
148
+ const nonNormalizedRegistry = publishConfigRegistry ?? registries[registryName] ?? registries.default;
149
+ const supportedRegistryInfo = parseSupportedRegistryUrl(nonNormalizedRegistry);
150
+ if (!supportedRegistryInfo) {
151
+ throw new PublishUnsupportedRegistryProtocolError(nonNormalizedRegistry);
152
+ }
153
+ const { normalizedUrl: registry, longestConfigKey: initialRegistryConfigKey, } = supportedRegistryInfo;
154
+ const credsScope = registryName === 'default' ? DEFAULT_REGISTRY_SCOPE : registryName;
155
+ let creds;
156
+ let tls = {};
157
+ for (const registryConfigKey of allRegistryConfigKeys(initialRegistryConfigKey)) {
158
+ const entry = configByUri[registryConfigKey];
159
+ if (!entry)
160
+ continue;
161
+ // Auth from longer path collectively overrides shorter path
162
+ creds ??= entry[credsScope] ?? entry[DEFAULT_REGISTRY_SCOPE];
163
+ // TLS from longer path individually overrides shorter path
164
+ tls = { ...entry.tls, ...tls };
165
+ }
166
+ const config = { tls };
167
+ if (creds) {
168
+ config[DEFAULT_REGISTRY_SCOPE] = creds;
169
+ }
170
+ return {
171
+ registry,
172
+ config,
173
+ };
174
+ }
175
+ function extractToken({ authToken, tokenHelper, }) {
176
+ if (authToken)
177
+ return authToken;
178
+ if (tokenHelper) {
179
+ return executeTokenHelper(tokenHelper, { globalWarn });
180
+ }
181
+ return undefined;
182
+ }
183
+ export class PublishUnsupportedRegistryProtocolError extends PnpmError {
184
+ registryUrl;
185
+ constructor(registryUrl) {
186
+ super('PUBLISH_UNSUPPORTED_REGISTRY_PROTOCOL', `Registry ${registryUrl} has an unsupported protocol`, {
187
+ hint: '`pnpm publish` only supports HTTP and HTTPS registries',
188
+ });
189
+ this.registryUrl = registryUrl;
190
+ }
191
+ }
192
+ /**
193
+ * Try fetching an authentication token and provenance by OpenID Connect.
194
+ *
195
+ * The result, when defined, is intended to take precedence over any statically configured
196
+ * authentication. This mirrors the npm CLI's OIDC flow, which always attempts the exchange
197
+ * in supported CI environments and overwrites a configured `_authToken` on success.
198
+ *
199
+ * @returns the OIDC-derived authToken (and provenance flag) on success, or `undefined` when
200
+ * OIDC is not applicable / not configured on the registry — in which case callers should
201
+ * fall back to whatever static authentication they already have.
202
+ *
203
+ * @internal Exported for unit testing of the precedence rules. Not part of the package's
204
+ * public API.
205
+ */
206
+ export async function fetchTokenAndProvenanceByOidc(packageName, registry, options) {
207
+ let idToken;
208
+ try {
209
+ idToken = await getIdToken({
210
+ options,
211
+ registry,
212
+ });
213
+ }
214
+ catch (error) {
215
+ if (error instanceof IdTokenError) {
216
+ globalWarn(`Skipped OIDC: ${displayError(error)}`);
217
+ return undefined;
218
+ }
219
+ throw error;
220
+ }
221
+ if (!idToken) {
222
+ // OIDC is simply not applicable here — either we're outside of CI, or we're in a CI
223
+ // that doesn't natively drive OIDC and the user hasn't forwarded a token via
224
+ // `NPM_ID_TOKEN`. This is the common case for local publishes, so it must stay
225
+ // silent — only configuration *errors* in a supported CI environment surface as
226
+ // warnings, and those come back as `IdTokenError` and are handled above.
227
+ return undefined;
228
+ }
229
+ let authToken;
230
+ try {
231
+ authToken = await fetchAuthToken({
232
+ idToken,
233
+ options,
234
+ packageName,
235
+ registry,
236
+ });
237
+ }
238
+ catch (error) {
239
+ if (error instanceof AuthTokenError) {
240
+ globalWarn(`Skipped OIDC: ${displayError(error)}`);
241
+ return undefined;
242
+ }
243
+ throw error;
244
+ }
245
+ if (options.provenance != null) {
246
+ return {
247
+ authToken,
248
+ provenance: options.provenance,
249
+ };
250
+ }
251
+ let provenance;
252
+ try {
253
+ provenance = await determineProvenance({
254
+ authToken,
255
+ idToken,
256
+ options,
257
+ packageName,
258
+ registry,
259
+ });
260
+ }
261
+ catch (error) {
262
+ if (error instanceof ProvenanceError) {
263
+ // Don't lose the OIDC-derived authToken just because we couldn't determine the
264
+ // provenance flag — the publish itself can still go through, and that's what
265
+ // the npm CLI does too.
266
+ globalWarn(`Skipped setting provenance: ${displayError(error)}`);
267
+ return { authToken };
268
+ }
269
+ throw error;
270
+ }
271
+ return { authToken, provenance };
272
+ }
273
+ /**
274
+ * Appends authentication information to {@link targetPublishOptions} to explicitly target {@link registry}.
275
+ *
276
+ * `libnpmpublish` has a quirk in which it only read the authentication information from `//<registry>:_authToken`
277
+ * instead of `token`.
278
+ * This function fixes that by making sure the registry specific authentication information exists.
279
+ */
280
+ function appendAuthOptionsForRegistry(targetPublishOptions, registry) {
281
+ const registryInfo = parseSupportedRegistryUrl(registry);
282
+ if (!registryInfo) {
283
+ globalWarn(`The registry ${registry} cannot be converted into a config key. Supplement is skipped. Subsequent libnpmpublish call may fail.`);
284
+ return;
285
+ }
286
+ const registryConfigKey = registryInfo.longestConfigKey;
287
+ targetPublishOptions[`${registryConfigKey}:_authToken`] ??= targetPublishOptions.token;
288
+ targetPublishOptions[`${registryConfigKey}:username`] ??= targetPublishOptions.username;
289
+ targetPublishOptions[`${registryConfigKey}:_password`] ??= targetPublishOptions.password && btoa(targetPublishOptions.password);
290
+ }
291
+ function pruneUndefined(object) {
292
+ for (const key in object) {
293
+ if (object[key] === undefined) {
294
+ delete object[key];
295
+ }
296
+ }
297
+ }
298
+ //# sourceMappingURL=publishPackedPkg.js.map
@@ -0,0 +1,18 @@
1
+ import type { Config, ConfigContext } from '@pnpm/config.reader';
2
+ import type { PublishPackedPkgOptions, PublishSummary } from './publishPackedPkg.js';
3
+ export type PublishRecursiveOpts = Required<Pick<Config, 'bin' | 'cacheDir' | 'dir' | 'pnpmHomeDir' | 'configByUri' | 'registries' | 'workspaceDir'>> & Required<Pick<ConfigContext, 'cliOptions'>> & Partial<Pick<Config, 'tag' | 'ca' | 'catalogs' | 'cert' | 'fetchTimeout' | 'force' | 'dryRun' | 'extraBinPaths' | 'extraEnv' | 'fetchRetries' | 'fetchRetryFactor' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'key' | 'httpProxy' | 'httpsProxy' | 'localAddress' | 'lockfileDir' | 'noProxy' | 'npmPath' | 'offline' | 'strictSsl' | 'unsafePerm' | 'userAgent' | 'verifyStoreIntegrity' | 'versioning'>> & Partial<Pick<ConfigContext, 'selectedProjectsGraph' | 'allProjectsGraph' | 'prodAllProjectsGraph' | 'prodOnlySelectedProjectDirs'>> & {
4
+ access?: 'public' | 'restricted';
5
+ argv: {
6
+ original: string[];
7
+ };
8
+ batch?: boolean;
9
+ reportSummary?: boolean;
10
+ } & PublishPackedPkgOptions;
11
+ export type RecursivePublishedPackage = PublishSummary | {
12
+ name?: string;
13
+ version?: string;
14
+ };
15
+ export declare function recursivePublish(opts: PublishRecursiveOpts & Required<Pick<ConfigContext, 'selectedProjectsGraph'>>): Promise<{
16
+ exitCode: number;
17
+ publishedPackages: RecursivePublishedPackage[];
18
+ }>;