@vscode/vsce 3.9.3-10 → 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 +20 -0
- package/out/keytarMigration.js +181 -0
- package/out/legacyCredentials.js +115 -0
- package/out/main.js +1 -5
- package/out/multipart.js +54 -0
- package/out/package.js +21 -21
- package/out/publish.js +20 -20
- package/out/secretLint.js +5 -9
- package/out/store.js +14 -5
- package/out/util.js +61 -15
- package/out/windowsKeytar.js +85 -0
- package/package.json +34 -34
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 =
|
|
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
|
});
|
package/out/multipart.js
ADDED
|
@@ -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/package.js
CHANGED
|
@@ -70,10 +70,10 @@ const minimatch_1 = require("minimatch");
|
|
|
70
70
|
const parse5_1 = require("parse5");
|
|
71
71
|
const marked_1 = require("marked");
|
|
72
72
|
const url = __importStar(require("url"));
|
|
73
|
+
const util_2 = require("util");
|
|
73
74
|
const mime_1 = __importDefault(require("mime"));
|
|
74
75
|
const semver = __importStar(require("semver"));
|
|
75
76
|
const url_join_1 = __importDefault(require("url-join"));
|
|
76
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
77
77
|
const validation_1 = require("./validation");
|
|
78
78
|
const npm_1 = require("./npm");
|
|
79
79
|
const GitHost = __importStar(require("hosted-git-info"));
|
|
@@ -1566,7 +1566,7 @@ async function packageCommand(options = {}) {
|
|
|
1566
1566
|
}
|
|
1567
1567
|
const stats = await fs.promises.stat(packagePath);
|
|
1568
1568
|
const packageSize = util.bytesToString(stats.size);
|
|
1569
|
-
util.log.done(`Packaged: ${packagePath} ` +
|
|
1569
|
+
util.log.done(`Packaged: ${packagePath} ` + (0, util_2.styleText)('bold', `(${files.length} files, ${packageSize})`));
|
|
1570
1570
|
}
|
|
1571
1571
|
/**
|
|
1572
1572
|
* Lists the files included in the extension's package.
|
|
@@ -1602,26 +1602,26 @@ async function printAndValidatePackagedFiles(files, cwd, manifest, options) {
|
|
|
1602
1602
|
const jsFiles = files.filter(f => /\.js$/i.test(f.path));
|
|
1603
1603
|
if (files.length > 5000 || jsFiles.length > 100) {
|
|
1604
1604
|
let message = '';
|
|
1605
|
-
message += `This extension consists of ${
|
|
1606
|
-
message += `For performance reasons, you should bundle your extension: ${
|
|
1607
|
-
message += `You should also exclude unnecessary files by adding them to your .vscodeignore: ${
|
|
1605
|
+
message += `This extension consists of ${(0, util_2.styleText)('bold', String(files.length))} files, out of which ${(0, util_2.styleText)('bold', String(jsFiles.length))} are JavaScript files. `;
|
|
1606
|
+
message += `For performance reasons, you should bundle your extension: ${(0, util_2.styleText)('underline', 'https://aka.ms/vscode-bundle-extension')}. `;
|
|
1607
|
+
message += `You should also exclude unnecessary files by adding them to your .vscodeignore: ${(0, util_2.styleText)('underline', 'https://aka.ms/vscode-vscodeignore')}.\n`;
|
|
1608
1608
|
util.log.warn(message);
|
|
1609
1609
|
}
|
|
1610
1610
|
// Warn if the extension does not have a .vscodeignore file or a files property in package.json
|
|
1611
1611
|
const hasIgnoreFile = fs.existsSync(options.ignoreFile ?? path.join(cwd, '.vscodeignore'));
|
|
1612
1612
|
if (!hasIgnoreFile && !manifest.files) {
|
|
1613
1613
|
let message = '';
|
|
1614
|
-
message += `Neither a ${
|
|
1614
|
+
message += `Neither a ${(0, util_2.styleText)('bold', '.vscodeignore')} file nor a ${(0, util_2.styleText)('bold', '"files"')} property in package.json was found. `;
|
|
1615
1615
|
message += `To ensure only necessary files are included in your extension, `;
|
|
1616
|
-
message += `add a .vscodeignore file or specify the "files" property in package.json. More info: ${
|
|
1616
|
+
message += `add a .vscodeignore file or specify the "files" property in package.json. More info: ${(0, util_2.styleText)('underline', 'https://aka.ms/vscode-vscodeignore')}\n`;
|
|
1617
1617
|
util.log.warn(message);
|
|
1618
1618
|
}
|
|
1619
1619
|
// Throw an error if the extension uses both a .vscodeignore file and the files property in package.json
|
|
1620
1620
|
else if (hasIgnoreFile && manifest.files !== undefined && manifest.files.length > 0) {
|
|
1621
1621
|
let message = '';
|
|
1622
|
-
message += `Both a ${
|
|
1622
|
+
message += `Both a ${(0, util_2.styleText)('bold', '.vscodeignore')} file and a ${(0, util_2.styleText)('bold', '"files"')} property in package.json were found. `;
|
|
1623
1623
|
message += `VSCE does not support combining both strategies. `;
|
|
1624
|
-
message += `Either remove the ${
|
|
1624
|
+
message += `Either remove the ${(0, util_2.styleText)('bold', '.vscodeignore')} file or the ${(0, util_2.styleText)('bold', '"files"')} property in package.json.`;
|
|
1625
1625
|
util.log.error(message);
|
|
1626
1626
|
process.exit(1);
|
|
1627
1627
|
}
|
|
@@ -1646,11 +1646,11 @@ async function printAndValidatePackagedFiles(files, cwd, manifest, options) {
|
|
|
1646
1646
|
});
|
|
1647
1647
|
if (unusedIncludePatterns.length > 0) {
|
|
1648
1648
|
let message = '';
|
|
1649
|
-
message += `The following include patterns in the ${
|
|
1649
|
+
message += `The following include patterns in the ${(0, util_2.styleText)('bold', '"files"')} property in package.json do not match any files packaged in the extension:\n`;
|
|
1650
1650
|
message += unusedIncludePatterns.map(p => ` - ${p.relative}`).join('\n');
|
|
1651
1651
|
message += '\nRemove any include pattern which is not needed.\n';
|
|
1652
|
-
message += `\n=> Run ${
|
|
1653
|
-
message += `=> Use ${
|
|
1652
|
+
message += `\n=> Run ${(0, util_2.styleText)('bold', 'vsce ls --tree')} to see all included files.\n`;
|
|
1653
|
+
message += `=> Use ${(0, util_2.styleText)('bold', '--allow-unused-files-pattern')} to skip this check`;
|
|
1654
1654
|
util.log.error(message);
|
|
1655
1655
|
process.exit(1);
|
|
1656
1656
|
}
|
|
@@ -1664,11 +1664,11 @@ async function printAndValidatePackagedFiles(files, cwd, manifest, options) {
|
|
|
1664
1664
|
})), 35 // Print up to 35 files/folders
|
|
1665
1665
|
);
|
|
1666
1666
|
let message = '';
|
|
1667
|
-
message +=
|
|
1667
|
+
message += (0, util_2.styleText)(['bold', 'blue'], `Files included in the VSIX:\n`);
|
|
1668
1668
|
message += printableFileStructure.join('\n');
|
|
1669
1669
|
// If not all files have been printed, mention how all files can be printed
|
|
1670
1670
|
if (files.length + 1 > printableFileStructure.length) {
|
|
1671
|
-
message += `\n\n=> Run ${
|
|
1671
|
+
message += `\n\n=> Run ${(0, util_2.styleText)('bold', 'vsce ls --tree')} to see all included files.`;
|
|
1672
1672
|
}
|
|
1673
1673
|
message += '\n';
|
|
1674
1674
|
util.log.info(message);
|
|
@@ -1703,33 +1703,33 @@ async function scanFilesForSecrets(files, fileExclusion, options) {
|
|
|
1703
1703
|
if (noneDotEnvSecretsFound.length > 0) {
|
|
1704
1704
|
const uniqueSecretIds = new Set(noneDotEnvSecretsFound.map(result => result.ruleId));
|
|
1705
1705
|
const secretsFoundRuleNames = Array.from(uniqueSecretIds).map(secretLint_1.getRuleNameFromRuleId);
|
|
1706
|
-
let errorMessage = `${
|
|
1706
|
+
let errorMessage = `${(0, util_2.styleText)('bold', 'Potential security issue detected:')}`;
|
|
1707
1707
|
errorMessage += ` Your extension package contains sensitive information that should not be published.`;
|
|
1708
1708
|
errorMessage += ` Please remove these secrets before packaging.`;
|
|
1709
1709
|
errorMessage += `\n` + noneDotEnvSecretsFound.map(secretLint_1.prettyPrintLintResult).join('\n');
|
|
1710
1710
|
let hintMessage = `\nIn case of false positives, you can allow specific types of secrets with `;
|
|
1711
1711
|
hintMessage += secretsFoundRuleNames.map(name => `--allow-package-secrets ${name}`).join(' ');
|
|
1712
1712
|
hintMessage += ` or use --allow-package-all-secrets to skip this check entirely (not recommended).`;
|
|
1713
|
-
util.log.error(errorMessage +
|
|
1713
|
+
util.log.error(errorMessage + (0, util_2.styleText)('italic', hintMessage));
|
|
1714
1714
|
process.exit(1);
|
|
1715
1715
|
}
|
|
1716
1716
|
// .env file found
|
|
1717
1717
|
const allRuleIds = new Set(secretsFound.map(result => result.ruleId).filter(Boolean));
|
|
1718
1718
|
if (!options.allowPackageEnvFile && allRuleIds.has('@secretlint/secretlint-rule-no-dotenv')) {
|
|
1719
|
-
let errorMessage = `${
|
|
1719
|
+
let errorMessage = `${(0, util_2.styleText)(['bold', 'red'], '.env')} files should not be packaged.`;
|
|
1720
1720
|
switch (fileExclusion) {
|
|
1721
1721
|
case FileExclusionType.None:
|
|
1722
|
-
errorMessage += ` Ignore the file in your ${
|
|
1722
|
+
errorMessage += ` Ignore the file in your ${(0, util_2.styleText)('bold', '.vscodeignore')} or exclude it from the package.json ${(0, util_2.styleText)('bold', 'files')} property.`;
|
|
1723
1723
|
break;
|
|
1724
1724
|
case FileExclusionType.VSCodeIgnore:
|
|
1725
|
-
errorMessage += ` Ignore the file in your ${
|
|
1725
|
+
errorMessage += ` Ignore the file in your ${(0, util_2.styleText)('bold', '.vscodeignore')}.`;
|
|
1726
1726
|
break;
|
|
1727
1727
|
case FileExclusionType.PackageFiles:
|
|
1728
|
-
errorMessage += ` Do not include the file in your package.json ${
|
|
1728
|
+
errorMessage += ` Do not include the file in your package.json ${(0, util_2.styleText)('bold', 'files')} property.`;
|
|
1729
1729
|
break;
|
|
1730
1730
|
}
|
|
1731
1731
|
const hintMessage = `\nTo ignore this check, you can use --allow-package-env-file (not recommended).`;
|
|
1732
|
-
util.log.error(errorMessage +
|
|
1732
|
+
util.log.error(errorMessage + (0, util_2.styleText)('italic', hintMessage));
|
|
1733
1733
|
process.exit(1);
|
|
1734
1734
|
}
|
|
1735
1735
|
}
|
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,
|
|
171
|
+
await _publishSignedPackage(api, packagePath, sigzipPath, manifest);
|
|
176
172
|
}
|
|
177
173
|
else {
|
|
178
174
|
try {
|
|
179
|
-
await api.updateExtension(undefined,
|
|
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,
|
|
195
|
+
await _publishSignedPackage(api, packagePath, sigzipPath, manifest);
|
|
200
196
|
}
|
|
201
197
|
else {
|
|
202
|
-
await api.createExtension(undefined,
|
|
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,
|
|
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
|
-
|
|
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/secretLint.js
CHANGED
|
@@ -32,19 +32,16 @@ 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.lintFiles = lintFiles;
|
|
40
37
|
exports.lintText = lintText;
|
|
41
38
|
exports.getRuleNameFromRuleId = getRuleNameFromRuleId;
|
|
42
39
|
exports.prettyPrintLintResult = prettyPrintLintResult;
|
|
43
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
44
40
|
const os = __importStar(require("os"));
|
|
45
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const util_1 = require("util");
|
|
46
43
|
const url_1 = require("url");
|
|
47
|
-
const
|
|
44
|
+
const util_2 = require("./util");
|
|
48
45
|
const secretsScanningRules = [
|
|
49
46
|
{
|
|
50
47
|
id: "@secretlint/secretlint-rule-preset-recommend",
|
|
@@ -133,7 +130,7 @@ async function lintFiles(filePaths, scanSecrets, scanDotEnv) {
|
|
|
133
130
|
}));
|
|
134
131
|
}
|
|
135
132
|
catch (error) {
|
|
136
|
-
|
|
133
|
+
util_2.log.error('Error occurred while scanning secrets (files):', error);
|
|
137
134
|
process.exit(1);
|
|
138
135
|
}
|
|
139
136
|
return parseResult(results);
|
|
@@ -159,7 +156,7 @@ async function lintText(content, fileName, scanSecrets, scanDotEnv) {
|
|
|
159
156
|
});
|
|
160
157
|
}
|
|
161
158
|
catch (error) {
|
|
162
|
-
|
|
159
|
+
util_2.log.error('Error occurred while scanning secrets (content):', error);
|
|
163
160
|
process.exit(1);
|
|
164
161
|
}
|
|
165
162
|
return parseResult([result]);
|
|
@@ -194,10 +191,9 @@ function getRuleNameFromRuleId(ruleId) {
|
|
|
194
191
|
}
|
|
195
192
|
function prettyPrintLintResult(result) {
|
|
196
193
|
const text = result.message;
|
|
197
|
-
const titleColor = result.level === "error" ? chalk_1.default.bold.red : chalk_1.default.bold.yellow;
|
|
198
194
|
const title = text.length > 54 ? text.slice(0, 50) + '...' : text;
|
|
199
195
|
const ruleName = getRuleNameFromRuleId(result.ruleId);
|
|
200
|
-
let output = `\t${
|
|
196
|
+
let output = `\t${(0, util_1.styleText)(['bold', result.level === "error" ? 'red' : 'yellow'], title)} [${ruleName}]\n`;
|
|
201
197
|
output += `\t${prettyPrintLocation(result)}\n`;
|
|
202
198
|
return output;
|
|
203
199
|
}
|
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;
|
|
@@ -60,7 +62,6 @@ const fs = __importStar(require("fs"));
|
|
|
60
62
|
const read_1 = __importDefault(require("read"));
|
|
61
63
|
const WebApi_1 = require("azure-devops-node-api/WebApi");
|
|
62
64
|
const GalleryApi_1 = require("azure-devops-node-api/GalleryApi");
|
|
63
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
64
65
|
const publicgalleryapi_1 = require("./publicgalleryapi");
|
|
65
66
|
const os_1 = require("os");
|
|
66
67
|
const __read = (0, util_1.promisify)(read_1.default);
|
|
@@ -113,6 +114,51 @@ function flatten(arr) {
|
|
|
113
114
|
function nonnull(arg) {
|
|
114
115
|
return !!arg;
|
|
115
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
|
+
}
|
|
116
162
|
const CancelledError = 'Cancelled';
|
|
117
163
|
function isCancelledError(error) {
|
|
118
164
|
return error === CancelledError;
|
|
@@ -155,10 +201,10 @@ var LogMessageType;
|
|
|
155
201
|
LogMessageType[LogMessageType["ERROR"] = 3] = "ERROR";
|
|
156
202
|
})(LogMessageType || (LogMessageType = {}));
|
|
157
203
|
const LogPrefix = {
|
|
158
|
-
[LogMessageType.DONE]:
|
|
159
|
-
[LogMessageType.INFO]:
|
|
160
|
-
[LogMessageType.WARNING]:
|
|
161
|
-
[LogMessageType.ERROR]:
|
|
204
|
+
[LogMessageType.DONE]: (0, util_1.styleText)(['bgGreen', 'black'], ' DONE '),
|
|
205
|
+
[LogMessageType.INFO]: (0, util_1.styleText)(['bgBlueBright', 'black'], ' INFO '),
|
|
206
|
+
[LogMessageType.WARNING]: (0, util_1.styleText)(['bgYellow', 'black'], ' WARNING '),
|
|
207
|
+
[LogMessageType.ERROR]: (0, util_1.styleText)(['bgRed', 'black'], ' ERROR '),
|
|
162
208
|
};
|
|
163
209
|
function _log(type, msg, ...args) {
|
|
164
210
|
args = [LogPrefix[type], msg, ...args];
|
|
@@ -303,11 +349,11 @@ async function generateFileStructureTree(rootFolder, filePaths, printLinesLimit
|
|
|
303
349
|
});
|
|
304
350
|
});
|
|
305
351
|
let output = [];
|
|
306
|
-
output.push(
|
|
352
|
+
output.push((0, util_1.styleText)('bold', rootFolder));
|
|
307
353
|
output.push(...createTreeOutput(folderTree, maxDepth, totalFileSizes));
|
|
308
354
|
for (const [size, filePath] of fileSizes) {
|
|
309
355
|
if (size > FILE_SIZE_WARNING_THRESHOLD * totalFileSizes) {
|
|
310
|
-
output.push(`\nThe file ${filePath} is ${
|
|
356
|
+
output.push(`\nThe file ${filePath} is ${(0, util_1.styleText)('red', 'large')} (${bytesToString(size)})`);
|
|
311
357
|
break;
|
|
312
358
|
}
|
|
313
359
|
}
|
|
@@ -316,20 +362,20 @@ async function generateFileStructureTree(rootFolder, filePaths, printLinesLimit
|
|
|
316
362
|
function createTreeOutput(fileSystem, maxDepth, totalFileSizes) {
|
|
317
363
|
const getColorFromSize = (size) => {
|
|
318
364
|
if (size > FILE_SIZE_WARNING_THRESHOLD * totalFileSizes) {
|
|
319
|
-
return
|
|
365
|
+
return 'red';
|
|
320
366
|
}
|
|
321
367
|
else if (size > FILE_SIZE_LARGE_THRESHOLD * totalFileSizes) {
|
|
322
|
-
return
|
|
368
|
+
return 'yellow';
|
|
323
369
|
}
|
|
324
370
|
else {
|
|
325
|
-
return
|
|
371
|
+
return 'gray';
|
|
326
372
|
}
|
|
327
373
|
};
|
|
328
374
|
const createFileOutput = (prefix, fileName, fileSize) => {
|
|
329
375
|
let fileSizeColored = '';
|
|
330
376
|
if (fileSize > 0) {
|
|
331
377
|
const fileSizeString = `[${bytesToString(fileSize)}]`;
|
|
332
|
-
fileSizeColored = getColorFromSize(fileSize)
|
|
378
|
+
fileSizeColored = (0, util_1.styleText)(getColorFromSize(fileSize), fileSizeString);
|
|
333
379
|
}
|
|
334
380
|
return `${prefix}${fileName} ${fileSizeColored}`;
|
|
335
381
|
};
|
|
@@ -337,14 +383,14 @@ function createTreeOutput(fileSystem, maxDepth, totalFileSizes) {
|
|
|
337
383
|
if (depth < maxDepth) {
|
|
338
384
|
// Max depth is not reached, print only the folder
|
|
339
385
|
// as children will be printed
|
|
340
|
-
return prefix +
|
|
386
|
+
return prefix + (0, util_1.styleText)('bold', `${folderName}/`);
|
|
341
387
|
}
|
|
342
388
|
// Max depth is reached, print the folder name and additional metadata
|
|
343
389
|
// as children will not be printed
|
|
344
390
|
const folderSizeString = bytesToString(folderSize);
|
|
345
|
-
const folder =
|
|
346
|
-
const numFilesString =
|
|
347
|
-
const folderSizeColored = getColorFromSize(folderSize)
|
|
391
|
+
const folder = (0, util_1.styleText)('bold', `${folderName}/`);
|
|
392
|
+
const numFilesString = (0, util_1.styleText)('green', `(${filesCount} ${filesCount === 1 ? 'file' : 'files'})`);
|
|
393
|
+
const folderSizeColored = (0, util_1.styleText)(getColorFromSize(folderSize), `[${folderSizeString}]`);
|
|
348
394
|
return `${prefix}${folder} ${numFilesString} ${folderSizeColored}`;
|
|
349
395
|
};
|
|
350
396
|
const createTreeLayerOutput = (tree, depth, prefix, path) => {
|
|
@@ -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-
|
|
3
|
+
"version": "3.9.3-12",
|
|
4
4
|
"description": "VS Code Extensions Manager",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,51 +39,50 @@
|
|
|
39
39
|
"node": ">= 22"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@azure/identity": "^4.
|
|
42
|
+
"@azure/identity": "^4.13.2",
|
|
43
43
|
"@napi-rs/keyring": "^1.3.0",
|
|
44
|
-
"@secretlint/core": "^10.
|
|
45
|
-
"@secretlint/secretlint-rule-no-dotenv": "^10.
|
|
46
|
-
"@secretlint/secretlint-rule-preset-recommend": "^10.
|
|
47
|
-
"@secretlint/source-creator": "^10.
|
|
48
|
-
"@secretlint/types": "^10.
|
|
49
|
-
"@vscode/vsce-sign": "^2.
|
|
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
|
-
"
|
|
52
|
-
"cockatiel": "^3.1.2",
|
|
51
|
+
"cockatiel": "^3.2.1",
|
|
53
52
|
"commander": "^12.1.0",
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"mime": "^1.3.4",
|
|
60
|
-
"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",
|
|
61
58
|
"parse5": "^8.0.1",
|
|
59
|
+
"proper-lockfile": "^4.1.2",
|
|
62
60
|
"read": "^1.0.7",
|
|
63
|
-
"semver": "^7.5
|
|
61
|
+
"semver": "^7.8.5",
|
|
64
62
|
"tinyglobby": "^0.2.17",
|
|
65
|
-
"typed-rest-client": "^1.8.
|
|
63
|
+
"typed-rest-client": "^1.8.11",
|
|
66
64
|
"url-join": "^4.0.1",
|
|
67
65
|
"xml2js": "^0.5.0",
|
|
68
|
-
"yauzl": "^3.
|
|
69
|
-
"yazl": "^2.
|
|
66
|
+
"yauzl": "^3.4.0",
|
|
67
|
+
"yazl": "^2.5.1"
|
|
70
68
|
},
|
|
71
69
|
"devDependencies": {
|
|
72
|
-
"@microsoft/api-extractor": "^7.
|
|
73
|
-
"@types/hosted-git-info": "^3.0.
|
|
74
|
-
"@types/mime": "^1",
|
|
70
|
+
"@microsoft/api-extractor": "^7.59.0",
|
|
71
|
+
"@types/hosted-git-info": "^3.0.5",
|
|
72
|
+
"@types/mime": "^1.3.5",
|
|
75
73
|
"@types/mocha": "^7.0.2",
|
|
76
|
-
"@types/node": "^22.
|
|
74
|
+
"@types/node": "^22.20.1",
|
|
77
75
|
"@types/picomatch": "^4.0.3",
|
|
76
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
78
77
|
"@types/read": "^0.0.28",
|
|
79
|
-
"@types/semver": "^6.
|
|
80
|
-
"@types/url-join": "^4.0.
|
|
81
|
-
"@types/xml2js": "^0.4.
|
|
82
|
-
"@types/yauzl": "^2.
|
|
83
|
-
"@types/yazl": "^2.4.
|
|
84
|
-
"mocha": "^11.
|
|
85
|
-
"ts-node": "^10.9.
|
|
86
|
-
"typescript": "~5.9.
|
|
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"
|
|
87
86
|
},
|
|
88
87
|
"mocha": {
|
|
89
88
|
"require": [
|
|
@@ -94,6 +93,7 @@
|
|
|
94
93
|
},
|
|
95
94
|
"overrides": {
|
|
96
95
|
"serialize-javascript": "7.x",
|
|
97
|
-
"diff": "^9.0.0"
|
|
96
|
+
"diff": "^9.0.0",
|
|
97
|
+
"@azure/msal-common": "16.14.0"
|
|
98
98
|
}
|
|
99
99
|
}
|