@vscode/vsce 3.9.3-2 → 3.9.3-3
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/README.md +25 -0
- package/dist/vsce.d.ts +4 -0
- package/out/main.js +7 -2
- package/out/oidc.js +127 -0
- package/out/publish.js +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,31 @@ Or you can also set them in the `package.json`, so that you avoid having to rety
|
|
|
58
58
|
}
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
### Trusted publishing
|
|
62
|
+
|
|
63
|
+
`vsce publish --oidc` publishes from GitHub Actions without storing a Personal Access Token. Configure a trusted
|
|
64
|
+
publishing policy for the repository and workflow on the Visual Studio Marketplace, then grant the workflow permission
|
|
65
|
+
to request an OIDC token:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
jobs:
|
|
69
|
+
publish:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
permissions:
|
|
72
|
+
contents: read
|
|
73
|
+
id-token: write
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
- uses: actions/setup-node@v4
|
|
77
|
+
with:
|
|
78
|
+
node-version: 22
|
|
79
|
+
- run: npm ci
|
|
80
|
+
- run: npx @vscode/vsce publish --oidc
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
OIDC publishing requests a GitHub Actions token for the `marketplace.visualstudio.com` audience and exchanges it for a
|
|
84
|
+
short-lived Marketplace credential. It does not fall back to a PAT when token acquisition or exchange fails.
|
|
85
|
+
|
|
61
86
|
## Development
|
|
62
87
|
|
|
63
88
|
First clone this repository, then:
|
package/dist/vsce.d.ts
CHANGED
|
@@ -174,6 +174,10 @@ export declare interface IPublishOptions {
|
|
|
174
174
|
*/
|
|
175
175
|
readonly pat?: string;
|
|
176
176
|
readonly azureCredential?: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Use OpenID Connect trusted publishing to acquire a short-lived Marketplace credential.
|
|
179
|
+
*/
|
|
180
|
+
readonly oidc?: boolean;
|
|
177
181
|
readonly allowProposedApi?: boolean;
|
|
178
182
|
readonly noVerify?: boolean;
|
|
179
183
|
readonly allowProposedApis?: string[];
|
package/out/main.js
CHANGED
|
@@ -173,6 +173,10 @@ module.exports = function (argv) {
|
|
|
173
173
|
.description('Publishes an extension')
|
|
174
174
|
.option('-p, --pat <token>', 'Personal Access Token (defaults to VSCE_PAT environment variable)', process.env['VSCE_PAT'])
|
|
175
175
|
.option('--azure-credential', 'Use Microsoft Entra ID for authentication')
|
|
176
|
+
.addOption(new commander_1.Option('--oidc', 'Use OpenID Connect trusted publishing for authentication').conflicts([
|
|
177
|
+
'pat',
|
|
178
|
+
'azureCredential',
|
|
179
|
+
]))
|
|
176
180
|
.option('-t, --target <targets...>', `Target architectures. Valid targets: ${ValidTargets}`)
|
|
177
181
|
.option('--ignore-other-target-folders', `Ignore other target folders. Valid only when --target <target> is provided.`)
|
|
178
182
|
.option('--readme-path <path>', 'Path to README file (defaults to README.md)')
|
|
@@ -209,9 +213,10 @@ module.exports = function (argv) {
|
|
|
209
213
|
.option('--skip-duplicate', 'Fail silently if version already exists on the marketplace')
|
|
210
214
|
.option('--skip-license', 'Allow publishing without license file')
|
|
211
215
|
.option('--follow-symlinks', 'Recurse into symlinked directories instead of treating them as files')
|
|
212
|
-
.action((version, { pat, azureCredential, target, ignoreOtherTargetFolders, readmePath, changelogPath, message, gitTagVersion, updatePackageJson, packagePath, manifestPath, signaturePath, sigzipPath, githubBranch, gitlabBranch, baseContentUrl, baseImagesUrl, yarn, verify, noVerify, allowProposedApis, allowAllProposedApis, allowPackageSecrets, allowPackageAllSecrets, allowPackageEnvFile, ignoreFile, dependencies, preRelease, allowStarActivation, allowMissingRepository, allowUnusedFilesPattern, skipDuplicate, skipLicense, signTool, followSymlinks, }) => main((0, publish_1.publish)({
|
|
213
|
-
pat,
|
|
216
|
+
.action((version, { pat, azureCredential, oidc, target, ignoreOtherTargetFolders, readmePath, changelogPath, message, gitTagVersion, updatePackageJson, packagePath, manifestPath, signaturePath, sigzipPath, githubBranch, gitlabBranch, baseContentUrl, baseImagesUrl, yarn, verify, noVerify, allowProposedApis, allowAllProposedApis, allowPackageSecrets, allowPackageAllSecrets, allowPackageEnvFile, ignoreFile, dependencies, preRelease, allowStarActivation, allowMissingRepository, allowUnusedFilesPattern, skipDuplicate, skipLicense, signTool, followSymlinks, }) => main((0, publish_1.publish)({
|
|
217
|
+
pat: oidc ? undefined : pat,
|
|
214
218
|
azureCredential,
|
|
219
|
+
oidc,
|
|
215
220
|
version,
|
|
216
221
|
targets: target,
|
|
217
222
|
ignoreOtherTargetFolders,
|
package/out/oidc.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OIDC_AUDIENCE = void 0;
|
|
4
|
+
exports.getOIDCCredential = getOIDCCredential;
|
|
5
|
+
const util_1 = require("./util");
|
|
6
|
+
exports.OIDC_AUDIENCE = 'marketplace.visualstudio.com';
|
|
7
|
+
class GitHubActionsOIDCTokenProvider {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.name = 'GitHub Actions';
|
|
10
|
+
}
|
|
11
|
+
isAvailable(environment) {
|
|
12
|
+
return environment['GITHUB_ACTIONS']?.toLowerCase() === 'true';
|
|
13
|
+
}
|
|
14
|
+
async getToken(audience, { environment, request }) {
|
|
15
|
+
const requestUrl = environment['ACTIONS_ID_TOKEN_REQUEST_URL'];
|
|
16
|
+
const requestToken = environment['ACTIONS_ID_TOKEN_REQUEST_TOKEN'];
|
|
17
|
+
if (!requestUrl || !requestToken) {
|
|
18
|
+
throw new Error('GitHub Actions did not provide an OIDC token request URL and token. Add `permissions: id-token: write` to the workflow or job.');
|
|
19
|
+
}
|
|
20
|
+
let tokenUrl;
|
|
21
|
+
try {
|
|
22
|
+
tokenUrl = new URL(requestUrl);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error('GitHub Actions provided an invalid ACTIONS_ID_TOKEN_REQUEST_URL.');
|
|
26
|
+
}
|
|
27
|
+
tokenUrl.searchParams.set('audience', audience);
|
|
28
|
+
const result = await requestJSON('GitHub Actions OIDC token request', tokenUrl.toString(), request, {
|
|
29
|
+
method: 'GET',
|
|
30
|
+
headers: {
|
|
31
|
+
Accept: 'application/json',
|
|
32
|
+
Authorization: `Bearer ${requestToken}`,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (!isRecord(result) || typeof result.value !== 'string' || !result.value) {
|
|
36
|
+
throw new Error('GitHub Actions OIDC token request returned an invalid response without a token.');
|
|
37
|
+
}
|
|
38
|
+
return result.value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const oidcTokenProviders = [new GitHubActionsOIDCTokenProvider()];
|
|
42
|
+
async function getOIDCCredential(publisherName, options = {}) {
|
|
43
|
+
const environment = options.environment ?? process.env;
|
|
44
|
+
const request = options.request ?? defaultRequest;
|
|
45
|
+
const provider = oidcTokenProviders.find(candidate => candidate.isAvailable(environment));
|
|
46
|
+
if (!provider) {
|
|
47
|
+
throw new Error('No supported OIDC provider was detected. OIDC publishing currently supports GitHub Actions only.');
|
|
48
|
+
}
|
|
49
|
+
const oidcToken = await provider.getToken(exports.OIDC_AUDIENCE, { environment, request });
|
|
50
|
+
return await exchangeOIDCToken(publisherName, oidcToken, options.marketplaceUrl ?? (0, util_1.getMarketplaceUrl)(), request);
|
|
51
|
+
}
|
|
52
|
+
async function exchangeOIDCToken(publisherName, oidcToken, marketplaceUrl, request) {
|
|
53
|
+
const result = await requestJSON('Marketplace OIDC token exchange', `${marketplaceUrl.replace(/\/$/, '')}/_apis/gallery/token`, request, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
Accept: 'application/json',
|
|
57
|
+
Authorization: `Bearer ${oidcToken}`,
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
'User-Agent': 'vsce',
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({ publisherName }),
|
|
62
|
+
});
|
|
63
|
+
if (!isRecord(result) || typeof result.credential !== 'string' || !result.credential) {
|
|
64
|
+
throw new Error('Marketplace OIDC token exchange returned an invalid response without a credential.');
|
|
65
|
+
}
|
|
66
|
+
return result.credential;
|
|
67
|
+
}
|
|
68
|
+
async function defaultRequest(url, request) {
|
|
69
|
+
const response = await fetch(url, request);
|
|
70
|
+
return {
|
|
71
|
+
statusCode: response.status,
|
|
72
|
+
statusMessage: response.statusText,
|
|
73
|
+
readBody: () => response.text(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function requestJSON(operation, url, request, init) {
|
|
77
|
+
let response;
|
|
78
|
+
try {
|
|
79
|
+
response = await request(url, init);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw new Error(`${operation} failed: ${getErrorMessage(error)}`);
|
|
83
|
+
}
|
|
84
|
+
let body;
|
|
85
|
+
try {
|
|
86
|
+
body = await response.readBody();
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw new Error(`${operation} failed while reading the response: ${getErrorMessage(error)}`);
|
|
90
|
+
}
|
|
91
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
92
|
+
const status = `${response.statusCode}${response.statusMessage ? ` ${response.statusMessage}` : ''}`;
|
|
93
|
+
throw new Error(`${operation} failed with ${status}${getResponseDetails(body)}`);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(body);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new Error(`${operation} returned an invalid JSON response.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function getResponseDetails(body) {
|
|
103
|
+
const trimmedBody = body.trim();
|
|
104
|
+
if (!trimmedBody) {
|
|
105
|
+
return '.';
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const parsed = JSON.parse(trimmedBody);
|
|
109
|
+
if (isRecord(parsed)) {
|
|
110
|
+
const message = parsed.message ?? parsed.error_description ?? parsed.error;
|
|
111
|
+
if (typeof message === 'string' && message) {
|
|
112
|
+
return `: ${message}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Use the plain response body below.
|
|
118
|
+
}
|
|
119
|
+
return `: ${trimmedBody.slice(0, 500)}`;
|
|
120
|
+
}
|
|
121
|
+
function getErrorMessage(error) {
|
|
122
|
+
return error instanceof Error ? error.message : String(error);
|
|
123
|
+
}
|
|
124
|
+
function isRecord(value) {
|
|
125
|
+
return typeof value === 'object' && value !== null;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=oidc.js.map
|
package/out/publish.js
CHANGED
|
@@ -53,8 +53,10 @@ const form_data_1 = __importDefault(require("form-data"));
|
|
|
53
53
|
const path_1 = require("path");
|
|
54
54
|
const cockatiel_1 = require("cockatiel");
|
|
55
55
|
const auth_1 = require("./auth");
|
|
56
|
+
const oidc_1 = require("./oidc");
|
|
56
57
|
const tmpName = (0, util_1.promisify)(tmp.tmpName);
|
|
57
58
|
async function publish(options = {}) {
|
|
59
|
+
validateAuthenticationOptions(options);
|
|
58
60
|
if (options.packagePath) {
|
|
59
61
|
if (options.version) {
|
|
60
62
|
throw new Error(`Both options not supported simultaneously: 'packagePath' and 'version'.`);
|
|
@@ -260,6 +262,10 @@ function validateManifestForPublishing(manifest, options) {
|
|
|
260
262
|
return { ...manifest, publisher: (0, validation_1.validatePublisher)(manifest.publisher) };
|
|
261
263
|
}
|
|
262
264
|
async function getPAT(publisher, options) {
|
|
265
|
+
validateAuthenticationOptions(options);
|
|
266
|
+
if (options.oidc) {
|
|
267
|
+
return await (0, oidc_1.getOIDCCredential)(publisher);
|
|
268
|
+
}
|
|
263
269
|
if (options.pat) {
|
|
264
270
|
return options.pat;
|
|
265
271
|
}
|
|
@@ -268,4 +274,15 @@ async function getPAT(publisher, options) {
|
|
|
268
274
|
}
|
|
269
275
|
return (await (0, store_1.getPublisher)(publisher)).pat;
|
|
270
276
|
}
|
|
277
|
+
function validateAuthenticationOptions(options) {
|
|
278
|
+
if (!options.oidc) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (options.pat) {
|
|
282
|
+
throw new Error(`The '--oidc' and '--pat' options cannot be used together.`);
|
|
283
|
+
}
|
|
284
|
+
if (options.azureCredential) {
|
|
285
|
+
throw new Error(`The '--oidc' and '--azure-credential' options cannot be used together.`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
271
288
|
//# sourceMappingURL=publish.js.map
|