@vscode/vsce 3.9.3-11 → 3.9.3-12

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
@@ -17,6 +17,26 @@ Read the [**Documentation**](https://code.visualstudio.com/api/working-with-exte
17
17
 
18
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.
19
19
 
20
+ ### Upgrading saved PATs
21
+
22
+ When a command needs a saved Personal Access Token and the publisher has none in the current native credential store, `vsce` checks for that publisher's old `keytar` credential. If one is found in an interactive terminal, it offers:
23
+
24
+ ```text
25
+ A saved PAT for publisher 'your-publisher' was found in the previous credential store. Copy it to the new store? [y/N]
26
+ ```
27
+
28
+ Enter `Y` to copy just that publisher's PAT and continue without re-entering it. Enter `N` (or press Enter) to continue to the normal PAT prompt without copying anything. Migration is skipped in non-interactive runs such as CI.
29
+
30
+ - **Windows:** reads the requested Windows Credential Manager entry using the built-in Windows PowerShell. No `keytar` installation is needed.
31
+ - **Linux:** requires `secret-tool` (`libsecret-tools` on Debian/Ubuntu) and access to the same desktop Secret Service used previously. You may be prompted to unlock the keyring.
32
+ - **macOS:** existing Keychain entries are already compatible; no copying is necessary.
33
+
34
+ Existing PATs in the new store take precedence; migration does not run while listing publishers or logging out. Each copied PAT is read back before it is used. **Old keytar entries are never modified or deleted**, so older `vsce` versions can still use them. Declining or logging out does not permanently suppress the offer: the old PAT can be offered again when needed, but copying always requires fresh consent. Logout removes only the new entry and does not revoke the PAT.
35
+
36
+ Credential writes are serialized across `vsce` processes, and the destination is checked again after confirmation to avoid overwriting a newer PAT. The non-secret lock lives under `~/.vsce-keytar-migration`; no PATs or consent decisions are stored there. A command waits up to 30 seconds for the lock; a lock abandoned by a crashed process can be recovered after two minutes.
37
+
38
+ If legacy lookup or copying fails, `vsce` displays a warning and continues to the normal PAT prompt. Install the required helper/unlock the keyring and retry, or enter a PAT to save it normally. Migration helpers time out after 30 seconds. Using `VSCE_STORE=file`, `VSCE_PAT`, or `--pat` does not trigger this native-store migration; the existing plaintext-file migration is unchanged.
39
+
20
40
  ## Usage
21
41
 
22
42
  ```console
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.LegacyCredentialMigration = void 0;
37
+ const crypto_1 = require("crypto");
38
+ const fs = __importStar(require("fs"));
39
+ const os_1 = require("os");
40
+ const path = __importStar(require("path"));
41
+ const proper_lockfile_1 = require("proper-lockfile");
42
+ const legacyCredentials_1 = require("./legacyCredentials");
43
+ const util_1 = require("./util");
44
+ const validation_1 = require("./validation");
45
+ // Decorate the native store so migration policy and write synchronization stay
46
+ // separate from its ordinary credential operations.
47
+ class LegacyCredentialMigration {
48
+ store;
49
+ openStore;
50
+ static wrap(store, openStore, options = {}) {
51
+ const platform = options.platform ?? process.platform;
52
+ return platform === 'win32' || platform === 'linux'
53
+ ? new LegacyCredentialMigration(store, openStore, options)
54
+ : store;
55
+ }
56
+ serviceName;
57
+ platform;
58
+ lockPath;
59
+ interactive;
60
+ prompt;
61
+ readCredential;
62
+ constructor(store, openStore, options = {}) {
63
+ this.store = store;
64
+ this.openStore = openStore;
65
+ this.serviceName = options.serviceName ?? 'vscode-vsce';
66
+ this.platform = options.platform ?? process.platform;
67
+ this.lockPath = options.lockPath ?? path.join((0, os_1.homedir)(), '.vsce-keytar-migration', (0, crypto_1.createHash)('sha256').update(`${this.platform}:${this.serviceName}`).digest('hex'));
68
+ // read() otherwise answers "y" in tests and non-interactive processes.
69
+ this.interactive = options.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.VSCE_TESTS);
70
+ this.prompt = options.prompt ?? util_1.read;
71
+ this.readCredential = options.readCredential
72
+ ?? ((service, name) => (0, legacyCredentials_1.readLegacyCredential)(service, name, { platform: this.platform }));
73
+ }
74
+ get size() {
75
+ return this.store.size;
76
+ }
77
+ get(name) {
78
+ return this.findPublisher(this.store, name);
79
+ }
80
+ async add(publisher) {
81
+ await this.withLock(() => this.store.add({
82
+ name: this.get(publisher.name)?.name ?? publisher.name,
83
+ pat: publisher.pat,
84
+ }));
85
+ }
86
+ async delete(name) {
87
+ await this.withLock(() => this.store.delete(this.get(name)?.name ?? name));
88
+ }
89
+ [Symbol.iterator]() {
90
+ return this.store[Symbol.iterator]();
91
+ }
92
+ async tryMigratePublisher(name) {
93
+ (0, validation_1.validatePublisher)(name);
94
+ const existing = this.get(name);
95
+ if (existing || !this.interactive || (this.platform !== 'win32' && this.platform !== 'linux')) {
96
+ return existing;
97
+ }
98
+ try {
99
+ const legacy = await this.readCredential(this.serviceName, name);
100
+ if (!legacy) {
101
+ return undefined;
102
+ }
103
+ if (!this.sameAccount(legacy.name, name) || !legacy.pat) {
104
+ throw new legacyCredentials_1.LegacyMigrationError('The legacy credential reader returned an invalid credential.');
105
+ }
106
+ const answer = await this.prompt(`A saved PAT for publisher '${name}' was found in the previous credential store. Copy it to the new store? [y/N] `);
107
+ if (!/^y$/i.test(answer.trim())) {
108
+ return undefined;
109
+ }
110
+ // Do not hold a cross-process lock while waiting for the user's answer.
111
+ return await this.withLock(() => this.copyAndVerify({ name, pat: legacy.pat }));
112
+ }
113
+ catch (error) {
114
+ if (!(error instanceof legacyCredentials_1.LegacyMigrationError)) {
115
+ throw error;
116
+ }
117
+ util_1.log.warn(`${error.message} The previous credential was not changed. `
118
+ + (this.platform === 'linux' ? 'Legacy lookup requires secret-tool (libsecret-tools on Debian/Ubuntu) and an accessible desktop keyring. ' : '')
119
+ + 'Enter a PAT to continue, or retry after resolving the credential-store problem.');
120
+ return undefined;
121
+ }
122
+ }
123
+ async copyAndVerify(publisher) {
124
+ let verified;
125
+ try {
126
+ const destination = await this.openStore();
127
+ const current = this.findPublisher(destination, publisher.name);
128
+ if (current) {
129
+ this.store = destination;
130
+ return current;
131
+ }
132
+ await destination.add(publisher);
133
+ verified = await this.openStore();
134
+ const saved = this.findPublisher(verified, publisher.name);
135
+ if (saved?.pat !== publisher.pat) {
136
+ throw new legacyCredentials_1.LegacyMigrationError('The copied PAT could not be verified.');
137
+ }
138
+ }
139
+ catch (error) {
140
+ if (!(error instanceof Error)) {
141
+ throw error;
142
+ }
143
+ // Native failures must not include secret values in CLI diagnostics.
144
+ throw new legacyCredentials_1.LegacyMigrationError(`Could not copy and verify the previous PAT for publisher '${publisher.name}'.`);
145
+ }
146
+ this.store = verified;
147
+ util_1.log.info(`Copied the saved PAT for publisher '${publisher.name}'. The previous credential was not changed.`);
148
+ return publisher;
149
+ }
150
+ sameAccount(a, b) {
151
+ return this.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
152
+ }
153
+ findPublisher(store, name) {
154
+ return [...store].find(publisher => this.sameAccount(publisher.name, name));
155
+ }
156
+ async withLock(operation) {
157
+ let release;
158
+ try {
159
+ await fs.promises.mkdir(this.lockPath, { recursive: true, mode: 0o700 });
160
+ release = await (0, proper_lockfile_1.lock)(this.lockPath, {
161
+ stale: 120_000,
162
+ update: 5_000,
163
+ retries: { retries: 120, factor: 1, minTimeout: 250, maxTimeout: 250 },
164
+ });
165
+ }
166
+ catch (error) {
167
+ if (error instanceof Error && 'code' in error) {
168
+ throw new legacyCredentials_1.LegacyMigrationError('Could not lock the credential store. Another vsce command may still be using it.');
169
+ }
170
+ throw error;
171
+ }
172
+ try {
173
+ return await operation();
174
+ }
175
+ finally {
176
+ await release();
177
+ }
178
+ }
179
+ }
180
+ exports.LegacyCredentialMigration = LegacyCredentialMigration;
181
+ //# sourceMappingURL=keytarMigration.js.map
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.LegacyMigrationError = void 0;
37
+ exports.readLegacyCredential = readLegacyCredential;
38
+ const child_process_1 = require("child_process");
39
+ const path = __importStar(require("path"));
40
+ const validation_1 = require("./validation");
41
+ const windowsKeytar_1 = require("./windowsKeytar");
42
+ class LegacyMigrationError extends Error {
43
+ }
44
+ exports.LegacyMigrationError = LegacyMigrationError;
45
+ const runCredentialCommand = (file, args, env) => new Promise((resolve, reject) => {
46
+ (0, child_process_1.execFile)(file, args, {
47
+ encoding: 'utf8',
48
+ env,
49
+ windowsHide: true,
50
+ timeout: 30_000,
51
+ maxBuffer: 1024 * 1024,
52
+ }, (error, stdout, stderr) => {
53
+ if (error && (typeof error.code !== 'number' || error.killed)) {
54
+ // Child-process errors can contain stdout/stderr, including PATs.
55
+ const reason = error.code === 'ENOENT' ? 'is not installed'
56
+ : error.killed ? 'timed out or was terminated'
57
+ : 'failed to read the previous credential';
58
+ reject(new LegacyMigrationError(`${path.basename(file)} ${reason}.`));
59
+ }
60
+ else {
61
+ resolve({ stdout, stderr, exitCode: typeof error?.code === 'number' ? error.code : 0 });
62
+ }
63
+ });
64
+ });
65
+ function parseWindowsCredential(stdout, name) {
66
+ let value;
67
+ try {
68
+ value = JSON.parse(stdout);
69
+ }
70
+ catch (error) {
71
+ if (!(error instanceof SyntaxError)) {
72
+ throw error;
73
+ }
74
+ throw new LegacyMigrationError('The Windows credential reader returned an invalid response.');
75
+ }
76
+ if (value === null) {
77
+ return undefined;
78
+ }
79
+ if (!value || typeof value !== 'object'
80
+ || !('name' in value) || typeof value.name !== 'string' || value.name.toLowerCase() !== name.toLowerCase()
81
+ || !('pat' in value) || typeof value.pat !== 'string' || !value.pat) {
82
+ throw new LegacyMigrationError('The Windows credential reader returned an invalid credential.');
83
+ }
84
+ return { name, pat: value.pat };
85
+ }
86
+ async function readLegacyCredential(serviceName, publisherName, { platform = process.platform, run = runCredentialCommand } = {}) {
87
+ (0, validation_1.validatePublisher)(publisherName);
88
+ if (platform === 'win32') {
89
+ const powershell = path.win32.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
90
+ const { stdout, exitCode } = await run(powershell, [
91
+ '-NoLogo', '-NoProfile', '-NonInteractive', '-Command', windowsKeytar_1.windowsKeytarReadScript,
92
+ ], { ...process.env, VSCE_KEYTAR_SERVICE: serviceName, VSCE_KEYTAR_ACCOUNT: publisherName });
93
+ if (exitCode !== 0) {
94
+ throw new LegacyMigrationError('Windows PowerShell could not read the previous credential.');
95
+ }
96
+ return parseWindowsCredential(stdout, publisherName);
97
+ }
98
+ if (platform !== 'linux') {
99
+ return undefined;
100
+ }
101
+ const { stdout, stderr, exitCode } = await run('secret-tool', [
102
+ 'lookup', 'service', serviceName, 'account', publisherName,
103
+ 'xdg:schema', 'org.freedesktop.Secret.Generic',
104
+ ]);
105
+ // secret-tool exits 1 without output when no item matches; other failures
106
+ // must not masquerade as a missing credential or expose raw diagnostics.
107
+ if (exitCode === 1 && !stdout && !stderr) {
108
+ return undefined;
109
+ }
110
+ if (exitCode !== 0 || !stdout) {
111
+ throw new LegacyMigrationError('secret-tool could not read the previous credential.');
112
+ }
113
+ return { name: publisherName, pat: stdout };
114
+ }
115
+ //# sourceMappingURL=legacyCredentials.js.map
package/out/main.js CHANGED
@@ -32,12 +32,8 @@ 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
  const commander_1 = require("commander");
40
- const leven_1 = __importDefault(require("leven"));
41
37
  const package_1 = require("./package");
42
38
  const publish_1 = require("./publish");
43
39
  const show_1 = require("./show");
@@ -311,7 +307,7 @@ module.exports = function (argv) {
311
307
  }
312
308
  program.outputHelp(help => {
313
309
  const availableCommands = program.commands.map(c => c.name());
314
- const suggestion = availableCommands.find(c => (0, leven_1.default)(c, cmd) < c.length * 0.4);
310
+ const suggestion = (0, util_1.findSimilar)(cmd, availableCommands);
315
311
  help = `${help}\n Unknown command '${cmd}'`;
316
312
  return suggestion ? `${help}, did you mean '${suggestion}'?\n` : `${help}.\n`;
317
313
  });
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMultipartStream = createMultipartStream;
4
+ exports.runWithStreamError = runWithStreamError;
5
+ const stream_1 = require("stream");
6
+ function escapeHeaderParameter(value) {
7
+ return value.replace(/\r/g, '%0D').replace(/\n/g, '%0A').replace(/"/g, '%22');
8
+ }
9
+ function createMultipartStream(parts, boundary) {
10
+ const lineBreak = '\r\n';
11
+ for (const part of parts) {
12
+ // A part can fail before it is reached. Readable stores the error and its async iterator
13
+ // rethrows it when the part is consumed; this listener just keeps it from being unhandled.
14
+ part.stream.on('error', () => { });
15
+ }
16
+ const destroyParts = () => {
17
+ for (const part of parts) {
18
+ part.stream.destroy();
19
+ }
20
+ };
21
+ const stream = stream_1.Readable.from((async function* () {
22
+ try {
23
+ for (const part of parts) {
24
+ yield `--${boundary}${lineBreak}` +
25
+ `Content-Disposition: attachment; name=${escapeHeaderParameter(part.name)}; filename="${escapeHeaderParameter(part.filename)}"${lineBreak}` +
26
+ `Content-Type: application/octet-stream${lineBreak}${lineBreak}`;
27
+ yield* part.stream;
28
+ yield lineBreak;
29
+ }
30
+ yield `--${boundary}--${lineBreak}`;
31
+ }
32
+ finally {
33
+ // Parts after a failed or abandoned one are never consumed, so release them here.
34
+ destroyParts();
35
+ }
36
+ })(),
37
+ // Without this the stream emits 'close' once it ends, which makes consumers that treat
38
+ // 'close' as "the body is complete" end the underlying request a second time.
39
+ { autoDestroy: false });
40
+ // The generator body, and therefore its `finally`, never runs if the stream is destroyed
41
+ // before anything is read from it.
42
+ stream.on('close', destroyParts);
43
+ return stream;
44
+ }
45
+ function runWithStreamError(stream, operation) {
46
+ return new Promise((resolve, reject) => {
47
+ // This listener is deliberately never removed. An error arriving after the operation has
48
+ // settled would otherwise be an unhandled 'error' event, which terminates the process.
49
+ // Settling a promise more than once is a no-op, so the first outcome wins.
50
+ stream.on('error', reject);
51
+ Promise.resolve().then(operation).then(resolve, reject);
52
+ });
53
+ }
54
+ //# sourceMappingURL=multipart.js.map
package/out/publish.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.publish = publish;
40
37
  exports.unpublish = unpublish;
@@ -47,12 +44,12 @@ const store_1 = require("./store");
47
44
  const util_1 = require("./util");
48
45
  const zip_1 = require("./zip");
49
46
  const validation_1 = require("./validation");
50
- const form_data_1 = __importDefault(require("form-data"));
51
47
  const path_1 = require("path");
52
48
  const os_1 = require("os");
53
49
  const cockatiel_1 = require("cockatiel");
54
50
  const auth_1 = require("./auth");
55
51
  const oidc_1 = require("./oidc");
52
+ const multipart_1 = require("./multipart");
56
53
  async function withTemporaryPackage(fn) {
57
54
  const directory = await fs.promises.mkdtemp((0, path_1.join)((0, os_1.tmpdir)(), 'vsce-'));
58
55
  try {
@@ -143,7 +140,6 @@ async function publish(options = {}) {
143
140
  async function _publish(packagePath, sigzipPath, manifest, options) {
144
141
  const pat = await getPAT(manifest.publisher, options);
145
142
  const api = await (0, util_1.getGalleryAPI)(pat);
146
- const packageStream = fs.createReadStream(packagePath);
147
143
  const name = `${manifest.publisher}.${manifest.name}`;
148
144
  const description = options.target
149
145
  ? `${name} (${options.target}) v${manifest.version}`
@@ -172,11 +168,11 @@ async function _publish(packagePath, sigzipPath, manifest, options) {
172
168
  }
173
169
  }
174
170
  if (sigzipPath) {
175
- await _publishSignedPackage(api, (0, path_1.basename)(packagePath), packageStream, (0, path_1.basename)(sigzipPath), fs.createReadStream(sigzipPath), manifest);
171
+ await _publishSignedPackage(api, packagePath, sigzipPath, manifest);
176
172
  }
177
173
  else {
178
174
  try {
179
- await api.updateExtension(undefined, packageStream, manifest.publisher, manifest.name);
175
+ await api.updateExtension(undefined, fs.createReadStream(packagePath), manifest.publisher, manifest.name);
180
176
  }
181
177
  catch (err) {
182
178
  if (err.statusCode === 409) {
@@ -196,10 +192,10 @@ async function _publish(packagePath, sigzipPath, manifest, options) {
196
192
  }
197
193
  else {
198
194
  if (sigzipPath) {
199
- await _publishSignedPackage(api, (0, path_1.basename)(packagePath), packageStream, (0, path_1.basename)(sigzipPath), fs.createReadStream(sigzipPath), manifest);
195
+ await _publishSignedPackage(api, packagePath, sigzipPath, manifest);
200
196
  }
201
197
  else {
202
- await api.createExtension(undefined, packageStream);
198
+ await api.createExtension(undefined, fs.createReadStream(packagePath));
203
199
  }
204
200
  }
205
201
  }
@@ -217,23 +213,27 @@ async function _publish(packagePath, sigzipPath, manifest, options) {
217
213
  util_1.log.info(`Hub URL: ${(0, util_1.getHubUrl)(manifest.publisher, manifest.name)}`);
218
214
  util_1.log.done(`Published ${description}.`);
219
215
  }
220
- async function _publishSignedPackage(api, packageName, packageStream, sigzipName, sigzipStream, manifest) {
216
+ async function _publishSignedPackage(api, packagePath, sigzipPath, manifest) {
221
217
  const extensionType = 'Visual Studio Code';
222
- const form = new form_data_1.default();
223
- const lineBreak = '\r\n';
224
- form.setBoundary('0f411892-ef48-488f-89d3-4f0546e84723');
225
- form.append('vsix', packageStream, {
226
- header: `--${form.getBoundary()}${lineBreak}Content-Disposition: attachment; name=vsix; filename=\"${packageName}\"${lineBreak}Content-Type: application/octet-stream${lineBreak}${lineBreak}`
227
- });
228
- form.append('sigzip', sigzipStream, {
229
- header: `--${form.getBoundary()}${lineBreak}Content-Disposition: attachment; name=sigzip; filename=\"${sigzipName}\"${lineBreak}Content-Type: application/octet-stream${lineBreak}${lineBreak}`
230
- });
231
218
  const publishWithRetry = (0, cockatiel_1.retry)((0, cockatiel_1.handleWhen)(err => err.message.includes('timeout')), {
232
219
  maxAttempts: 3,
233
220
  backoff: new cockatiel_1.IterableBackoff([5_000, 10_000, 20_000])
234
221
  });
235
222
  return await publishWithRetry.execute(async () => {
236
- return await api.publishExtensionWithPublisherSignature(undefined, form, manifest.publisher, manifest.name, extensionType);
223
+ const form = (0, multipart_1.createMultipartStream)([
224
+ { name: 'vsix', filename: (0, path_1.basename)(packagePath), stream: fs.createReadStream(packagePath) },
225
+ { name: 'sigzip', filename: (0, path_1.basename)(sigzipPath), stream: fs.createReadStream(sigzipPath) },
226
+ ], '0f411892-ef48-488f-89d3-4f0546e84723');
227
+ try {
228
+ return await (0, multipart_1.runWithStreamError)(form, () => api.publishExtensionWithPublisherSignature(undefined, form, manifest.publisher, manifest.name, extensionType));
229
+ }
230
+ finally {
231
+ // Release the file handles when the request did not consume the whole form, which
232
+ // otherwise keeps the package locked until the process exits.
233
+ if (!form.readableEnded) {
234
+ form.destroy();
235
+ }
236
+ }
237
237
  });
238
238
  }
239
239
  async function unpublish(options = {}) {
package/out/store.js CHANGED
@@ -47,6 +47,7 @@ const util_1 = require("./util");
47
47
  const validation_1 = require("./validation");
48
48
  const package_1 = require("./package");
49
49
  const publish_1 = require("./publish");
50
+ const keytarMigration_1 = require("./keytarMigration");
50
51
  ;
51
52
  class FileStore {
52
53
  path;
@@ -105,8 +106,7 @@ class KeytarStore {
105
106
  keytar;
106
107
  serviceName;
107
108
  publishers;
108
- static async open(serviceName = 'vscode-vsce') {
109
- const keytar = require('@napi-rs/keyring/keytar.js');
109
+ static async open(serviceName = 'vscode-vsce', keytar = require('@napi-rs/keyring/keytar.js')) {
110
110
  const creds = await keytar.findCredentials(serviceName);
111
111
  return new KeytarStore(keytar, serviceName, creds.map(({ account, password }) => ({ name: account, pat: password })));
112
112
  }
@@ -122,12 +122,14 @@ class KeytarStore {
122
122
  return this.publishers.filter(p => p.name === name)[0];
123
123
  }
124
124
  async add(publisher) {
125
- this.publishers = [...this.publishers.filter(p => p.name !== publisher.name), publisher];
126
125
  await this.keytar.setPassword(this.serviceName, publisher.name, publisher.pat);
126
+ this.publishers = [...this.publishers.filter(p => p.name !== publisher.name), publisher];
127
127
  }
128
128
  async delete(name) {
129
+ if (!await this.keytar.deletePassword(this.serviceName, name)) {
130
+ throw new Error(`Could not remove the saved PAT for publisher '${name}'.`);
131
+ }
129
132
  this.publishers = this.publishers.filter(p => p.name !== name);
130
- await this.keytar.deletePassword(this.serviceName, name);
131
133
  }
132
134
  [Symbol.iterator]() {
133
135
  return this.publishers[Symbol.iterator]();
@@ -171,6 +173,7 @@ async function openDefaultStore() {
171
173
  util_1.log.warn(`Failed to open credential store. Falling back to storing secrets clear-text in: ${store.path}`);
172
174
  return store;
173
175
  }
176
+ keytarStore = keytarMigration_1.LegacyCredentialMigration.wrap(keytarStore, () => KeytarStore.open());
174
177
  const fileStore = await FileStore.open();
175
178
  // migrate from file store
176
179
  if (fileStore.size) {
@@ -185,7 +188,7 @@ async function openDefaultStore() {
185
188
  async function getPublisher(publisherName) {
186
189
  (0, validation_1.validatePublisher)(publisherName);
187
190
  const store = await openDefaultStore();
188
- let publisher = store.get(publisherName);
191
+ let publisher = store.get(publisherName) ?? await store.tryMigratePublisher?.(publisherName);
189
192
  if (publisher) {
190
193
  return publisher;
191
194
  }
@@ -205,6 +208,12 @@ async function loginPublisher(publisherName) {
205
208
  throw new Error('Aborted');
206
209
  }
207
210
  }
211
+ else {
212
+ publisher = await store.tryMigratePublisher?.(publisherName);
213
+ if (publisher) {
214
+ return publisher;
215
+ }
216
+ }
208
217
  const pat = await requestPAT(publisherName);
209
218
  publisher = { name: publisherName, pat };
210
219
  await store.add(publisher);
package/out/util.js CHANGED
@@ -48,6 +48,8 @@ exports.normalize = normalize;
48
48
  exports.chain = chain;
49
49
  exports.flatten = flatten;
50
50
  exports.nonnull = nonnull;
51
+ exports.levenshtein = levenshtein;
52
+ exports.findSimilar = findSimilar;
51
53
  exports.isCancelledError = isCancelledError;
52
54
  exports.sequence = sequence;
53
55
  exports.patchOptionsWithManifest = patchOptionsWithManifest;
@@ -112,6 +114,51 @@ function flatten(arr) {
112
114
  function nonnull(arg) {
113
115
  return !!arg;
114
116
  }
117
+ /**
118
+ * Computes the Levenshtein distance between `a` and `b`, ie. the minimum number of
119
+ * single character insertions, deletions or substitutions needed to turn one into the
120
+ * other. Comparison happens on UTF-16 code units.
121
+ */
122
+ function levenshtein(a, b) {
123
+ if (a === b) {
124
+ return 0;
125
+ }
126
+ // The distance is symmetric, so keep the shorter string in `b` to bound the row size.
127
+ if (b.length > a.length) {
128
+ [a, b] = [b, a];
129
+ }
130
+ // A single row of the edit distance matrix, seeded with the distance between the
131
+ // empty prefix of `a` and every prefix of `b`.
132
+ const row = Array.from({ length: b.length + 1 }, (_, j) => j);
133
+ for (let i = 1; i <= a.length; i++) {
134
+ // The value of `row[j - 1]` before this row started being overwritten.
135
+ let diagonal = row[0];
136
+ row[0] = i;
137
+ for (let j = 1; j <= b.length; j++) {
138
+ const above = row[j];
139
+ row[j] = Math.min(row[j - 1] + 1, above + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));
140
+ diagonal = above;
141
+ }
142
+ }
143
+ return row[b.length];
144
+ }
145
+ /**
146
+ * Returns the candidate closest to `target`, or `undefined` when none of them is a
147
+ * plausible correction of it. A candidate qualifies when fewer than 40% of the characters
148
+ * of `target` have to be edited to reach it; ties are broken by iteration order.
149
+ */
150
+ function findSimilar(target, candidates) {
151
+ let best;
152
+ let bestDistance = Math.ceil(target.length * 0.4);
153
+ for (const candidate of candidates) {
154
+ const distance = levenshtein(candidate, target);
155
+ if (distance < bestDistance) {
156
+ best = candidate;
157
+ bestDistance = distance;
158
+ }
159
+ }
160
+ return best;
161
+ }
115
162
  const CancelledError = 'Cancelled';
116
163
  function isCancelledError(error) {
117
164
  return error === CancelledError;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.windowsKeytarReadScript = void 0;
4
+ // Read keytar's UTF-8 credentials without constructing a keyring Entry.withTarget,
5
+ // which overwrites the target credential even when only used to read a secret.
6
+ exports.windowsKeytarReadScript = String.raw `
7
+ $ErrorActionPreference = 'Stop'
8
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
9
+ Add-Type -TypeDefinition @'
10
+ using System;
11
+ using System.ComponentModel;
12
+ using System.Runtime.InteropServices;
13
+ using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
14
+ using System.Text;
15
+
16
+ public static class VsceKeytarReader {
17
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
18
+ private struct Credential {
19
+ public uint Flags;
20
+ public uint Type;
21
+ public string TargetName;
22
+ public string Comment;
23
+ public FILETIME LastWritten;
24
+ public uint CredentialBlobSize;
25
+ public IntPtr CredentialBlob;
26
+ public uint Persist;
27
+ public uint AttributeCount;
28
+ public IntPtr Attributes;
29
+ public string TargetAlias;
30
+ public string UserName;
31
+ }
32
+
33
+ public class Publisher {
34
+ public string name;
35
+ public string pat;
36
+ }
37
+
38
+ [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
39
+ [return: MarshalAs(UnmanagedType.Bool)]
40
+ private static extern bool CredRead(string target, uint type, uint flags, out IntPtr credential);
41
+
42
+ [DllImport("advapi32.dll")]
43
+ private static extern void CredFree(IntPtr credentials);
44
+
45
+ public static Publisher Read(string service, string account) {
46
+ IntPtr pointer;
47
+ if (!CredRead(service + "/" + account, 1, 0, out pointer)) {
48
+ int error = Marshal.GetLastWin32Error();
49
+ if (error == 1168) {
50
+ return null;
51
+ }
52
+ throw new Win32Exception(error);
53
+ }
54
+
55
+ try {
56
+ var credential = (Credential)Marshal.PtrToStructure(pointer, typeof(Credential));
57
+ if (!String.Equals(credential.UserName, account, StringComparison.OrdinalIgnoreCase)) {
58
+ throw new InvalidOperationException("The credential account does not match.");
59
+ }
60
+ var secret = new byte[checked((int)credential.CredentialBlobSize)];
61
+ try {
62
+ if (secret.Length > 0) {
63
+ Marshal.Copy(credential.CredentialBlob, secret, 0, secret.Length);
64
+ }
65
+ return new Publisher {
66
+ name = credential.UserName,
67
+ pat = new UTF8Encoding(false, true).GetString(secret)
68
+ };
69
+ } finally {
70
+ Array.Clear(secret, 0, secret.Length);
71
+ }
72
+ } finally {
73
+ CredFree(pointer);
74
+ }
75
+ }
76
+ }
77
+ '@
78
+ $credential = [VsceKeytarReader]::Read($env:VSCE_KEYTAR_SERVICE, $env:VSCE_KEYTAR_ACCOUNT)
79
+ if ($null -eq $credential) {
80
+ [Console]::WriteLine('null')
81
+ } else {
82
+ ConvertTo-Json -InputObject $credential -Compress
83
+ }
84
+ `;
85
+ //# sourceMappingURL=windowsKeytar.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vscode/vsce",
3
- "version": "3.9.3-11",
3
+ "version": "3.9.3-12",
4
4
  "description": "VS Code Extensions Manager",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,50 +39,50 @@
39
39
  "node": ">= 22"
40
40
  },
41
41
  "dependencies": {
42
- "@azure/identity": "^4.1.0",
42
+ "@azure/identity": "^4.13.2",
43
43
  "@napi-rs/keyring": "^1.3.0",
44
- "@secretlint/core": "^10.1.2",
45
- "@secretlint/secretlint-rule-no-dotenv": "^10.1.2",
46
- "@secretlint/secretlint-rule-preset-recommend": "^10.1.2",
47
- "@secretlint/source-creator": "^10.1.2",
48
- "@secretlint/types": "^10.1.2",
49
- "@vscode/vsce-sign": "^2.0.0",
44
+ "@secretlint/core": "^10.2.2",
45
+ "@secretlint/secretlint-rule-no-dotenv": "^10.2.2",
46
+ "@secretlint/secretlint-rule-preset-recommend": "^10.2.2",
47
+ "@secretlint/source-creator": "^10.2.2",
48
+ "@secretlint/types": "^10.2.2",
49
+ "@vscode/vsce-sign": "^2.1.0",
50
50
  "azure-devops-node-api": "^12.5.0",
51
- "cockatiel": "^3.1.2",
51
+ "cockatiel": "^3.2.1",
52
52
  "commander": "^12.1.0",
53
- "form-data": "^4.0.0",
54
- "hosted-git-info": "^4.0.2",
55
- "jsonc-parser": "^3.2.0",
56
- "leven": "^3.1.0",
57
- "marked": "^18.0.10",
58
- "mime": "^1.3.4",
59
- "minimatch": "^10.2.2",
53
+ "hosted-git-info": "^4.1.0",
54
+ "jsonc-parser": "^3.3.1",
55
+ "marked": "^18.0.11",
56
+ "mime": "^1.6.0",
57
+ "minimatch": "^10.2.6",
60
58
  "parse5": "^8.0.1",
59
+ "proper-lockfile": "^4.1.2",
61
60
  "read": "^1.0.7",
62
- "semver": "^7.5.2",
61
+ "semver": "^7.8.5",
63
62
  "tinyglobby": "^0.2.17",
64
- "typed-rest-client": "^1.8.4",
63
+ "typed-rest-client": "^1.8.11",
65
64
  "url-join": "^4.0.1",
66
65
  "xml2js": "^0.5.0",
67
- "yauzl": "^3.2.1",
68
- "yazl": "^2.2.2"
66
+ "yauzl": "^3.4.0",
67
+ "yazl": "^2.5.1"
69
68
  },
70
69
  "devDependencies": {
71
- "@microsoft/api-extractor": "^7.33.7",
72
- "@types/hosted-git-info": "^3.0.2",
73
- "@types/mime": "^1",
70
+ "@microsoft/api-extractor": "^7.59.0",
71
+ "@types/hosted-git-info": "^3.0.5",
72
+ "@types/mime": "^1.3.5",
74
73
  "@types/mocha": "^7.0.2",
75
- "@types/node": "^22.0.0",
74
+ "@types/node": "^22.20.1",
76
75
  "@types/picomatch": "^4.0.3",
76
+ "@types/proper-lockfile": "^4.1.4",
77
77
  "@types/read": "^0.0.28",
78
- "@types/semver": "^6.0.0",
79
- "@types/url-join": "^4.0.1",
80
- "@types/xml2js": "^0.4.4",
81
- "@types/yauzl": "^2.9.2",
82
- "@types/yazl": "^2.4.2",
83
- "mocha": "^11.1.0",
84
- "ts-node": "^10.9.1",
85
- "typescript": "~5.9.0"
78
+ "@types/semver": "^6.2.7",
79
+ "@types/url-join": "^4.0.3",
80
+ "@types/xml2js": "^0.4.14",
81
+ "@types/yauzl": "^2.10.3",
82
+ "@types/yazl": "^2.4.6",
83
+ "mocha": "^11.8.0",
84
+ "ts-node": "^10.9.2",
85
+ "typescript": "~5.9.3"
86
86
  },
87
87
  "mocha": {
88
88
  "require": [
@@ -93,6 +93,7 @@
93
93
  },
94
94
  "overrides": {
95
95
  "serialize-javascript": "7.x",
96
- "diff": "^9.0.0"
96
+ "diff": "^9.0.0",
97
+ "@azure/msal-common": "16.14.0"
97
98
  }
98
99
  }