@pnpm/registry-access.commands 1000.0.0

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors
4
+ Copyright (c) 2016-2026 Zoltan Kochan and other contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @pnpm/registry-access.commands
2
+
3
+ > Commands for managing packages on the registry (deprecate, undeprecate)
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ pnpm add @pnpm/registry-access.commands
9
+ ```
10
+
11
+ ## License
12
+
13
+ MIT
@@ -0,0 +1,5 @@
1
+ export declare function rcOptionsTypes(): Record<string, unknown>;
2
+ export declare function parsePackageSpec(spec: string): {
3
+ name: string;
4
+ versionRange: string | undefined;
5
+ };
package/lib/common.js ADDED
@@ -0,0 +1,24 @@
1
+ import { types as allTypes } from '@pnpm/config.reader';
2
+ import { PnpmError } from '@pnpm/error';
3
+ import npa from '@pnpm/npm-package-arg';
4
+ import { pick } from 'ramda';
5
+ export function rcOptionsTypes() {
6
+ return pick([
7
+ 'registry',
8
+ ], allTypes);
9
+ }
10
+ export function parsePackageSpec(spec) {
11
+ let parsed;
12
+ try {
13
+ parsed = npa(spec);
14
+ }
15
+ catch {
16
+ throw new PnpmError('INVALID_PACKAGE_SPEC', `Invalid package spec: ${spec}`);
17
+ }
18
+ if (!parsed.name) {
19
+ throw new PnpmError('INVALID_PACKAGE_SPEC', `Invalid package spec: ${spec}`);
20
+ }
21
+ const versionRange = parsed.rawSpec || undefined;
22
+ return { name: parsed.name, versionRange };
23
+ }
24
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1,18 @@
1
+ import { type CreateFetchFromRegistryOptions } from '@pnpm/network.fetch';
2
+ import type { Registries, RegistryConfig } from '@pnpm/types';
3
+ import { parsePackageSpec, rcOptionsTypes } from '../common.js';
4
+ export { parsePackageSpec, rcOptionsTypes };
5
+ export declare function cliOptionsTypes(): Record<string, unknown>;
6
+ export interface DeprecateOptions extends CreateFetchFromRegistryOptions {
7
+ cliOptions: {
8
+ otp?: string;
9
+ };
10
+ configByUri?: Record<string, RegistryConfig>;
11
+ registries?: Registries;
12
+ }
13
+ interface UpdateDeprecationOptions {
14
+ deprecated: string | undefined;
15
+ packageName: string;
16
+ versionRange: string | undefined;
17
+ }
18
+ export declare function updateDeprecation(opts: DeprecateOptions, { deprecated, packageName, versionRange }: UpdateDeprecationOptions): Promise<string>;
@@ -0,0 +1,76 @@
1
+ import { pickRegistryForPackage } from '@pnpm/config.pick-registry-for-package';
2
+ import { PnpmError } from '@pnpm/error';
3
+ import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header';
4
+ import { createFetchFromRegistry } from '@pnpm/network.fetch';
5
+ import npa from '@pnpm/npm-package-arg';
6
+ import semver from 'semver';
7
+ import { parsePackageSpec, rcOptionsTypes } from '../common.js';
8
+ export { parsePackageSpec, rcOptionsTypes };
9
+ export function cliOptionsTypes() {
10
+ return {
11
+ ...rcOptionsTypes(),
12
+ otp: String,
13
+ };
14
+ }
15
+ export async function updateDeprecation(opts, { deprecated, packageName, versionRange }) {
16
+ const registryUrl = pickRegistryForPackage(opts.registries ?? { default: 'https://registry.npmjs.org/' }, packageName);
17
+ const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri ?? {}, registryUrl);
18
+ const authHeader = getAuthHeader(registryUrl);
19
+ const packageUrl = new URL(npa(packageName).escapedName, registryUrl).href;
20
+ const fetchFromRegistry = createFetchFromRegistry(opts);
21
+ const getResponse = await fetchFromRegistry(packageUrl, {
22
+ authHeaderValue: authHeader,
23
+ fullMetadata: true,
24
+ });
25
+ if (!getResponse.ok) {
26
+ if (getResponse.status === 404) {
27
+ throw new PnpmError('PACKAGE_NOT_FOUND', `Package "${packageName}" not found in registry`);
28
+ }
29
+ throw new PnpmError('REGISTRY_ERROR', `Failed to fetch package info: ${getResponse.status} ${getResponse.statusText}`);
30
+ }
31
+ const pkg = await getResponse.json();
32
+ if (!pkg.versions || Object.keys(pkg.versions).length === 0) {
33
+ throw new PnpmError('NO_VERSIONS', `Package "${packageName}" has no versions`);
34
+ }
35
+ const versionsToUpdate = versionRange
36
+ ? getVersionsMatchingRange(pkg.versions, versionRange)
37
+ : Object.keys(pkg.versions);
38
+ if (versionsToUpdate.length === 0) {
39
+ throw new PnpmError('NO_MATCHING_VERSIONS', `No versions match "${versionRange}"`);
40
+ }
41
+ if (deprecated == null) {
42
+ const deprecatedVersions = versionsToUpdate.filter((ver) => pkg.versions[ver].deprecated);
43
+ if (deprecatedVersions.length === 0) {
44
+ throw new PnpmError('NOT_DEPRECATED', `No deprecated versions found in "${packageName}"${versionRange ? ` matching "${versionRange}"` : ''}`);
45
+ }
46
+ }
47
+ for (const ver of versionsToUpdate) {
48
+ pkg.versions[ver].deprecated = deprecated ?? '';
49
+ }
50
+ const otp = opts.cliOptions?.otp;
51
+ const putResponse = await fetchFromRegistry(packageUrl, {
52
+ authHeaderValue: authHeader,
53
+ method: 'PUT',
54
+ headers: {
55
+ 'content-type': 'application/json',
56
+ ...(otp ? { 'npm-otp': otp } : {}),
57
+ },
58
+ body: JSON.stringify(pkg),
59
+ });
60
+ if (!putResponse.ok) {
61
+ const verb = deprecated != null ? 'deprecate' : 'undeprecate';
62
+ const errorBody = await putResponse.text();
63
+ if (putResponse.status === 401) {
64
+ throw new PnpmError('UNAUTHORIZED', `You must be logged in to ${verb} packages. ${errorBody}`);
65
+ }
66
+ if (putResponse.status === 403) {
67
+ throw new PnpmError('FORBIDDEN', `You do not have permission to ${verb} this package. ${errorBody}`);
68
+ }
69
+ throw new PnpmError('REGISTRY_ERROR', `Failed to ${verb} package: ${putResponse.status} ${putResponse.statusText}. ${errorBody}`);
70
+ }
71
+ return `Successfully ${deprecated != null ? 'deprecated' : 'un-deprecated'} ${versionsToUpdate.length} version(s) of ${packageName}`;
72
+ }
73
+ function getVersionsMatchingRange(versions, range) {
74
+ return Object.keys(versions).filter((v) => semver.satisfies(v, range));
75
+ }
76
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1,5 @@
1
+ import { cliOptionsTypes, type DeprecateOptions, rcOptionsTypes } from './common.js';
2
+ export { cliOptionsTypes, rcOptionsTypes };
3
+ export declare const commandNames: string[];
4
+ export declare function help(): string;
5
+ export declare function handler(opts: DeprecateOptions, params: string[]): Promise<string>;
@@ -0,0 +1,43 @@
1
+ import { docsUrl } from '@pnpm/cli.utils';
2
+ import { PnpmError } from '@pnpm/error';
3
+ import { renderHelp } from 'render-help';
4
+ import { cliOptionsTypes, parsePackageSpec, rcOptionsTypes, updateDeprecation } from './common.js';
5
+ export { cliOptionsTypes, rcOptionsTypes };
6
+ export const commandNames = ['deprecate'];
7
+ export function help() {
8
+ return renderHelp({
9
+ description: 'Deprecates a version of a package in the registry.',
10
+ descriptionLists: [
11
+ {
12
+ title: 'Options',
13
+ list: [
14
+ {
15
+ description: 'The base URL of the npm registry.',
16
+ name: '--registry <url>',
17
+ },
18
+ {
19
+ description: 'When publishing packages that require two-factor authentication, this option can specify a one-time password.',
20
+ name: '--otp',
21
+ },
22
+ ],
23
+ },
24
+ ],
25
+ url: docsUrl('deprecate'),
26
+ usages: [
27
+ 'pnpm deprecate <package>[@<version>] <message>',
28
+ ],
29
+ });
30
+ }
31
+ export async function handler(opts, params) {
32
+ if (params.length === 0) {
33
+ throw new PnpmError('DEPRECATE_REQUIRED', 'Package name is required');
34
+ }
35
+ const packageSpec = params[0];
36
+ const deprecated = params.slice(1).join(' ');
37
+ if (deprecated === '') {
38
+ throw new PnpmError('DEPRECATE_MESSAGE_REQUIRED', 'Deprecation message is required. To un-deprecate, use the undeprecate command.');
39
+ }
40
+ const { name, versionRange } = parsePackageSpec(packageSpec);
41
+ return updateDeprecation(opts, { deprecated, packageName: name, versionRange });
42
+ }
43
+ //# sourceMappingURL=deprecate.js.map
@@ -0,0 +1,5 @@
1
+ import { cliOptionsTypes, type DeprecateOptions, rcOptionsTypes } from './common.js';
2
+ export { cliOptionsTypes, rcOptionsTypes };
3
+ export declare const commandNames: string[];
4
+ export declare function help(): string;
5
+ export declare function handler(opts: DeprecateOptions, params: string[]): Promise<string>;
@@ -0,0 +1,41 @@
1
+ import { docsUrl } from '@pnpm/cli.utils';
2
+ import { PnpmError } from '@pnpm/error';
3
+ import { renderHelp } from 'render-help';
4
+ import { cliOptionsTypes, parsePackageSpec, rcOptionsTypes, updateDeprecation } from './common.js';
5
+ export { cliOptionsTypes, rcOptionsTypes };
6
+ export const commandNames = ['undeprecate'];
7
+ export function help() {
8
+ return renderHelp({
9
+ description: 'Removes deprecation from a version of a package in the registry. Only works on already deprecated versions.',
10
+ descriptionLists: [
11
+ {
12
+ title: 'Options',
13
+ list: [
14
+ {
15
+ description: 'The base URL of the npm registry.',
16
+ name: '--registry <url>',
17
+ },
18
+ {
19
+ description: 'When publishing packages that require two-factor authentication, this option can specify a one-time password.',
20
+ name: '--otp',
21
+ },
22
+ ],
23
+ },
24
+ ],
25
+ url: docsUrl('undeprecate'),
26
+ usages: [
27
+ 'pnpm undeprecate <package>[@<version>]',
28
+ ],
29
+ });
30
+ }
31
+ export async function handler(opts, params) {
32
+ if (params.length === 0) {
33
+ throw new PnpmError('UNDEPRECATE_REQUIRED', 'Package name is required');
34
+ }
35
+ if (params.length > 1) {
36
+ throw new PnpmError('UNDEPRECATE_NO_MESSAGE', 'The undeprecate command does not accept a message.');
37
+ }
38
+ const { name, versionRange } = parsePackageSpec(params[0]);
39
+ return updateDeprecation(opts, { deprecated: undefined, packageName: name, versionRange });
40
+ }
41
+ //# sourceMappingURL=undeprecate.js.map
@@ -0,0 +1,15 @@
1
+ import { type CreateFetchFromRegistryOptions } from '@pnpm/network.fetch';
2
+ import type { Registries, RegistryConfig } from '@pnpm/types';
3
+ import { rcOptionsTypes } from './common.js';
4
+ export { rcOptionsTypes };
5
+ export declare function cliOptionsTypes(): Record<string, unknown>;
6
+ export declare const commandNames: string[];
7
+ export declare function help(): string;
8
+ export interface DistTagOptions extends CreateFetchFromRegistryOptions {
9
+ cliOptions: {
10
+ otp?: string;
11
+ };
12
+ configByUri?: Record<string, RegistryConfig>;
13
+ registries?: Registries;
14
+ }
15
+ export declare function handler(opts: DistTagOptions, params: string[]): Promise<string>;
package/lib/distTag.js ADDED
@@ -0,0 +1,184 @@
1
+ import { docsUrl } from '@pnpm/cli.utils';
2
+ import { pickRegistryForPackage } from '@pnpm/config.pick-registry-for-package';
3
+ import { PnpmError } from '@pnpm/error';
4
+ import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header';
5
+ import { createFetchFromRegistry } from '@pnpm/network.fetch';
6
+ import npa from '@pnpm/npm-package-arg';
7
+ import { renderHelp } from 'render-help';
8
+ import semver from 'semver';
9
+ import { parsePackageSpec, rcOptionsTypes } from './common.js';
10
+ export { rcOptionsTypes };
11
+ export function cliOptionsTypes() {
12
+ return {
13
+ ...rcOptionsTypes(),
14
+ otp: String,
15
+ };
16
+ }
17
+ export const commandNames = ['dist-tag', 'dist-tags'];
18
+ export function help() {
19
+ return renderHelp({
20
+ description: 'Manages distribution tags for a package.',
21
+ descriptionLists: [
22
+ {
23
+ title: 'Commands',
24
+ list: [
25
+ {
26
+ description: 'List all dist-tags for a package. Default if no subcommand is given.',
27
+ name: 'ls',
28
+ },
29
+ {
30
+ description: 'Add a dist-tag to a specific version of a package.',
31
+ name: 'add',
32
+ },
33
+ {
34
+ description: 'Remove a dist-tag from a package.',
35
+ name: 'rm',
36
+ },
37
+ ],
38
+ },
39
+ {
40
+ title: 'Options',
41
+ list: [
42
+ {
43
+ description: 'The base URL of the npm registry.',
44
+ name: '--registry <url>',
45
+ },
46
+ {
47
+ description: 'When publishing packages that require two-factor authentication, this option can specify a one-time password.',
48
+ name: '--otp',
49
+ },
50
+ ],
51
+ },
52
+ ],
53
+ url: docsUrl('dist-tag'),
54
+ usages: [
55
+ 'pnpm dist-tag ls [<package>]',
56
+ 'pnpm dist-tag add <package>@<version> [<tag>]',
57
+ 'pnpm dist-tag rm <package> <tag>',
58
+ ],
59
+ });
60
+ }
61
+ export async function handler(opts, params) {
62
+ const subcommand = params[0];
63
+ if (subcommand === 'add') {
64
+ return distTagAdd(opts, params.slice(1));
65
+ }
66
+ if (subcommand === 'rm') {
67
+ return distTagRm(opts, params.slice(1));
68
+ }
69
+ if (subcommand === 'ls' || subcommand === 'list') {
70
+ return distTagLs(opts, params.slice(1));
71
+ }
72
+ // Default: treat all params as arguments to ls
73
+ return distTagLs(opts, params);
74
+ }
75
+ async function distTagLs(opts, params) {
76
+ if (params.length === 0) {
77
+ throw new PnpmError('DIST_TAG_LS_PACKAGE_REQUIRED', 'Package name is required');
78
+ }
79
+ const packageName = params[0];
80
+ const registryUrl = pickRegistryForPackage(opts.registries ?? { default: 'https://registry.npmjs.org/' }, packageName);
81
+ const authHeader = getAuthHeaderForRegistry(opts.configByUri, registryUrl);
82
+ const fetchFromRegistry = createFetchFromRegistry(opts);
83
+ const distTags = await fetchDistTags(packageName, registryUrl, fetchFromRegistry, authHeader);
84
+ const lines = [];
85
+ for (const [tag, version] of Object.entries(distTags).sort(([a], [b]) => a.localeCompare(b))) {
86
+ lines.push(`${tag}: ${version}`);
87
+ }
88
+ return lines.join('\n');
89
+ }
90
+ async function distTagAdd(opts, params) {
91
+ if (params.length === 0) {
92
+ throw new PnpmError('DIST_TAG_ADD_SPEC_REQUIRED', 'Package name and version are required (e.g., pnpm dist-tag add pkg@1.0.0 latest)');
93
+ }
94
+ const { name: packageName, versionRange: version } = parsePackageSpec(params[0]);
95
+ if (!version) {
96
+ throw new PnpmError('DIST_TAG_ADD_VERSION_REQUIRED', 'Version is required (e.g., pnpm dist-tag add pkg@1.0.0 latest)');
97
+ }
98
+ if (!semver.valid(version)) {
99
+ throw new PnpmError('DIST_TAG_ADD_INVALID_VERSION', `Version must be an exact semver version, got "${version}"`);
100
+ }
101
+ const tag = params[1] ?? 'latest';
102
+ const registryUrl = pickRegistryForPackage(opts.registries ?? { default: 'https://registry.npmjs.org/' }, packageName);
103
+ const authHeader = getAuthHeaderForRegistry(opts.configByUri, registryUrl);
104
+ const fetchFromRegistry = createFetchFromRegistry(opts);
105
+ const otp = opts.cliOptions?.otp;
106
+ const distTagUrl = getDistTagUrl(packageName, registryUrl, tag);
107
+ const response = await fetchFromRegistry(distTagUrl, {
108
+ authHeaderValue: authHeader,
109
+ method: 'PUT',
110
+ headers: {
111
+ 'content-type': 'application/json',
112
+ ...(otp ? { 'npm-otp': otp } : {}),
113
+ },
114
+ body: JSON.stringify(version),
115
+ });
116
+ if (!response.ok) {
117
+ await throwRegistryError(response, `set dist-tag "${tag}" on`);
118
+ }
119
+ return `+${tag}: ${packageName}@${version}`;
120
+ }
121
+ async function distTagRm(opts, params) {
122
+ if (params.length < 2) {
123
+ throw new PnpmError('DIST_TAG_RM_ARGS_REQUIRED', 'Package name and tag are required (e.g., pnpm dist-tag rm pkg tag)');
124
+ }
125
+ const packageName = params[0];
126
+ const tag = params[1];
127
+ if (tag === 'latest') {
128
+ throw new PnpmError('DIST_TAG_RM_LATEST', 'Removing the "latest" dist-tag is not allowed');
129
+ }
130
+ const registryUrl = pickRegistryForPackage(opts.registries ?? { default: 'https://registry.npmjs.org/' }, packageName);
131
+ const authHeader = getAuthHeaderForRegistry(opts.configByUri, registryUrl);
132
+ const fetchFromRegistry = createFetchFromRegistry(opts);
133
+ const otp = opts.cliOptions?.otp;
134
+ // First check the tag exists
135
+ const distTags = await fetchDistTags(packageName, registryUrl, fetchFromRegistry, authHeader);
136
+ if (!(tag in distTags)) {
137
+ throw new PnpmError('DIST_TAG_NOT_FOUND', `dist-tag "${tag}" is not set on package "${packageName}"`);
138
+ }
139
+ const distTagUrl = getDistTagUrl(packageName, registryUrl, tag);
140
+ const response = await fetchFromRegistry(distTagUrl, {
141
+ authHeaderValue: authHeader,
142
+ method: 'DELETE',
143
+ headers: {
144
+ ...(otp ? { 'npm-otp': otp } : {}),
145
+ },
146
+ });
147
+ if (!response.ok) {
148
+ await throwRegistryError(response, `remove dist-tag "${tag}" from`);
149
+ }
150
+ return `-${tag}: ${packageName}@${distTags[tag]}`;
151
+ }
152
+ function getAuthHeaderForRegistry(configByUri, registryUrl) {
153
+ const getAuthHeader = createGetAuthHeaderByURI(configByUri ?? {}, registryUrl);
154
+ return getAuthHeader(registryUrl);
155
+ }
156
+ function getDistTagUrl(packageName, registryUrl, tag) {
157
+ const encodedName = npa(packageName).escapedName;
158
+ return new URL(`-/package/${encodedName}/dist-tags/${encodeURIComponent(tag)}`, registryUrl).href;
159
+ }
160
+ async function fetchDistTags(packageName, registryUrl, fetchFromRegistry, authHeader) {
161
+ const encodedName = npa(packageName).escapedName;
162
+ const distTagsUrl = new URL(`-/package/${encodedName}/dist-tags`, registryUrl).href;
163
+ const response = await fetchFromRegistry(distTagsUrl, {
164
+ authHeaderValue: authHeader,
165
+ });
166
+ if (!response.ok) {
167
+ if (response.status === 404) {
168
+ throw new PnpmError('PACKAGE_NOT_FOUND', `Package "${packageName}" not found in registry`);
169
+ }
170
+ throw new PnpmError('REGISTRY_ERROR', `Failed to fetch package info: ${response.status} ${response.statusText}`);
171
+ }
172
+ return await response.json();
173
+ }
174
+ async function throwRegistryError(response, action) {
175
+ const errorBody = await response.text();
176
+ if (response.status === 401) {
177
+ throw new PnpmError('UNAUTHORIZED', `You must be logged in to ${action} packages. ${errorBody}`);
178
+ }
179
+ if (response.status === 403) {
180
+ throw new PnpmError('FORBIDDEN', `You do not have permission to ${action} this package. ${errorBody}`);
181
+ }
182
+ throw new PnpmError('REGISTRY_ERROR', `Failed to ${action} package: ${response.status} ${response.statusText}. ${errorBody}`);
183
+ }
184
+ //# sourceMappingURL=distTag.js.map
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import * as deprecate from './deprecation/deprecate.js';
2
+ import * as undeprecate from './deprecation/undeprecate.js';
3
+ import * as distTag from './distTag.js';
4
+ import * as unpublish from './unpublish.js';
5
+ export { deprecate, distTag, undeprecate, unpublish };
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import * as deprecate from './deprecation/deprecate.js';
2
+ import * as undeprecate from './deprecation/undeprecate.js';
3
+ import * as distTag from './distTag.js';
4
+ import * as unpublish from './unpublish.js';
5
+ export { deprecate, distTag, undeprecate, unpublish };
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ import { type CreateFetchFromRegistryOptions } from '@pnpm/network.fetch';
2
+ import type { Registries, RegistryConfig } from '@pnpm/types';
3
+ import { rcOptionsTypes } from './common.js';
4
+ export { rcOptionsTypes };
5
+ export declare function cliOptionsTypes(): Record<string, unknown>;
6
+ export declare const commandNames: string[];
7
+ export declare function help(): string;
8
+ export interface UnpublishOptions extends CreateFetchFromRegistryOptions {
9
+ cliOptions: {
10
+ force?: boolean;
11
+ otp?: string;
12
+ };
13
+ configByUri?: Record<string, RegistryConfig>;
14
+ registries?: Registries;
15
+ }
16
+ export declare function handler(opts: UnpublishOptions, params: string[]): Promise<string>;
@@ -0,0 +1,204 @@
1
+ import { docsUrl } from '@pnpm/cli.utils';
2
+ import { pickRegistryForPackage } from '@pnpm/config.pick-registry-for-package';
3
+ import { PnpmError } from '@pnpm/error';
4
+ import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header';
5
+ import { createFetchFromRegistry } from '@pnpm/network.fetch';
6
+ import npa from '@pnpm/npm-package-arg';
7
+ import { renderHelp } from 'render-help';
8
+ import semver from 'semver';
9
+ import { parsePackageSpec, rcOptionsTypes } from './common.js';
10
+ export { rcOptionsTypes };
11
+ export function cliOptionsTypes() {
12
+ return {
13
+ ...rcOptionsTypes(),
14
+ force: Boolean,
15
+ otp: String,
16
+ };
17
+ }
18
+ export const commandNames = ['unpublish'];
19
+ export function help() {
20
+ return renderHelp({
21
+ description: 'Removes a package from the registry.',
22
+ descriptionLists: [
23
+ {
24
+ title: 'Options',
25
+ list: [
26
+ {
27
+ description: 'The base URL of the npm registry.',
28
+ name: '--registry <url>',
29
+ },
30
+ {
31
+ description: 'When publishing packages that require two-factor authentication, this option can specify a one-time password.',
32
+ name: '--otp',
33
+ },
34
+ {
35
+ description: 'Removes the package from the registry regardless of what version is currently published. Without this flag, pnpm will refuse to unpublish an entire package.',
36
+ name: '--force',
37
+ },
38
+ ],
39
+ },
40
+ ],
41
+ url: docsUrl('unpublish'),
42
+ usages: [
43
+ 'pnpm unpublish [<package>[@<version>]]',
44
+ ],
45
+ });
46
+ }
47
+ export async function handler(opts, params) {
48
+ if (params.length === 0) {
49
+ throw new PnpmError('UNPUBLISH_REQUIRED', 'Package name is required');
50
+ }
51
+ const packageSpec = params[0];
52
+ const { name, versionRange } = parsePackageSpec(packageSpec);
53
+ return unpublishPackage(name, versionRange, opts);
54
+ }
55
+ async function unpublishPackage(packageName, versionRange, opts) {
56
+ const registryUrl = pickRegistryForPackage(opts.registries ?? { default: 'https://registry.npmjs.org/' }, packageName);
57
+ const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri ?? {}, registryUrl);
58
+ const authHeader = getAuthHeader(registryUrl);
59
+ const packageUrl = new URL(npa(packageName).escapedName, registryUrl).href;
60
+ const fetchFromRegistry = createFetchFromRegistry(opts);
61
+ const pkg = await fetchPackument(packageUrl, fetchFromRegistry, authHeader);
62
+ const allVersions = pkg.versions;
63
+ if (!allVersions || Object.keys(allVersions).length === 0) {
64
+ throw new PnpmError('NO_VERSIONS', `Package "${packageName}" has no versions`);
65
+ }
66
+ const otp = opts.cliOptions?.otp;
67
+ if (!versionRange) {
68
+ return unpublishAll(packageUrl, pkg, fetchFromRegistry, authHeader, otp, opts.cliOptions);
69
+ }
70
+ const versionsToUnpublish = getVersionsMatchingRange(allVersions, versionRange);
71
+ if (versionsToUnpublish.length === 0) {
72
+ throw new PnpmError('NO_MATCHING_VERSIONS', `No versions match "${versionRange}"`);
73
+ }
74
+ // If removing all matched versions leaves none, treat as full unpublish
75
+ if (versionsToUnpublish.length === Object.keys(allVersions).length) {
76
+ return unpublishAll(packageUrl, pkg, fetchFromRegistry, authHeader, otp, opts.cliOptions);
77
+ }
78
+ return unpublishVersions(packageUrl, registryUrl, pkg, versionsToUnpublish, fetchFromRegistry, authHeader, otp);
79
+ }
80
+ async function fetchPackument(packageUrl, fetchFromRegistry, authHeader) {
81
+ const response = await fetchFromRegistry(packageUrl, {
82
+ authHeaderValue: authHeader,
83
+ fullMetadata: true,
84
+ });
85
+ if (!response.ok) {
86
+ if (response.status === 404) {
87
+ const url = new URL(packageUrl);
88
+ const packageName = decodeURIComponent(url.pathname.split('/').pop());
89
+ throw new PnpmError('PACKAGE_NOT_FOUND', `Package "${packageName}" not found in registry`);
90
+ }
91
+ throw new PnpmError('REGISTRY_ERROR', `Failed to fetch package info: ${response.status} ${response.statusText}`);
92
+ }
93
+ return await response.json();
94
+ }
95
+ async function unpublishVersions(packageUrl, registryUrl, pkg, versions, fetchFromRegistry, authHeader, otp) {
96
+ // Collect tarball URLs before mutating
97
+ const tarballs = [];
98
+ for (const version of versions) {
99
+ const versionData = pkg.versions[version];
100
+ if (versionData?.dist?.tarball) {
101
+ tarballs.push(versionData.dist.tarball);
102
+ }
103
+ delete pkg.versions[version];
104
+ }
105
+ // Update dist-tags: remove any tag pointing to removed versions
106
+ const removedSet = new Set(versions);
107
+ const latestVer = pkg['dist-tags'].latest;
108
+ for (const tag of Object.keys(pkg['dist-tags'])) {
109
+ if (removedSet.has(pkg['dist-tags'][tag])) {
110
+ delete pkg['dist-tags'][tag];
111
+ }
112
+ }
113
+ // If we removed 'latest', reassign it to the highest remaining version
114
+ if (latestVer && removedSet.has(latestVer)) {
115
+ const remaining = Object.keys(pkg.versions).sort(semver.compareLoose);
116
+ if (remaining.length > 0) {
117
+ pkg['dist-tags'].latest = remaining[remaining.length - 1];
118
+ }
119
+ }
120
+ // Clean up internal metadata
121
+ delete pkg._revisions;
122
+ delete pkg._attachments;
123
+ // PUT updated packument
124
+ const putResponse = await fetchFromRegistry(`${packageUrl}/-rev/${pkg._rev}`, {
125
+ authHeaderValue: authHeader,
126
+ method: 'PUT',
127
+ headers: {
128
+ 'content-type': 'application/json',
129
+ ...(otp ? { 'npm-otp': otp } : {}),
130
+ },
131
+ body: JSON.stringify(pkg),
132
+ });
133
+ if (!putResponse.ok) {
134
+ await throwRegistryError(putResponse, 'unpublish');
135
+ }
136
+ // Delete each tarball
137
+ const registryOrigin = new URL(registryUrl).origin;
138
+ /* eslint-disable no-await-in-loop */
139
+ for (const tarball of tarballs) {
140
+ const updated = await fetchPackument(packageUrl, fetchFromRegistry, authHeader);
141
+ const tarballPathname = getTarballPathname(tarball, registryUrl);
142
+ const deleteResponse = await fetchFromRegistry(`${registryOrigin}/${tarballPathname}/-rev/${updated._rev}`, {
143
+ authHeaderValue: authHeader,
144
+ method: 'DELETE',
145
+ headers: {
146
+ ...(otp ? { 'npm-otp': otp } : {}),
147
+ },
148
+ });
149
+ // Some registries handle tarball cleanup automatically on packument update,
150
+ // so treat 404 as success.
151
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
152
+ await throwRegistryError(deleteResponse, 'unpublish');
153
+ }
154
+ }
155
+ /* eslint-enable no-await-in-loop */
156
+ return `Successfully unpublished ${versions.length} version(s) of ${pkg.name}`;
157
+ }
158
+ async function unpublishAll(packageUrl, pkg, fetchFromRegistry, authHeader, otp, cliOptions) {
159
+ const packageName = pkg.name;
160
+ const versionCount = Object.keys(pkg.versions).length;
161
+ const force = cliOptions?.force ?? false;
162
+ if (!force) {
163
+ const versionsList = Object.keys(pkg.versions).join(', ');
164
+ throw new PnpmError('UNPUBLISH_CONFIRM', `Run pnpm unpublish --force to remove all published versions of ${packageName} (${versionsList}) from the registry.
165
+ This is a protection mechanism to prevent accidental unpublish of packages with many versions.
166
+ If you want to unpublish a specific version, run pnpm unpublish ${packageName}@<version>`);
167
+ }
168
+ const deleteResponse = await fetchFromRegistry(`${packageUrl}/-rev/${pkg._rev}`, {
169
+ authHeaderValue: authHeader,
170
+ method: 'DELETE',
171
+ headers: {
172
+ ...(otp ? { 'npm-otp': otp } : {}),
173
+ },
174
+ });
175
+ if (!deleteResponse.ok) {
176
+ if (deleteResponse.status === 405) {
177
+ throw new PnpmError('UNPUBLISH_FORBIDDEN', 'This package cannot be completely unpublished. Deprecate it instead or contact npm support.');
178
+ }
179
+ await throwRegistryError(deleteResponse, 'unpublish');
180
+ }
181
+ return `Successfully unpublished all ${versionCount} version(s) of ${packageName}`;
182
+ }
183
+ async function throwRegistryError(response, verb) {
184
+ if (response.status === 401) {
185
+ throw new PnpmError('UNAUTHORIZED', `You must be logged in to ${verb} packages`);
186
+ }
187
+ if (response.status === 403) {
188
+ throw new PnpmError('FORBIDDEN', `You do not have permission to ${verb} this package`);
189
+ }
190
+ const errorBody = await response.text();
191
+ throw new PnpmError('REGISTRY_ERROR', `Failed to ${verb} package: ${response.status} ${response.statusText}. ${errorBody}`);
192
+ }
193
+ function getTarballPathname(tarballUrl, registryUrl) {
194
+ const registryPath = new URL(registryUrl).pathname.slice(1);
195
+ let tarballPath = new URL(tarballUrl).pathname.slice(1);
196
+ if (registryPath && tarballPath.startsWith(registryPath)) {
197
+ tarballPath = tarballPath.slice(registryPath.length);
198
+ }
199
+ return tarballPath;
200
+ }
201
+ function getVersionsMatchingRange(versions, range) {
202
+ return Object.keys(versions).filter((v) => semver.satisfies(v, range));
203
+ }
204
+ //# sourceMappingURL=unpublish.js.map
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@pnpm/registry-access.commands",
3
+ "version": "1000.0.0",
4
+ "description": "Commands for managing packages on the registry",
5
+ "keywords": [
6
+ "pnpm",
7
+ "pnpm11"
8
+ ],
9
+ "license": "MIT",
10
+ "funding": "https://opencollective.com/pnpm",
11
+ "repository": "https://github.com/pnpm/pnpm/tree/main/registry-access/commands",
12
+ "homepage": "https://github.com/pnpm/pnpm/tree/main/registry-access/commands#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/pnpm/pnpm/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/index.d.ts",
19
+ "exports": {
20
+ ".": "./lib/index.js"
21
+ },
22
+ "files": [
23
+ "lib",
24
+ "!*.map"
25
+ ],
26
+ "dependencies": {
27
+ "@pnpm/npm-package-arg": "^2.0.0",
28
+ "ramda": "npm:@pnpm/ramda@0.28.1",
29
+ "render-help": "^2.0.0",
30
+ "semver": "^7.7.2",
31
+ "@pnpm/cli.utils": "1001.2.8",
32
+ "@pnpm/config.reader": "1004.4.2",
33
+ "@pnpm/error": "1000.0.5",
34
+ "@pnpm/network.fetch": "1000.2.6",
35
+ "@pnpm/network.auth-header": "1000.0.6",
36
+ "@pnpm/config.pick-registry-for-package": "1000.0.11",
37
+ "@pnpm/resolving.registry.types": "1000.0.1",
38
+ "@pnpm/types": "1000.9.0"
39
+ },
40
+ "devDependencies": {
41
+ "@jest/globals": "30.3.0",
42
+ "@pnpm/registry-mock": "6.0.0-6",
43
+ "@types/ramda": "0.31.1",
44
+ "@types/semver": "7.7.1",
45
+ "execa": "npm:safe-execa@0.3.0",
46
+ "@pnpm/registry-access.commands": "1000.0.0",
47
+ "@pnpm/testing.command-defaults": "0.0.0",
48
+ "@pnpm/releasing.commands": "1000.0.0",
49
+ "@pnpm/prepare": "1000.0.4"
50
+ },
51
+ "engines": {
52
+ "node": ">=22.13"
53
+ },
54
+ "jest": {
55
+ "preset": "@pnpm/jest-config/with-registry"
56
+ },
57
+ "scripts": {
58
+ "start": "tsgo --watch",
59
+ "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
60
+ "test": "pn compile && pn .test",
61
+ "compile": "tsgo --build && pn lint --fix",
62
+ ".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
63
+ }
64
+ }