Skip to content

Commit ab0afaf

Browse files
authored
feat(plugin-npm): add npm provenance support (#6750)
## What's the problem this PR addresses? <!-- Describe the rationale of your PR. --> <!-- Link all issues that it closes. (Closes/Resolves #xxxx.) --> Hi! I added support for provenance to `yarn npm publish`. Closes #5430 ## How did you fix it? <!-- A detailed description of your implementation. --> Adapted code from npm to produce a provenance signature in supported CI environment. ## Checklist <!--- Don't worry if you miss something, chores are automatically tested. --> <!--- This checklist exists to help you remember doing the chores when you submit a PR. --> <!--- Put an `x` in all the boxes that apply. --> - [x] I have read the [Contributing Guide](https://yarnpkg.com/advanced/contributing). <!-- See https://yarnpkg.com/advanced/contributing#preparing-your-pr-to-be-released for more details. --> <!-- Check with `yarn version check` and fix with `yarn version check -i` --> - [x] I have set the packages that need to be released for my changes to be effective. <!-- The "Testing chores" workflow validates that your PR follows our guidelines. --> <!-- If it doesn't pass, click on it to see details as to what your PR might be missing. --> - [x] I will check that all automated PR checks pass before the PR gets reviewed. ## Next steps - Update https://github.com/npm/documentation/blob/c2efb649816e27d37b37da2b21200e4c9ade0d17/content/packages-and-modules/securing-your-code/generating-provenance-statements.mdx?plain=1#L124
1 parent 242234c commit ab0afaf

40 files changed

+819
-160
lines changed

.pnp.cjs

+260-103
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

.yarn/versions/e30c5e10.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
releases:
2+
"@yarnpkg/cli": minor
3+
"@yarnpkg/core": minor
4+
"@yarnpkg/plugin-npm": minor
5+
"@yarnpkg/plugin-npm-cli": minor
6+
7+
declined:
8+
- "@yarnpkg/plugin-compat"
9+
- "@yarnpkg/plugin-constraints"
10+
- "@yarnpkg/plugin-dlx"
11+
- "@yarnpkg/plugin-essentials"
12+
- "@yarnpkg/plugin-exec"
13+
- "@yarnpkg/plugin-file"
14+
- "@yarnpkg/plugin-git"
15+
- "@yarnpkg/plugin-github"
16+
- "@yarnpkg/plugin-http"
17+
- "@yarnpkg/plugin-init"
18+
- "@yarnpkg/plugin-interactive-tools"
19+
- "@yarnpkg/plugin-link"
20+
- "@yarnpkg/plugin-nm"
21+
- "@yarnpkg/plugin-pack"
22+
- "@yarnpkg/plugin-patch"
23+
- "@yarnpkg/plugin-pnp"
24+
- "@yarnpkg/plugin-pnpm"
25+
- "@yarnpkg/plugin-stage"
26+
- "@yarnpkg/plugin-typescript"
27+
- "@yarnpkg/plugin-version"
28+
- "@yarnpkg/plugin-workspace-tools"
29+
- "@yarnpkg/builder"
30+
- "@yarnpkg/doctor"
31+
- "@yarnpkg/extensions"
32+
- "@yarnpkg/nm"
33+
- "@yarnpkg/pnpify"
34+
- "@yarnpkg/sdks"

.yarnrc.yml

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pnpZipBackend: js
1919

2020
npmPublishAccess: public
2121

22+
npmPublishProvenance: true
23+
2224
packageExtensions:
2325
"@codemirror/lang-html@*":
2426
dependencies:

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
},
2929
"resolutions": {
3030
"ink@^3.2.0": "patch:ink@npm%3A3.2.0#~/.yarn/patches/ink-npm-3.2.0-2f1df5b094.patch",
31-
"yoga-layout-prebuilt": "patch:[email protected]#./.yarn/patches/yoga-layout-prebuilt.patch"
31+
"yoga-layout-prebuilt": "patch:[email protected]#./.yarn/patches/yoga-layout-prebuilt.patch",
32+
"make-fetch-happen@npm:^14.0.1": "portal:packages/make-fetch-smaller",
33+
"make-fetch-happen@npm:^14.0.2": "portal:packages/make-fetch-smaller"
3234
},
3335
"dependenciesMeta": {
3436
"core-js": {

packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx

+41
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,44 @@ Our research showed that even our power users aren't always aware of some of the
444444
When enabled, the `enableOfflineMode` flag tells Yarn to ignore remote registries and only pull data from its internal caches. This is a handy mode when working from within network-constrained environments such as planes or trains.
445445

446446
To leave the offline work mode, check how it got enabled by running `yarn config --why`. If `<environment>`, run `unset YARN_ENABLE_OFFLINE_MODE` in your terminal. Otherwise, remove the `enableOfflineMode` flag from the relevant `.yarnrc.yml` files.
447+
448+
## YN0091 - `INVALID_PROVENANCE_ENVIRONMENT`
449+
450+
This error is triggered when the [provenance statement](https://docs.npmjs.com/generating-provenance-statements) cannot be generated in the current environment. GitHub Actions and GitLab CI are the only supported environments at the moment, and this error is triggered when either running in another environment or when credentials are missing.
451+
452+
On GitHub Actions, you need to grant the `write-id` permission to your workflow. Here is an example of how to do that:
453+
454+
```yaml
455+
name: Publish Package to npmjs
456+
on:
457+
push:
458+
branches: [main]
459+
jobs:
460+
publish:
461+
runs-on: ubuntu-latest # Must run on GitHub-hosted runners
462+
permissions:
463+
id-token: write
464+
steps:
465+
- uses: actions/checkout@v4
466+
- run: npm install -g corepack && corepack enable
467+
- run: yarn && yarn build
468+
- run: yarn config set npmAuthToken '${{ secrets.NPM_TOKEN }}'
469+
- run: yarn publish --provenance --tolerate-republish
470+
```
471+
472+
On GitLab CI, you need to produce a `SIGSTORE_ID_TOKEN` for your workflow. Here is an example of how to do that:
473+
474+
```yaml
475+
publish:
476+
image: 'node:22'
477+
rules:
478+
- if: '$CI_COMMIT_BRANCH == "main"'
479+
id_tokens:
480+
SIGSTORE_ID_TOKEN:
481+
aud: sigstore
482+
script:
483+
- npm install -g corepack && corepack enable
484+
- yarn && yarn build
485+
- yarn config set npmAuthToken $NPM_TOKEN
486+
- yarn publish --provenance --tolerate-republish
487+
```

packages/docusaurus/static/configuration/manifest.json

+5
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@
362362
"format": "uri-reference",
363363
"examples": ["./build/index.mjs"]
364364
},
365+
"provenance": {
366+
"title": "Define whether to produce a provenance statement for the package when publishing. Overrides all other provenance settings.",
367+
"type": "boolean",
368+
"examples": [true]
369+
},
365370
"registry": {
366371
"description": "If present, will replace whatever registry is defined in the configuration when the package is about to be pushed to a remote location.",
367372
"type": "string",

packages/docusaurus/static/configuration/yarnrc.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -529,9 +529,16 @@
529529
"_package": "@yarnpkg/plugin-npm-cli",
530530
"type": "string",
531531
"title": "Define the default access to use when publishing packages to the npm registry.",
532-
"description": "Valid values are `public` and `restricted`, but `restricted` usually requires to register for a paid plan (this is up to the registry you use). Can be overridden on a per-package basis using the `publishConfig.access` field.",
532+
"description": "Valid values are `public` and `restricted`, but `restricted` usually requires to register for a paid plan (this is up to the registry you use). Can be overridden on a per-package basis using the [`publishConfig.access`](manifest#publishConfig.access) field.",
533533
"enum": ["public", "restricted"]
534534
},
535+
"npmPublishProvenance": {
536+
"_package": "@yarnpkg/plugin-npm-cli",
537+
"title": "Define whether to attach a provenance statement when publishing packages to the npm registry.",
538+
"description": "If true, Yarn will generate and publish the provenance information when publishing packages. Can be overridden on a per-package basis using the [`publishConfig.provenance`](manifest#publishConfig.provenance) field.",
539+
"type": "boolean",
540+
"default": false
541+
},
535542
"npmAuditExcludePackages": {
536543
"_package": "@yarnpkg/plugin-npm-cli",
537544
"title": "Array of package name glob patterns to exclude from `yarn npm audit`.",

packages/make-fetch-smaller/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# make-fetch-smaller
2+
3+
This package is a drop-in replacement for `make-fetch-happen`, but uses Node.js native `fetch` instead of pulling [79 dependencies.](https://node-modules.dev/graph#install=make-fetch-happen)
4+
5+
It is used by the [sigstore](https://www.npmjs.com/package/sigstore) package and its dependencies to produce the provenance statement for packages published to the npm registry with `yarn npm publish --provenance`.

packages/make-fetch-smaller/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// eslint-disable-next-line
2+
module.exports = fetch;
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "make-fetch-smaller",
3+
"private": true,
4+
"repository": {
5+
"type": "git",
6+
"url": "git+https://github.com/yarnpkg/berry.git",
7+
"directory": "packages/make-fetch-smaller"
8+
},
9+
"license": "BSD-2-Clause",
10+
"type": "commonjs",
11+
"engines": {
12+
"node": ">=18.12.0"
13+
}
14+
}

packages/plugin-npm-cli/sources/commands/npm/publish.ts

+22
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export default class NpmPublishCommand extends BaseCommand {
4242
description: `The OTP token to use with the command`,
4343
});
4444

45+
provenance = Option.Boolean(`--provenance`, false, {
46+
description: `Generate provenance for the package. Only available in GitHub Actions and GitLab CI. Can be set globally through the \`npmPublishProvenance\` setting or the \`YARN_NPM_CONFIG_PROVENANCE\` environment variable, or per-package through the \`publishConfig.provenance\` field in package.json.`,
47+
});
48+
4549
async execute() {
4650
const configuration = await Configuration.find(this.context.cwd, this.context.plugins);
4751
const {project, workspace} = await Project.find(configuration, this.context.cwd);
@@ -102,11 +106,29 @@ export default class NpmPublishCommand extends BaseCommand {
102106
const buffer = await miscUtils.bufferStream(pack);
103107

104108
const gitHead = await npmPublishUtils.getGitHead(workspace.cwd);
109+
110+
let provenance = false;
111+
if (workspace.manifest.publishConfig && `provenance` in workspace.manifest.publishConfig) {
112+
provenance = Boolean(workspace.manifest.publishConfig.provenance);
113+
if (provenance) {
114+
report.reportInfo(null, `Generating provenance statement because \`publishConfig.provenance\` field is set.`);
115+
} else {
116+
report.reportInfo(null, `Skipping provenance statement because \`publishConfig.provenance\` field is set to false.`);
117+
}
118+
} else if (this.provenance) {
119+
provenance = true;
120+
report.reportInfo(null, `Generating provenance statement because \`--provenance\` flag is set.`);
121+
} else if (configuration.get(`npmPublishProvenance`)) {
122+
provenance = true;
123+
report.reportInfo(null, `Generating provenance statement because \`npmPublishProvenance\` setting is set.`);
124+
}
125+
105126
const body = await npmPublishUtils.makePublishBody(workspace, buffer, {
106127
access: this.access,
107128
tag: this.tag,
108129
registry,
109130
gitHead,
131+
provenance,
110132
});
111133

112134
await npmHttpUtils.put(npmHttpUtils.getIdentUrl(ident), body, {

packages/plugin-npm-cli/sources/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export {NpmWhoamiCommand};
2727
declare module '@yarnpkg/core' {
2828
interface ConfigurationValueMap {
2929
npmPublishAccess: string | null;
30+
npmPublishProvenance: boolean;
3031
npmAuditExcludePackages: Array<string>;
3132
npmAuditIgnoreAdvisories: Array<string>;
3233
}
@@ -39,6 +40,11 @@ const plugin: Plugin = {
3940
type: SettingsType.STRING,
4041
default: null,
4142
},
43+
npmPublishProvenance: {
44+
description: `Whether to generate provenance for the published packages`,
45+
type: SettingsType.BOOLEAN,
46+
default: false,
47+
},
4248
npmAuditExcludePackages: {
4349
description: `Array of glob patterns of packages to exclude from npm audit`,
4450
type: SettingsType.STRING,

packages/plugin-npm/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ This plugin adds support for downloading packages from the npm registry.
55
## Install
66

77
This plugin is included by default in Yarn.
8+
9+
## Attribution
10+
11+
Provenance code adapted from [npm/cli](https://github.com/npm/cli/blob/04f53ce13201b460123067d7153f1681342548e1/workspaces/libnpmpublish/lib/provenance.js), under [ISC license](https://github.com/npm/cli/blob/04f53ce13201b460123067d7153f1681342548e1/workspaces/libnpmpublish/LICENSE).

packages/plugin-npm/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"enquirer": "^2.3.6",
1313
"lodash": "^4.17.15",
1414
"semver": "^7.1.2",
15-
"ssri": "^6.0.1",
15+
"sigstore": "^3.1.0",
16+
"ssri": "^12.0.0",
1617
"tslib": "^2.4.0"
1718
},
1819
"peerDependencies": {
@@ -22,7 +23,7 @@
2223
"devDependencies": {
2324
"@types/lodash": "^4.14.136",
2425
"@types/semver": "^7.1.0",
25-
"@types/ssri": "^6.0.1",
26+
"@types/ssri": "^7.1.5",
2627
"@yarnpkg/core": "workspace:^",
2728
"@yarnpkg/plugin-pack": "workspace:^"
2829
},

0 commit comments

Comments
 (0)