@anolilab/semantic-release-pnpm 3.2.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +5 -1
- package/dist/index.js +91 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## @anolilab/semantic-release-pnpm [3.2.2](https://github.com/anolilab/semantic-release/compare/@anolilab/semantic-release-pnpm@3.2.1...@anolilab/semantic-release-pnpm@3.2.2) (2025-12-05)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* enhance OIDC authentication handling in verifyAuth ([0141c07](https://github.com/anolilab/semantic-release/commit/0141c07cc4a0457b4aa01ecade582c40d17ef205))
|
|
6
|
+
|
|
7
|
+
## @anolilab/semantic-release-pnpm [3.2.1](https://github.com/anolilab/semantic-release/compare/@anolilab/semantic-release-pnpm@3.2.0...@anolilab/semantic-release-pnpm@3.2.1) (2025-12-05)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* implement caching for whoami results in verifyAuth ([e8a482f](https://github.com/anolilab/semantic-release/commit/e8a482fec317dc829f9a373804b99110abafd1b2))
|
|
12
|
+
|
|
1
13
|
## @anolilab/semantic-release-pnpm [3.2.0](https://github.com/anolilab/semantic-release/compare/@anolilab/semantic-release-pnpm@3.1.0...@anolilab/semantic-release-pnpm@3.2.0) (2025-12-04)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ The plugin can be configured in the [**semantic-release** configuration file](ht
|
|
|
53
53
|
|
|
54
54
|
| Step | Description |
|
|
55
55
|
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
-
| `verifyConditions` | Verify the presence of the `NPM_TOKEN` environment variable, or an `.npmrc` file, and verify the authentication method is valid. |
|
|
56
|
+
| `verifyConditions` | Verify the presence of the `NPM_TOKEN` environment variable, or an `.npmrc` file, and verify the authentication method is valid. Results are cached per registry/auth combination to prevent throttling in monorepos. |
|
|
57
57
|
| `prepare` | Update the `package.json` version and [create](https://docs.npmjs.com/cli/pack) the npm package tarball. |
|
|
58
58
|
| `addChannel` | [Add a release to a dist-tag](https://docs.npmjs.com/cli/dist-tag). |
|
|
59
59
|
| `publish` | [Publish the npm package](https://docs.npmjs.com/cli/publish) to the registry. |
|
|
@@ -76,11 +76,15 @@ When publishing to the [official registry](https://registry.npmjs.org/), it is r
|
|
|
76
76
|
|
|
77
77
|
> [!IMPORTANT]
|
|
78
78
|
> **First-time releases with OIDC**: npm requires a package to exist before you can configure OIDC trusted publishing. If you're releasing a package for the first time with OIDC, you have two options:
|
|
79
|
+
>
|
|
79
80
|
> 1. Publish a dummy version manually first (e.g., `pnpm publish --tag dummy`), then configure OIDC trusted publishing, and then use semantic-release for subsequent releases.
|
|
80
81
|
> 2. Use the [`setup-npm-trusted-publish`](https://github.com/azu/setup-npm-trusted-publish) tool to automatically create and publish a placeholder package for OIDC setup purposes.
|
|
81
82
|
>
|
|
82
83
|
> After the initial package exists, you can configure OIDC trusted publishing at `https://www.npmjs.com/package/<package-name>/access` and then use semantic-release for all future releases.
|
|
83
84
|
|
|
85
|
+
> [!TIP]
|
|
86
|
+
> **Monorepo Performance**: The plugin automatically caches authentication verification results per registry/auth token combination. This prevents throttling when verifying multiple packages in monorepos, as `pnpm whoami` is only called once per unique registry/authentication context rather than once per package.
|
|
87
|
+
|
|
84
88
|
##### Trusted publishing from GitHub Actions
|
|
85
89
|
|
|
86
90
|
To leverage trusted publishing and publish with provenance from GitHub Actions, the `id-token: write` permission is required to be enabled on the job:
|
package/dist/index.js
CHANGED
|
@@ -26,10 +26,10 @@ import { rc } from '@anolilab/rc';
|
|
|
26
26
|
import normalizeUrl from 'normalize-url';
|
|
27
27
|
import { findPackageJson, getPackageManagerVersion } from '@visulima/package';
|
|
28
28
|
import SemanticReleaseError from '@semantic-release/error';
|
|
29
|
-
import { getIDToken } from '@actions/core';
|
|
30
|
-
import envCi from 'env-ci';
|
|
31
29
|
import { stringify } from 'ini';
|
|
32
30
|
import getAuthToken from 'registry-auth-token';
|
|
31
|
+
import { getIDToken } from '@actions/core';
|
|
32
|
+
import envCi from 'env-ci';
|
|
33
33
|
const {
|
|
34
34
|
URL
|
|
35
35
|
} = __cjs_getBuiltinModule("node:url");
|
|
@@ -502,6 +502,34 @@ email = \${NPM_EMAIL}`
|
|
|
502
502
|
};
|
|
503
503
|
|
|
504
504
|
const debug$2 = dbg("semantic-release-pnpm:verify-auth");
|
|
505
|
+
const whoamiCache = /* @__PURE__ */ new Map();
|
|
506
|
+
const getCacheKey = (registry, context) => {
|
|
507
|
+
const normalizedRegistry = normalizeUrl(registry);
|
|
508
|
+
const {
|
|
509
|
+
env: { NPM_PASSWORD, NPM_TOKEN, NPM_USERNAME }
|
|
510
|
+
} = context;
|
|
511
|
+
if (NPM_TOKEN) {
|
|
512
|
+
const tokenId = NPM_TOKEN.length > 12 ? `${NPM_TOKEN.slice(0, 8)}...${NPM_TOKEN.slice(-4)}` : NPM_TOKEN.slice(0, 8);
|
|
513
|
+
return `${normalizedRegistry}:token:${tokenId}`;
|
|
514
|
+
}
|
|
515
|
+
if (NPM_USERNAME && NPM_PASSWORD) {
|
|
516
|
+
return `${normalizedRegistry}:user:${NPM_USERNAME}`;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
const { config } = rc("npm", {
|
|
520
|
+
config: context.env.NPM_CONFIG_USERCONFIG ?? resolve(context.cwd, ".npmrc"),
|
|
521
|
+
cwd: context.cwd,
|
|
522
|
+
defaults: { registry: OFFICIAL_REGISTRY }
|
|
523
|
+
});
|
|
524
|
+
const token = getAuthToken(registry, { npmrc: config });
|
|
525
|
+
if (token) {
|
|
526
|
+
const tokenId = token.length > 12 ? `${token.slice(0, 8)}...${token.slice(-4)}` : token.slice(0, 8);
|
|
527
|
+
return `${normalizedRegistry}:token:${tokenId}`;
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
return `${normalizedRegistry}:default`;
|
|
532
|
+
};
|
|
505
533
|
const isConnectionError = (error) => {
|
|
506
534
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
507
535
|
const errorCode = error?.code || "";
|
|
@@ -509,32 +537,55 @@ const isConnectionError = (error) => {
|
|
|
509
537
|
return isTimedOut || errorCode === "ECONNREFUSED" || errorCode === "ETIMEDOUT" || errorMessage.includes("ECONNREFUSED") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("getaddrinfo ENOTFOUND") || errorMessage.includes("timed out");
|
|
510
538
|
};
|
|
511
539
|
const verifyAuthContextAgainstRegistry = async (npmrc, registry, context) => {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
timeout: 5e3
|
|
523
|
-
// 5 second timeout to prevent hanging when registry is unavailable
|
|
524
|
-
});
|
|
525
|
-
if (whoamiResult.stdout) {
|
|
526
|
-
stdout.write(whoamiResult.stdout);
|
|
540
|
+
const cacheKey = getCacheKey(registry, context);
|
|
541
|
+
if (whoamiCache.has(cacheKey)) {
|
|
542
|
+
debug$2(`Using cached whoami result for registry "${registry}"`);
|
|
543
|
+
const cachedResult = whoamiCache.get(cacheKey);
|
|
544
|
+
try {
|
|
545
|
+
await cachedResult;
|
|
546
|
+
return;
|
|
547
|
+
} catch {
|
|
548
|
+
debug$2(`Cached whoami result failed, retrying for registry "${registry}"`);
|
|
549
|
+
whoamiCache.delete(cacheKey);
|
|
527
550
|
}
|
|
528
|
-
|
|
529
|
-
|
|
551
|
+
}
|
|
552
|
+
const verificationPromise = (async () => {
|
|
553
|
+
const { cwd, env, logger, stderr, stdout } = context;
|
|
554
|
+
try {
|
|
555
|
+
logger.log(`Running "pnpm whoami" to verify authentication on registry "${registry}"`);
|
|
556
|
+
const whoamiResult = await execa("pnpm", ["whoami", "--registry", registry], {
|
|
557
|
+
cwd,
|
|
558
|
+
env: {
|
|
559
|
+
...env,
|
|
560
|
+
NPM_CONFIG_USERCONFIG: npmrc
|
|
561
|
+
},
|
|
562
|
+
preferLocal: true,
|
|
563
|
+
timeout: 5e3
|
|
564
|
+
// 5 second timeout to prevent hanging when registry is unavailable
|
|
565
|
+
});
|
|
566
|
+
if (whoamiResult.stdout) {
|
|
567
|
+
stdout.write(whoamiResult.stdout);
|
|
568
|
+
}
|
|
569
|
+
if (whoamiResult.stderr) {
|
|
570
|
+
stderr.write(whoamiResult.stderr);
|
|
571
|
+
}
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (isConnectionError(error)) {
|
|
574
|
+
const semanticError2 = getError("EINVALIDNPMAUTH", { registry });
|
|
575
|
+
throw new AggregateError([semanticError2], semanticError2.message);
|
|
576
|
+
}
|
|
577
|
+
const semanticError = getError("EINVALIDNPMTOKEN", { registry });
|
|
578
|
+
throw new AggregateError([semanticError], semanticError.message);
|
|
530
579
|
}
|
|
580
|
+
})();
|
|
581
|
+
whoamiCache.set(cacheKey, verificationPromise);
|
|
582
|
+
try {
|
|
583
|
+
await verificationPromise;
|
|
531
584
|
} catch (error) {
|
|
532
585
|
if (isConnectionError(error)) {
|
|
533
|
-
|
|
534
|
-
throw new AggregateError([semanticError2], semanticError2.message);
|
|
586
|
+
whoamiCache.delete(cacheKey);
|
|
535
587
|
}
|
|
536
|
-
|
|
537
|
-
throw new AggregateError([semanticError], semanticError.message);
|
|
588
|
+
throw error;
|
|
538
589
|
}
|
|
539
590
|
};
|
|
540
591
|
const isAuthErrorMessage = (message) => message.includes("requires you to be logged in") || message.includes("authentication") || message.includes("Unauthorized") || message.includes("401") || message.includes("403");
|
|
@@ -593,10 +644,24 @@ const verifyAuth = async (npmrc, package_, context, pkgRoot) => {
|
|
|
593
644
|
if (package_.name) {
|
|
594
645
|
debug$2(`Checking OIDC trusted publishing for package "${package_.name}" on registry "${registry}"`);
|
|
595
646
|
if (await oidcContext(registry, package_, context)) {
|
|
596
|
-
debug$2("OIDC trusted publishing verified successfully,
|
|
597
|
-
|
|
647
|
+
debug$2("OIDC trusted publishing verified successfully, exchanging token for npmrc");
|
|
648
|
+
const oidcToken = await tokenExchange({ name: package_.name }, context);
|
|
649
|
+
if (oidcToken) {
|
|
650
|
+
const { config } = rc("npm", {
|
|
651
|
+
config: context.env.NPM_CONFIG_USERCONFIG ?? resolve(context.cwd, ".npmrc"),
|
|
652
|
+
cwd: context.cwd,
|
|
653
|
+
defaults: { registry: OFFICIAL_REGISTRY }
|
|
654
|
+
});
|
|
655
|
+
await writeFile(npmrc, `${Object.keys(config).length > 0 ? `${stringify(config)}
|
|
656
|
+
` : ""}${nerfDart(registry)}:_authToken = ${oidcToken}`);
|
|
657
|
+
debug$2(`Wrote OIDC-exchanged token to ${npmrc} for use during publish`);
|
|
658
|
+
context.logger.log(`OIDC trusted publishing configured for package "${package_.name}"`);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
debug$2("OIDC token exchange failed, falling back to NPM_TOKEN authentication");
|
|
662
|
+
} else {
|
|
663
|
+
debug$2("OIDC trusted publishing not available, falling back to NPM_TOKEN authentication");
|
|
598
664
|
}
|
|
599
|
-
debug$2("OIDC trusted publishing not available, falling back to NPM_TOKEN authentication");
|
|
600
665
|
} else {
|
|
601
666
|
debug$2("Package name not found, skipping OIDC check and using NPM_TOKEN authentication");
|
|
602
667
|
}
|