@pnpm/releasing.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 +22 -0
- package/lib/deploy/createDeployFiles.d.ts +23 -0
- package/lib/deploy/createDeployFiles.js +218 -0
- package/lib/deploy/deploy.d.ts +11 -0
- package/lib/deploy/deploy.js +270 -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 +2 -0
- package/lib/index.js +3 -0
- package/lib/publish/FailedToPublishError.d.ts +22 -0
- package/lib/publish/FailedToPublishError.js +40 -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 +14 -0
- package/lib/publish/extractManifestFromPacked.d.ts +12 -0
- package/lib/publish/extractManifestFromPacked.js +71 -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 +95 -0
- package/lib/publish/oidc/idToken.d.ts +76 -0
- package/lib/publish/oidc/idToken.js +85 -0
- package/lib/publish/oidc/provenance.d.ts +73 -0
- package/lib/publish/oidc/provenance.js +90 -0
- package/lib/publish/otp.d.ts +89 -0
- package/lib/publish/otp.js +165 -0
- package/lib/publish/otpEnv.d.ts +7 -0
- package/lib/publish/otpEnv.js +5 -0
- package/lib/publish/pack.d.ts +32 -0
- package/lib/publish/pack.js +303 -0
- package/lib/publish/publish.d.ts +31 -0
- package/lib/publish/publish.js +216 -0
- package/lib/publish/publishPackedPkg.d.ts +19 -0
- package/lib/publish/publishPackedPkg.js +233 -0
- package/lib/publish/recursivePublish.d.ts +12 -0
- package/lib/publish/recursivePublish.js +114 -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 +17 -0
- package/package.json +118 -0
|
@@ -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,14 @@
|
|
|
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
|
+
return (execResult.stdout?.toString() ?? '').trim();
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=executeTokenHelper.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
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 `${string}.tar.gz` | `${string}.tgz`;
|
|
7
|
+
export declare function extractManifestFromPacked<Output = ExportedManifest>(tarballPath: TarballPath): Promise<Output>;
|
|
8
|
+
export declare class PublishArchiveMissingManifestError extends PnpmError {
|
|
9
|
+
readonly tarballPath: string;
|
|
10
|
+
constructor(tarballPath: string);
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
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 extract = tar.extract();
|
|
10
|
+
const gunzip = createGunzip();
|
|
11
|
+
const tarballStream = fs.createReadStream(tarballPath);
|
|
12
|
+
let cleanedUp = false;
|
|
13
|
+
function cleanup() {
|
|
14
|
+
if (cleanedUp)
|
|
15
|
+
return;
|
|
16
|
+
cleanedUp = true;
|
|
17
|
+
extract.destroy();
|
|
18
|
+
gunzip.destroy();
|
|
19
|
+
tarballStream.destroy();
|
|
20
|
+
}
|
|
21
|
+
const promise = new Promise((resolve, reject) => {
|
|
22
|
+
function handleError(error) {
|
|
23
|
+
cleanup();
|
|
24
|
+
reject(error);
|
|
25
|
+
}
|
|
26
|
+
tarballStream.once('error', handleError);
|
|
27
|
+
gunzip.once('error', handleError);
|
|
28
|
+
let manifestFound = false;
|
|
29
|
+
extract.on('entry', (header, stream, next) => {
|
|
30
|
+
const normalizedPath = path.normalize(header.name).replaceAll('\\', '/');
|
|
31
|
+
if (normalizedPath !== 'package/package.json') {
|
|
32
|
+
stream.once('end', next);
|
|
33
|
+
stream.resume();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
manifestFound = true;
|
|
37
|
+
const chunks = [];
|
|
38
|
+
stream.on('data', (chunk) => {
|
|
39
|
+
chunks.push(chunk);
|
|
40
|
+
});
|
|
41
|
+
stream.once('end', () => {
|
|
42
|
+
try {
|
|
43
|
+
const text = Buffer.concat(chunks).toString();
|
|
44
|
+
cleanup();
|
|
45
|
+
resolve(text);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
handleError(error);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
stream.once('error', handleError);
|
|
52
|
+
});
|
|
53
|
+
extract.once('finish', () => {
|
|
54
|
+
cleanup();
|
|
55
|
+
if (!manifestFound) {
|
|
56
|
+
reject(new PublishArchiveMissingManifestError(tarballPath));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
extract.once('error', handleError);
|
|
60
|
+
});
|
|
61
|
+
tarballStream.pipe(gunzip).pipe(extract);
|
|
62
|
+
return JSON.parse(await promise);
|
|
63
|
+
}
|
|
64
|
+
export class PublishArchiveMissingManifestError extends PnpmError {
|
|
65
|
+
tarballPath;
|
|
66
|
+
constructor(tarballPath) {
|
|
67
|
+
super('PUBLISH_ARCHIVE_MISSING_MANIFEST', `The archive ${tarballPath} does not contain package/package.json`);
|
|
68
|
+
this.tarballPath = tarballPath;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# 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,95 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import { displayError } from '../displayError.js';
|
|
3
|
+
import { SHARED_CONTEXT } from '../utils/shared-context.js';
|
|
4
|
+
/**
|
|
5
|
+
* Retrieve an `authToken` from the registry.
|
|
6
|
+
*
|
|
7
|
+
* @throws instances of subclasses of {@link AuthTokenError} which can be converted into warnings and skipped.
|
|
8
|
+
*
|
|
9
|
+
* @see https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect for GitHub Actions OIDC.
|
|
10
|
+
* @see https://api-docs.npmjs.com/#tag/OIDC/operation/exchangeOidcToken for NPM Registry OIDC.
|
|
11
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js#L112-L142 for npm's implementation.
|
|
12
|
+
* @see https://github.com/yarnpkg/berry/blob/bafbef55/packages/plugin-npm/sources/npmHttpUtils.ts#L626-L641 for yarn's implementation.
|
|
13
|
+
*/
|
|
14
|
+
export async function fetchAuthToken({ context: { fetch, } = SHARED_CONTEXT, options, idToken, packageName, registry, }) {
|
|
15
|
+
const escapedPackageName = encodeURIComponent(packageName);
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await fetch(new URL(`/-/npm/v1/oidc/token/exchange/package/${escapedPackageName}`, 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
|
+
super('AUTH_TOKEN_FETCH', `Failed to fetch authToken for package ${packageName} from registry ${registry}: ${displayError(error)}`);
|
|
62
|
+
this.errorSource = error;
|
|
63
|
+
this.packageName = packageName;
|
|
64
|
+
this.registry = registry;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export class AuthTokenExchangeError extends AuthTokenError {
|
|
68
|
+
errorResponse;
|
|
69
|
+
httpStatus;
|
|
70
|
+
constructor(errorResponse, httpStatus) {
|
|
71
|
+
const message = errorResponse?.body?.message ?? 'Unknown error';
|
|
72
|
+
super('AUTH_TOKEN_EXCHANGE', `Failed token exchange request with body message: ${message} (status code ${httpStatus})`);
|
|
73
|
+
this.errorResponse = errorResponse;
|
|
74
|
+
this.httpStatus = httpStatus;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export class AuthTokenJsonInterruptedError extends AuthTokenError {
|
|
78
|
+
errorSource;
|
|
79
|
+
constructor(error) {
|
|
80
|
+
super('AUTH_TOKEN_JSON_INTERRUPTED', `Fetching of authToken JSON interrupted: ${displayError(error)}`);
|
|
81
|
+
this.errorSource = error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export class AuthTokenMalformedJsonError extends AuthTokenError {
|
|
85
|
+
malformedJsonResponse;
|
|
86
|
+
packageName;
|
|
87
|
+
registry;
|
|
88
|
+
constructor(malformedJsonResponse, packageName, registry) {
|
|
89
|
+
super('AUTH_TOKEN_MALFORMED_JSON', `Failed to fetch authToken for package ${packageName} from registry ${registry} due to malformed JSON response`);
|
|
90
|
+
this.malformedJsonResponse = malformedJsonResponse;
|
|
91
|
+
this.packageName = packageName;
|
|
92
|
+
this.registry = registry;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# 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, GITLAB }, 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
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import { displayError } from '../displayError.js';
|
|
3
|
+
import { SHARED_CONTEXT } from '../utils/shared-context.js';
|
|
4
|
+
/**
|
|
5
|
+
* Retrieve an `idToken` from the CI environment.
|
|
6
|
+
*
|
|
7
|
+
* @throws instances of subclasses of {@link IdTokenError} which can be converted into warnings and skipped.
|
|
8
|
+
*
|
|
9
|
+
* @see https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect for GitHub Actions OIDC.
|
|
10
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js#L37-L110 for npm's implementation
|
|
11
|
+
* @see https://github.com/yarnpkg/berry/blob/bafbef55/packages/plugin-npm/sources/npmHttpUtils.ts#L594-L624 for yarn's implementation
|
|
12
|
+
*/
|
|
13
|
+
export async function getIdToken({ context: { Date, ciInfo: { GITHUB_ACTIONS, GITLAB }, fetch, globalInfo, process: { env }, } = SHARED_CONTEXT, options, registry, }) {
|
|
14
|
+
if (!GITHUB_ACTIONS && !GITLAB)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (env?.NPM_ID_TOKEN)
|
|
17
|
+
return env.NPM_ID_TOKEN;
|
|
18
|
+
if (!GITHUB_ACTIONS)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (!env?.ACTIONS_ID_TOKEN_REQUEST_TOKEN || !env?.ACTIONS_ID_TOKEN_REQUEST_URL) {
|
|
21
|
+
throw new IdTokenGitHubWorkflowIncorrectPermissionsError();
|
|
22
|
+
}
|
|
23
|
+
const parsedRegistry = new URL(registry);
|
|
24
|
+
const audience = `npm:${parsedRegistry.hostname}`;
|
|
25
|
+
const url = new URL(env.ACTIONS_ID_TOKEN_REQUEST_URL);
|
|
26
|
+
url.searchParams.append('audience', audience);
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
const response = await fetch(url.href, {
|
|
29
|
+
headers: {
|
|
30
|
+
Accept: 'application/json',
|
|
31
|
+
Authorization: `Bearer ${env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}`,
|
|
32
|
+
},
|
|
33
|
+
method: 'GET',
|
|
34
|
+
retry: {
|
|
35
|
+
factor: options?.fetchRetryFactor,
|
|
36
|
+
maxTimeout: options?.fetchRetryMaxtimeout,
|
|
37
|
+
minTimeout: options?.fetchRetryMintimeout,
|
|
38
|
+
retries: options?.fetchRetries,
|
|
39
|
+
},
|
|
40
|
+
timeout: options?.fetchTimeout,
|
|
41
|
+
});
|
|
42
|
+
const elapsedTime = Date.now() - startTime;
|
|
43
|
+
globalInfo(`GET ${url.href} ${response.status} ${elapsedTime}ms`);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new IdTokenGitHubInvalidResponseError();
|
|
46
|
+
}
|
|
47
|
+
let json;
|
|
48
|
+
try {
|
|
49
|
+
json = await response.json();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw new IdTokenGitHubJsonInterruptedError(error);
|
|
53
|
+
}
|
|
54
|
+
if (!json || typeof json !== 'object' || !('value' in json) || typeof json.value !== 'string') {
|
|
55
|
+
throw new IdTokenGitHubJsonInvalidValueError(json);
|
|
56
|
+
}
|
|
57
|
+
return json.value;
|
|
58
|
+
}
|
|
59
|
+
export class IdTokenError extends PnpmError {
|
|
60
|
+
}
|
|
61
|
+
export class IdTokenGitHubWorkflowIncorrectPermissionsError extends IdTokenError {
|
|
62
|
+
constructor() {
|
|
63
|
+
super('ID_TOKEN_GITHUB_WORKFLOW_INCORRECT_PERMISSIONS', 'Incorrect permissions for idToken within GitHub Workflows');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export class IdTokenGitHubInvalidResponseError extends IdTokenError {
|
|
67
|
+
constructor() {
|
|
68
|
+
super('ID_TOKEN_GITHUB_INVALID_RESPONSE', 'Failed to fetch idToken from GitHub: received an invalid response');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export class IdTokenGitHubJsonInterruptedError extends IdTokenError {
|
|
72
|
+
errorSource;
|
|
73
|
+
constructor(error) {
|
|
74
|
+
super('ID_TOKEN_GITHUB_JSON_INTERRUPTED_ERROR', `Fetching of idToken JSON interrupted: ${displayError(error)}`);
|
|
75
|
+
this.errorSource = error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export class IdTokenGitHubJsonInvalidValueError extends IdTokenError {
|
|
79
|
+
jsonResponse;
|
|
80
|
+
constructor(jsonResponse) {
|
|
81
|
+
super('ID_TOKEN_GITHUB_JSON_INVALID_VALUE', 'Failed to fetch idToken from GitHub: missing or invalid value');
|
|
82
|
+
this.jsonResponse = jsonResponse;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=idToken.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import type { PublishPackedPkgOptions } from '../publishPackedPkg.js';
|
|
3
|
+
export interface ProvenanceCIInfo {
|
|
4
|
+
GITHUB_ACTIONS?: boolean;
|
|
5
|
+
GITLAB?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ProvenanceEnv extends NodeJS.ProcessEnv {
|
|
8
|
+
SIGSTORE_ID_TOKEN?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ProvenanceFetchOptions {
|
|
11
|
+
headers: {
|
|
12
|
+
Accept: 'application/json';
|
|
13
|
+
Authorization: `Bearer ${string}`;
|
|
14
|
+
};
|
|
15
|
+
method: 'GET';
|
|
16
|
+
retry?: {
|
|
17
|
+
factor?: number;
|
|
18
|
+
maxTimeout?: number;
|
|
19
|
+
minTimeout?: number;
|
|
20
|
+
randomize?: boolean;
|
|
21
|
+
retries?: number;
|
|
22
|
+
};
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ProvenanceFetchResponse {
|
|
26
|
+
readonly json: (this: this) => Promise<unknown>;
|
|
27
|
+
readonly ok: boolean;
|
|
28
|
+
readonly status: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ProvenanceContext {
|
|
31
|
+
ciInfo: ProvenanceCIInfo;
|
|
32
|
+
fetch: (url: URL, options: ProvenanceFetchOptions) => Promise<ProvenanceFetchResponse>;
|
|
33
|
+
process: {
|
|
34
|
+
env?: ProvenanceEnv;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export type ProvenanceOptions = Pick<PublishPackedPkgOptions, 'fetchRetries' | 'fetchRetryFactor' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'fetchTimeout'>;
|
|
38
|
+
export interface ProvenanceParams {
|
|
39
|
+
authToken: string;
|
|
40
|
+
context?: ProvenanceContext;
|
|
41
|
+
idToken: string;
|
|
42
|
+
options?: ProvenanceOptions;
|
|
43
|
+
packageName: string;
|
|
44
|
+
registry: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Determine `provenance` for a package from the CI context and the visibility of the package.
|
|
48
|
+
*
|
|
49
|
+
* @throws instances of subclasses of {@link ProvenanceError} which can be converted into warnings and skipped.
|
|
50
|
+
*
|
|
51
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js#L145-L164 for npm's implementation.
|
|
52
|
+
*/
|
|
53
|
+
export declare function determineProvenance({ authToken, idToken, options, packageName, registry, context: { ciInfo: { GITHUB_ACTIONS, GITLAB }, fetch, process: { env } } }: ProvenanceParams): Promise<boolean | undefined>;
|
|
54
|
+
export declare abstract class ProvenanceError extends PnpmError {
|
|
55
|
+
}
|
|
56
|
+
export declare class ProvenanceMalformedIdTokenError extends ProvenanceError {
|
|
57
|
+
readonly idToken: string;
|
|
58
|
+
constructor(idToken: string);
|
|
59
|
+
}
|
|
60
|
+
export declare class ProvenanceInsufficientInformationError extends ProvenanceError {
|
|
61
|
+
constructor();
|
|
62
|
+
}
|
|
63
|
+
export declare class ProvenanceFailedToFetchVisibilityError extends ProvenanceError {
|
|
64
|
+
readonly errorResponse?: {
|
|
65
|
+
code?: string;
|
|
66
|
+
message?: string;
|
|
67
|
+
};
|
|
68
|
+
readonly packageName: string;
|
|
69
|
+
readonly registry: string;
|
|
70
|
+
readonly status: number;
|
|
71
|
+
constructor(errorResponse: ProvenanceFailedToFetchVisibilityError['errorResponse'], status: number, packageName: string, registry: string);
|
|
72
|
+
static createErrorFromFetchResponse(response: ProvenanceFetchResponse, packageName: string, registry: string): Promise<ProvenanceFailedToFetchVisibilityError>;
|
|
73
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import { SHARED_CONTEXT } from '../utils/shared-context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Determine `provenance` for a package from the CI context and the visibility of the package.
|
|
5
|
+
*
|
|
6
|
+
* @throws instances of subclasses of {@link ProvenanceError} which can be converted into warnings and skipped.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js#L145-L164 for npm's implementation.
|
|
9
|
+
*/
|
|
10
|
+
export async function determineProvenance({ authToken, idToken, options, packageName, registry, context: { ciInfo: { GITHUB_ACTIONS, GITLAB }, fetch, process: { env }, } = SHARED_CONTEXT, }) {
|
|
11
|
+
const [headerB64, payloadB64] = idToken.split('.');
|
|
12
|
+
if (!headerB64 || !payloadB64) {
|
|
13
|
+
throw new ProvenanceMalformedIdTokenError(idToken);
|
|
14
|
+
}
|
|
15
|
+
const payloadJson = Buffer.from(payloadB64, 'base64url').toString('utf8');
|
|
16
|
+
const payload = JSON.parse(payloadJson);
|
|
17
|
+
if ((!GITHUB_ACTIONS || payload.repository_visibility !== 'public') &&
|
|
18
|
+
(!GITLAB || payload.project_visibility !== 'public' || !env?.SIGSTORE_ID_TOKEN)) {
|
|
19
|
+
throw new ProvenanceInsufficientInformationError();
|
|
20
|
+
}
|
|
21
|
+
const escapedPackageName = encodeURIComponent(packageName);
|
|
22
|
+
const visibilityUrl = new URL(`/-/package/${escapedPackageName}/visibility`, registry);
|
|
23
|
+
const response = await fetch(visibilityUrl, {
|
|
24
|
+
headers: {
|
|
25
|
+
Accept: 'application/json',
|
|
26
|
+
Authorization: `Bearer ${authToken}`,
|
|
27
|
+
},
|
|
28
|
+
method: 'GET',
|
|
29
|
+
retry: {
|
|
30
|
+
factor: options?.fetchRetryFactor,
|
|
31
|
+
maxTimeout: options?.fetchRetryMaxtimeout,
|
|
32
|
+
minTimeout: options?.fetchRetryMintimeout,
|
|
33
|
+
retries: options?.fetchRetries,
|
|
34
|
+
},
|
|
35
|
+
timeout: options?.fetchTimeout,
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw await ProvenanceFailedToFetchVisibilityError.createErrorFromFetchResponse(response, packageName, registry);
|
|
39
|
+
}
|
|
40
|
+
const visibility = await response.json();
|
|
41
|
+
if (visibility?.public)
|
|
42
|
+
return true;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
export class ProvenanceError extends PnpmError {
|
|
46
|
+
}
|
|
47
|
+
export class ProvenanceMalformedIdTokenError extends ProvenanceError {
|
|
48
|
+
idToken;
|
|
49
|
+
constructor(idToken) {
|
|
50
|
+
super('PROVENANCE_MALFORMED_ID_TOKEN', 'The received idToken is not a valid JWT');
|
|
51
|
+
this.idToken = idToken;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export class ProvenanceInsufficientInformationError extends ProvenanceError {
|
|
55
|
+
constructor() {
|
|
56
|
+
super('PROVENANCE_INSUFFICIENT_INFORMATION', 'The environment does not provide enough information to determine visibility');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export class ProvenanceFailedToFetchVisibilityError extends ProvenanceError {
|
|
60
|
+
errorResponse;
|
|
61
|
+
packageName;
|
|
62
|
+
registry;
|
|
63
|
+
status;
|
|
64
|
+
constructor(errorResponse, status, packageName, registry) {
|
|
65
|
+
let message = 'an unknown error';
|
|
66
|
+
if (errorResponse?.code && errorResponse?.message) {
|
|
67
|
+
message = `${errorResponse.code}: ${errorResponse.message}`;
|
|
68
|
+
}
|
|
69
|
+
else if (errorResponse?.code) {
|
|
70
|
+
message = errorResponse.code;
|
|
71
|
+
}
|
|
72
|
+
else if (errorResponse?.message) {
|
|
73
|
+
message = errorResponse.message;
|
|
74
|
+
}
|
|
75
|
+
super('PROVENANCE_FAILED_TO_FETCH_VISIBILITY', `Failed to fetch visibility for package ${packageName} from registry ${registry} due to ${message} (status code ${status})`);
|
|
76
|
+
this.errorResponse = errorResponse;
|
|
77
|
+
this.status = status;
|
|
78
|
+
this.packageName = packageName;
|
|
79
|
+
this.registry = registry;
|
|
80
|
+
}
|
|
81
|
+
static async createErrorFromFetchResponse(response, packageName, registry) {
|
|
82
|
+
let errorResponse;
|
|
83
|
+
try {
|
|
84
|
+
errorResponse = await response.json();
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
87
|
+
return new ProvenanceFailedToFetchVisibilityError(errorResponse, response.status, packageName, registry);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=provenance.js.map
|