@vscode/vsce 3.9.3-1 → 3.9.3-11

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 CHANGED
@@ -15,14 +15,7 @@ Read the [**Documentation**](https://code.visualstudio.com/api/working-with-exte
15
15
 
16
16
  ### Linux
17
17
 
18
- In order to save credentials safely, this project uses [`keytar`](https://www.npmjs.com/package/keytar) which uses `libsecret`, which you may need to install before publishing extensions. Setting the `VSCE_STORE=file` environment variable will revert back to the file credential store. Using the `VSCE_PAT` environment variable will also avoid using `keytar`.
19
-
20
- Depending on your distribution, you will need to run the following command:
21
-
22
- - Debian/Ubuntu: `sudo apt-get install libsecret-1-dev`
23
- - Alpine: `apk add libsecret`
24
- - Red Hat-based: `sudo yum install libsecret-devel`
25
- - Arch Linux: `sudo pacman -S libsecret`
18
+ In order to save credentials safely, this project uses [`@napi-rs/keyring`](https://www.npmjs.com/package/@napi-rs/keyring), which uses the system Secret Service and falls back to the Linux kernel keyring. Setting the `VSCE_STORE=file` environment variable will revert back to the file credential store. Using the `VSCE_PAT` environment variable will also avoid using the system credential store.
26
19
 
27
20
  ## Usage
28
21
 
@@ -58,6 +51,31 @@ Or you can also set them in the `package.json`, so that you avoid having to rety
58
51
  }
59
52
  ```
60
53
 
54
+ ### Trusted publishing
55
+
56
+ `vsce publish --oidc` publishes from GitHub Actions without storing a Personal Access Token. Configure a trusted
57
+ publishing policy for the repository and workflow on the Visual Studio Marketplace, then grant the workflow permission
58
+ to request an OIDC token:
59
+
60
+ ```yaml
61
+ jobs:
62
+ publish:
63
+ runs-on: ubuntu-latest
64
+ permissions:
65
+ contents: read
66
+ id-token: write
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+ - uses: actions/setup-node@v4
70
+ with:
71
+ node-version: 22
72
+ - run: npm ci
73
+ - run: npx @vscode/vsce publish --oidc
74
+ ```
75
+
76
+ OIDC publishing requests a GitHub Actions token for the `marketplace.visualstudio.com` audience and exchanges it for a
77
+ short-lived Marketplace credential. It does not fall back to a PAT when token acquisition or exchange fails.
78
+
61
79
  ## Development
62
80
 
63
81
  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,9 @@ 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')
177
+ .conflicts(['pat', 'azureCredential'])
178
+ .hideHelp(true))
176
179
  .option('-t, --target <targets...>', `Target architectures. Valid targets: ${ValidTargets}`)
177
180
  .option('--ignore-other-target-folders', `Ignore other target folders. Valid only when --target <target> is provided.`)
178
181
  .option('--readme-path <path>', 'Path to README file (defaults to README.md)')
@@ -209,9 +212,10 @@ module.exports = function (argv) {
209
212
  .option('--skip-duplicate', 'Fail silently if version already exists on the marketplace')
210
213
  .option('--skip-license', 'Allow publishing without license file')
211
214
  .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,
215
+ .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)({
216
+ pat: oidc ? undefined : pat,
214
217
  azureCredential,
218
+ oidc,
215
219
  version,
216
220
  targets: target,
217
221
  ignoreOtherTargetFolders,
package/out/npm.js CHANGED
@@ -32,9 +32,6 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.detectYarn = detectYarn;
40
37
  exports.getDependencies = getDependencies;
@@ -42,8 +39,8 @@ exports.getLatestVersion = getLatestVersion;
42
39
  const path = __importStar(require("path"));
43
40
  const fs = __importStar(require("fs"));
44
41
  const cp = __importStar(require("child_process"));
45
- const parse_semver_1 = __importDefault(require("parse-semver"));
46
42
  const util_1 = require("./util");
43
+ const packageSpec_1 = require("./packageSpec");
47
44
  const exists = (file) => fs.promises.stat(file).then(_ => true, _ => false);
48
45
  function parseStdout({ stdout }) {
49
46
  return stdout.split(/[\r\n]/).filter(line => !!line)[0];
@@ -87,7 +84,7 @@ function asYarnDependency(prefix, tree, prune) {
87
84
  }
88
85
  let name;
89
86
  try {
90
- const parseResult = (0, parse_semver_1.default)(tree.name);
87
+ const parseResult = (0, packageSpec_1.parsePackageSpec)(tree.name);
91
88
  name = parseResult.name;
92
89
  }
93
90
  catch (err) {
@@ -105,8 +102,8 @@ function asYarnDependency(prefix, tree, prune) {
105
102
  }
106
103
  function selectYarnDependencies(deps, packagedDependencies) {
107
104
  const index = new (class {
105
+ data = Object.create(null);
108
106
  constructor() {
109
- this.data = Object.create(null);
110
107
  for (const dep of deps) {
111
108
  if (this.data[dep.name]) {
112
109
  throw Error(`Dependency seen more than once: ${dep.name}`);
@@ -123,9 +120,7 @@ function selectYarnDependencies(deps, packagedDependencies) {
123
120
  }
124
121
  })();
125
122
  const reached = new (class {
126
- constructor() {
127
- this.values = [];
128
- }
123
+ values = [];
129
124
  add(dep) {
130
125
  if (this.values.indexOf(dep) < 0) {
131
126
  this.values.push(dep);
package/out/oidc.js ADDED
@@ -0,0 +1,125 @@
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
+ name = 'GitHub Actions';
9
+ isAvailable(environment) {
10
+ return environment['GITHUB_ACTIONS']?.toLowerCase() === 'true';
11
+ }
12
+ async getToken(audience, { environment, request }) {
13
+ const requestUrl = environment['ACTIONS_ID_TOKEN_REQUEST_URL'];
14
+ const requestToken = environment['ACTIONS_ID_TOKEN_REQUEST_TOKEN'];
15
+ if (!requestUrl || !requestToken) {
16
+ 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.');
17
+ }
18
+ let tokenUrl;
19
+ try {
20
+ tokenUrl = new URL(requestUrl);
21
+ }
22
+ catch {
23
+ throw new Error('GitHub Actions provided an invalid ACTIONS_ID_TOKEN_REQUEST_URL.');
24
+ }
25
+ tokenUrl.searchParams.set('audience', audience);
26
+ const result = await requestJSON('GitHub Actions OIDC token request', tokenUrl.toString(), request, {
27
+ method: 'GET',
28
+ headers: {
29
+ Accept: 'application/json',
30
+ Authorization: `Bearer ${requestToken}`,
31
+ },
32
+ });
33
+ if (!isRecord(result) || typeof result.value !== 'string' || !result.value) {
34
+ throw new Error('GitHub Actions OIDC token request returned an invalid response without a token.');
35
+ }
36
+ return result.value;
37
+ }
38
+ }
39
+ const oidcTokenProviders = [new GitHubActionsOIDCTokenProvider()];
40
+ async function getOIDCCredential(publisherName, options = {}) {
41
+ const environment = options.environment ?? process.env;
42
+ const request = options.request ?? defaultRequest;
43
+ const provider = oidcTokenProviders.find(candidate => candidate.isAvailable(environment));
44
+ if (!provider) {
45
+ throw new Error('No supported OIDC provider was detected. OIDC publishing currently supports GitHub Actions only.');
46
+ }
47
+ const oidcToken = await provider.getToken(exports.OIDC_AUDIENCE, { environment, request });
48
+ return await exchangeOIDCToken(publisherName, oidcToken, options.marketplaceUrl ?? (0, util_1.getMarketplaceUrl)(), request);
49
+ }
50
+ async function exchangeOIDCToken(publisherName, oidcToken, marketplaceUrl, request) {
51
+ const result = await requestJSON('Marketplace OIDC token exchange', `${marketplaceUrl.replace(/\/$/, '')}/_apis/gallery/token`, request, {
52
+ method: 'POST',
53
+ headers: {
54
+ Accept: 'application/json',
55
+ Authorization: `Bearer ${oidcToken}`,
56
+ 'Content-Type': 'application/json',
57
+ 'User-Agent': 'vsce',
58
+ },
59
+ body: JSON.stringify({ publisherName }),
60
+ });
61
+ if (!isRecord(result) || typeof result.credential !== 'string' || !result.credential) {
62
+ throw new Error('Marketplace OIDC token exchange returned an invalid response without a credential.');
63
+ }
64
+ return result.credential;
65
+ }
66
+ async function defaultRequest(url, request) {
67
+ const response = await fetch(url, request);
68
+ return {
69
+ statusCode: response.status,
70
+ statusMessage: response.statusText,
71
+ readBody: () => response.text(),
72
+ };
73
+ }
74
+ async function requestJSON(operation, url, request, init) {
75
+ let response;
76
+ try {
77
+ response = await request(url, init);
78
+ }
79
+ catch (error) {
80
+ throw new Error(`${operation} failed: ${getErrorMessage(error)}`);
81
+ }
82
+ let body;
83
+ try {
84
+ body = await response.readBody();
85
+ }
86
+ catch (error) {
87
+ throw new Error(`${operation} failed while reading the response: ${getErrorMessage(error)}`);
88
+ }
89
+ if (response.statusCode < 200 || response.statusCode >= 300) {
90
+ const status = `${response.statusCode}${response.statusMessage ? ` ${response.statusMessage}` : ''}`;
91
+ throw new Error(`${operation} failed with ${status}${getResponseDetails(body)}`);
92
+ }
93
+ try {
94
+ return JSON.parse(body);
95
+ }
96
+ catch {
97
+ throw new Error(`${operation} returned an invalid JSON response.`);
98
+ }
99
+ }
100
+ function getResponseDetails(body) {
101
+ const trimmedBody = body.trim();
102
+ if (!trimmedBody) {
103
+ return '.';
104
+ }
105
+ try {
106
+ const parsed = JSON.parse(trimmedBody);
107
+ if (isRecord(parsed)) {
108
+ const message = parsed.message ?? parsed.error_description ?? parsed.error;
109
+ if (typeof message === 'string' && message) {
110
+ return `: ${message}`;
111
+ }
112
+ }
113
+ }
114
+ catch {
115
+ // Use the plain response body below.
116
+ }
117
+ return `: ${trimmedBody.slice(0, 500)}`;
118
+ }
119
+ function getErrorMessage(error) {
120
+ return error instanceof Error ? error.message : String(error);
121
+ }
122
+ function isRecord(value) {
123
+ return typeof value === 'object' && value !== null;
124
+ }
125
+ //# sourceMappingURL=oidc.js.map