ovsx 1.2.0 → 1.4.0
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 +7 -1
- package/lib/check-license.d.ts.map +1 -1
- package/lib/check-license.js +5 -4
- package/lib/check-license.js.map +1 -1
- package/lib/create-namespace.d.ts.map +1 -1
- package/lib/create-namespace.js +3 -1
- package/lib/create-namespace.js.map +1 -1
- package/lib/get.js +5 -5
- package/lib/get.js.map +1 -1
- package/lib/http.d.ts +32 -0
- package/lib/http.d.ts.map +1 -0
- package/lib/http.js +170 -0
- package/lib/http.js.map +1 -0
- package/lib/index.d.ts +0 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -3
- package/lib/index.js.map +1 -1
- package/lib/main.js +50 -61
- package/lib/main.js.map +1 -1
- package/lib/oidc.d.ts.map +1 -1
- package/lib/oidc.js +5 -47
- package/lib/oidc.js.map +1 -1
- package/lib/pat.d.ts +6 -1
- package/lib/pat.d.ts.map +1 -1
- package/lib/pat.js +16 -7
- package/lib/pat.js.map +1 -1
- package/lib/publish.d.ts +6 -0
- package/lib/publish.d.ts.map +1 -1
- package/lib/publish.js +147 -40
- package/lib/publish.js.map +1 -1
- package/lib/registry.d.ts +50 -12
- package/lib/registry.d.ts.map +1 -1
- package/lib/registry.js +131 -126
- package/lib/registry.js.map +1 -1
- package/lib/trusted-publishing-options.d.ts +5 -0
- package/lib/trusted-publishing-options.d.ts.map +1 -1
- package/lib/trusted-publishing.js +28 -4
- package/lib/trusted-publishing.js.map +1 -1
- package/lib/unpublish.d.ts.map +1 -1
- package/lib/unpublish.js +5 -8
- package/lib/unpublish.js.map +1 -1
- package/lib/util.d.ts +19 -15
- package/lib/util.d.ts.map +1 -1
- package/lib/util.js +51 -71
- package/lib/util.js.map +1 -1
- package/lib/verify-pat.d.ts.map +1 -1
- package/lib/verify-pat.js +3 -1
- package/lib/verify-pat.js.map +1 -1
- package/lib/verify.js +2 -2
- package/lib/verify.js.map +1 -1
- package/lib/zip.d.ts.map +1 -1
- package/lib/zip.js +2 -10
- package/lib/zip.js.map +1 -1
- package/package.json +19 -14
- package/src/check-license.ts +5 -4
- package/src/create-namespace.ts +3 -1
- package/src/get.ts +6 -6
- package/src/http.ts +176 -0
- package/src/index.ts +0 -3
- package/src/main.ts +54 -63
- package/src/oidc.ts +5 -47
- package/src/pat.ts +22 -9
- package/src/publish.ts +187 -44
- package/src/registry.ts +153 -126
- package/src/trusted-publishing-options.ts +5 -0
- package/src/trusted-publishing.ts +33 -5
- package/src/unpublish.ts +4 -7
- package/src/util.ts +47 -73
- package/src/verify-pat.ts +3 -1
- package/src/verify.ts +3 -3
- package/src/zip.ts +2 -12
- package/lib/version.d.ts +0 -2
- package/lib/version.d.ts.map +0 -1
- package/lib/version.js +0 -5
- package/lib/version.js.map +0 -1
- package/src/version.ts +0 -1
package/src/publish.ts
CHANGED
|
@@ -8,15 +8,26 @@
|
|
|
8
8
|
* SPDX-License-Identifier: EPL-2.0
|
|
9
9
|
********************************************************************************/
|
|
10
10
|
import * as fs from 'fs';
|
|
11
|
+
import * as semver from 'semver';
|
|
11
12
|
import { createVSIX, IPackageOptions } from '@vscode/vsce';
|
|
12
13
|
import { getPAT } from './pat';
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getTempFilePath,
|
|
16
|
+
addEnvOptions,
|
|
17
|
+
addTrustedPublishingEnvOptions,
|
|
18
|
+
formatBytes,
|
|
19
|
+
Manifest,
|
|
20
|
+
StatusError
|
|
21
|
+
} from './util';
|
|
14
22
|
import { Extension, Registry } from './registry';
|
|
15
23
|
import { checkLicense } from './check-license';
|
|
16
24
|
import { readVSIXPackage } from './zip';
|
|
17
25
|
import { PublishOptions, PublishCommonOptions } from './publish-options';
|
|
18
26
|
import { getTrustedPublishingToken, refreshTrustedPublishingToken, useTrustedPublishing } from './trusted-publishing';
|
|
19
27
|
|
|
28
|
+
/** Registries older than this cannot report the limit that applies to a namespace. */
|
|
29
|
+
const MIN_SIZE_LIMIT_REGISTRY_VERSION = '1.3.0';
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
32
|
* Publishes an extension.
|
|
22
33
|
*/
|
|
@@ -24,27 +35,74 @@ export async function publish(options: PublishOptions = {}): Promise<PromiseSett
|
|
|
24
35
|
addEnvOptions(options);
|
|
25
36
|
addTrustedPublishingEnvOptions(options);
|
|
26
37
|
|
|
27
|
-
// Looked up once and shared by every target/package below, rather than once per artifact.
|
|
28
|
-
const maxExtensionSize = await getMaxExtensionSize(new Registry(options));
|
|
29
|
-
|
|
30
38
|
const internalPublishOptions: InternalPublishOptions[] = [];
|
|
39
|
+
const prepackagedFiles: string[] = [];
|
|
31
40
|
const packagePaths = options.packagePath || [undefined];
|
|
32
41
|
const targets = options.targets || [undefined];
|
|
33
42
|
for (const packagePath of packagePaths) {
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
const extensionFile = options.extensionFile ?? (packagePath?.endsWith('.vsix') ? packagePath : undefined);
|
|
44
|
+
if (extensionFile !== undefined) {
|
|
45
|
+
prepackagedFiles.push(extensionFile);
|
|
46
|
+
// A package is already built for its target, so it is published once, not once per target.
|
|
47
|
+
internalPublishOptions.push({ ...options, extensionFile, packagePath: undefined, target: undefined });
|
|
48
|
+
} else {
|
|
49
|
+
for (const target of targets) {
|
|
50
|
+
internalPublishOptions.push({ ...options, packagePath, target });
|
|
51
|
+
}
|
|
36
52
|
}
|
|
37
53
|
}
|
|
54
|
+
// Before the registry is contacted, so a slow or failing lookup cannot keep these from being seen.
|
|
55
|
+
warnIgnoredPackagingOptions(options, prepackagedFiles);
|
|
38
56
|
|
|
39
|
-
|
|
57
|
+
// Shared by every target/package below, rather than one per artifact: besides saving the extra
|
|
58
|
+
// `/api/version` round trips, it's also where the registry's reported version is cached (see
|
|
59
|
+
// Registry.tokenQuery), so a wide fan-out doesn't repeat that lookup once per target either.
|
|
60
|
+
const registry = new Registry(options);
|
|
61
|
+
const maxExtensionSize = await getMaxExtensionSize(registry);
|
|
62
|
+
|
|
63
|
+
return Promise.allSettled(internalPublishOptions.map(publishOptions =>
|
|
64
|
+
doPublish(registry, { ...publishOptions, maxExtensionSize })));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Names the files in the warning: next to a directory being packaged, the options still apply to that.
|
|
69
|
+
*/
|
|
70
|
+
function warnIgnoredPackagingOptions(options: PublishOptions, prepackagedFiles: string[]): void {
|
|
71
|
+
const files = [...new Set(prepackagedFiles)];
|
|
72
|
+
if (files.length === 0) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const subject = `${files.length === 1 ? 'extension' : 'extensions'} ${files.map(file => `'${file}'`).join(', ')}`;
|
|
76
|
+
for (const [flag, key] of PACKAGING_OPTIONS) {
|
|
77
|
+
// An explicit `false` is not a flag that was given.
|
|
78
|
+
if (options[key] !== undefined && options[key] !== false) {
|
|
79
|
+
console.warn(`Ignoring option '${flag}' for prepackaged ${subject}.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
40
82
|
}
|
|
41
83
|
|
|
42
84
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
85
|
+
* Options that only affect packaging, with the CLI flag that sets each, in the order they are reported
|
|
86
|
+
* as ignored for a prepackaged extension. `--no-dependencies` is left out: the CLI always sets
|
|
87
|
+
* `dependencies`, so whether it was given cannot be told apart.
|
|
88
|
+
*/
|
|
89
|
+
export const PACKAGING_OPTIONS: ReadonlyArray<readonly [string, keyof PublishOptions]> = [
|
|
90
|
+
['--target', 'targets'],
|
|
91
|
+
['--baseContentUrl', 'baseContentUrl'],
|
|
92
|
+
['--baseImagesUrl', 'baseImagesUrl'],
|
|
93
|
+
['--yarn', 'yarn'],
|
|
94
|
+
['--follow-symlinks', 'followSymlinks'],
|
|
95
|
+
['--packageVersion', 'packageVersion'],
|
|
96
|
+
['--allow-missing-repository', 'allowMissingRepository'],
|
|
97
|
+
['--pre-release', 'preRelease']
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Looks up the registry's default extension size limit, so an oversized package can be flagged
|
|
102
|
+
* before the upload instead of after transferring the whole payload.
|
|
45
103
|
*
|
|
46
104
|
* Best-effort: registries that don't expose `/api/version`, or don't report a limit, return
|
|
47
|
-
* `undefined` here, and
|
|
105
|
+
* `undefined` here, and nothing is said about the size.
|
|
48
106
|
*/
|
|
49
107
|
async function getMaxExtensionSize(registry: Registry): Promise<number | undefined> {
|
|
50
108
|
try {
|
|
@@ -54,39 +112,53 @@ async function getMaxExtensionSize(registry: Registry): Promise<number | undefin
|
|
|
54
112
|
}
|
|
55
113
|
}
|
|
56
114
|
|
|
57
|
-
async function doPublish(options: InternalPublishOptions = {}): Promise<void> {
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
115
|
+
async function doPublish(registry: Registry, options: InternalPublishOptions = {}): Promise<void> {
|
|
116
|
+
// Only a package created here is a temp file; one the user supplied must never be deleted.
|
|
117
|
+
const packaged = !options.extensionFile;
|
|
118
|
+
try {
|
|
119
|
+
if (packaged) {
|
|
120
|
+
await packageExtension(options, registry);
|
|
121
|
+
console.log(); // new line
|
|
122
|
+
}
|
|
123
|
+
await publishExtensionFile(registry, options);
|
|
124
|
+
} finally {
|
|
125
|
+
if (packaged && options.extensionFile) {
|
|
126
|
+
// Caught rather than awaited plainly: a failure here (e.g. the temp dir is locked or
|
|
127
|
+
// read-only) must not replace whatever error publishExtensionFile already threw above.
|
|
128
|
+
await fs.promises.rm(options.extensionFile, { force: true }).catch(err => {
|
|
129
|
+
console.warn(`Could not delete temporary file ${options.extensionFile}: ${err.message}`);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
70
132
|
}
|
|
133
|
+
}
|
|
71
134
|
|
|
72
|
-
|
|
135
|
+
async function publishExtensionFile(registry: Registry, options: InternalPublishOptions): Promise<void> {
|
|
136
|
+
// Read up front rather than only when a token has to be obtained: the size limit is looked up per
|
|
137
|
+
// namespace, and the namespace lives in the manifest.
|
|
138
|
+
const manifest = await readVSIXPackage(options.extensionFile!);
|
|
73
139
|
|
|
74
140
|
// Set only when this publish obtained the token itself through trusted publishing, which is the one
|
|
75
141
|
// case where a refusal can be answered by asking for a new token.
|
|
76
142
|
let exchanged: { namespace: string; extension: string } | undefined;
|
|
77
143
|
if (!options.pat) {
|
|
78
|
-
const manifest = await readVSIXPackage(options.extensionFile!);
|
|
79
144
|
if (useTrustedPublishing(options)) {
|
|
80
145
|
exchanged = { namespace: manifest.publisher, extension: manifest.name };
|
|
81
146
|
options.pat = await getTrustedPublishingToken(registry, manifest.publisher, manifest.name, options);
|
|
82
147
|
} else {
|
|
83
|
-
|
|
148
|
+
// getPAT has verified a prompted token already, so it can be stored before publishing
|
|
149
|
+
const { pat, save } = await getPAT(manifest.publisher, options);
|
|
150
|
+
options.pat = pat;
|
|
151
|
+
await save();
|
|
84
152
|
}
|
|
85
153
|
}
|
|
86
154
|
|
|
155
|
+
// After the token is resolved, because the lookup is authenticated - and by here a token always
|
|
156
|
+
// exists, whether supplied, fetched, or exchanged through trusted publishing.
|
|
157
|
+
const sizeLimit = await ensureWithinSizeLimit(registry, options, manifest);
|
|
158
|
+
|
|
87
159
|
let extension: Extension | undefined;
|
|
88
160
|
try {
|
|
89
|
-
extension = await doRegistryPublish(registry, options, exchanged);
|
|
161
|
+
extension = await doRegistryPublish(registry, options, exchanged, sizeLimit);
|
|
90
162
|
} catch (err) {
|
|
91
163
|
if (options.skipDuplicate && err.message.endsWith('is already published.')) {
|
|
92
164
|
console.log(err.message + ' Skipping publish.');
|
|
@@ -123,10 +195,11 @@ async function doPublish(options: InternalPublishOptions = {}): Promise<void> {
|
|
|
123
195
|
async function doRegistryPublish(
|
|
124
196
|
registry: Registry,
|
|
125
197
|
options: InternalPublishOptions,
|
|
126
|
-
exchanged: { namespace: string; extension: string } | undefined
|
|
198
|
+
exchanged: { namespace: string; extension: string } | undefined,
|
|
199
|
+
sizeLimit?: number
|
|
127
200
|
): Promise<Extension> {
|
|
128
201
|
try {
|
|
129
|
-
return await registry.publish(options.extensionFile!, options.pat
|
|
202
|
+
return await registry.publish(options.extensionFile!, options.pat!, sizeLimit);
|
|
130
203
|
} catch (err) {
|
|
131
204
|
if (!exchanged || (err as StatusError)?.status !== 401) {
|
|
132
205
|
throw err;
|
|
@@ -140,28 +213,97 @@ async function doRegistryPublish(
|
|
|
140
213
|
options,
|
|
141
214
|
options.pat!
|
|
142
215
|
);
|
|
143
|
-
return registry.publish(options.extensionFile!, options.pat);
|
|
216
|
+
return registry.publish(options.extensionFile!, options.pat, sizeLimit);
|
|
144
217
|
}
|
|
145
218
|
}
|
|
146
219
|
|
|
147
220
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
221
|
+
* Refuses a package the registry would reject anyway, before uploading it, and reports how many
|
|
222
|
+
* bytes it has concluded may be sent - the caller raises the transport cap to that, so nothing we
|
|
223
|
+
* decided to upload is then refused locally.
|
|
224
|
+
*
|
|
225
|
+
* The limit is the one that applies to this package's namespace and extension, which a size override
|
|
226
|
+
* can raise above the registry default. Registries older than
|
|
227
|
+
* {@link MIN_SIZE_LIMIT_REGISTRY_VERSION} cannot report it, so there the check stays advisory: it
|
|
228
|
+
* warns against the default and publishes, rather than refusing an upload those registries would
|
|
229
|
+
* have accepted.
|
|
152
230
|
*/
|
|
153
|
-
async function ensureWithinSizeLimit(
|
|
154
|
-
|
|
155
|
-
|
|
231
|
+
async function ensureWithinSizeLimit(
|
|
232
|
+
registry: Registry,
|
|
233
|
+
options: InternalPublishOptions,
|
|
234
|
+
manifest: Manifest
|
|
235
|
+
): Promise<number | undefined> {
|
|
236
|
+
const { size } = await fs.promises.stat(options.extensionFile!);
|
|
237
|
+
const limit = await resolveSizeLimit(registry, options, manifest);
|
|
238
|
+
|
|
239
|
+
if (limit.kind === 'failed') {
|
|
240
|
+
// Always said, however small the package: a size override can lower a limit as well as raise
|
|
241
|
+
// it, so there is no size from which it follows that this upload is fine. Staying quiet here
|
|
242
|
+
// left a registry whose /api/-/* routes never arrive indistinguishable from a working one.
|
|
243
|
+
console.warn(
|
|
244
|
+
`Could not check the size limit for ${manifest.publisher}.${manifest.name} at ${registry.url} `
|
|
245
|
+
+ `(${limit.reason}). Publishing anyway: the registry enforces its own limit, so an oversized `
|
|
246
|
+
+ `package is refused after the upload rather than before it.`
|
|
247
|
+
);
|
|
248
|
+
} else if (limit.kind === 'unsupported') {
|
|
249
|
+
const fallback = options.maxExtensionSize;
|
|
250
|
+
if (fallback && size > fallback) {
|
|
251
|
+
console.warn(
|
|
252
|
+
`The extension package (${formatBytes(size)}, ${size} bytes) exceeds the default size limit of `
|
|
253
|
+
+ `${formatBytes(fallback)} (${fallback} bytes) `
|
|
254
|
+
+ `reported by the registry at ${registry.url}, which is too old to report the limit for a `
|
|
255
|
+
+ `single namespace. Publishing anyway: the namespace may have a different limit configured, `
|
|
256
|
+
+ `and the registry decides.`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
156
259
|
}
|
|
157
260
|
|
|
158
|
-
|
|
159
|
-
|
|
261
|
+
if (limit.kind !== 'resolved') {
|
|
262
|
+
// The allowance is this package, not the built-in publish size: having decided to send it and
|
|
263
|
+
// let the registry answer, the transport cap must not be what refuses it instead. An older
|
|
264
|
+
// registry whose default is above 512 MiB would otherwise reject a package it would accept.
|
|
265
|
+
return size;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (size > limit.limit) {
|
|
160
269
|
throw new Error(
|
|
161
|
-
`The extension package (${formatBytes(size)}) exceeds the size limit of
|
|
162
|
-
+
|
|
270
|
+
`The extension package (${formatBytes(size)}, ${size} bytes) exceeds the size limit of `
|
|
271
|
+
+ `${formatBytes(limit.limit)} (${limit.limit} bytes) `
|
|
272
|
+
+ `for ${manifest.publisher}.${manifest.name} at ${registry.url}.`
|
|
163
273
|
);
|
|
164
274
|
}
|
|
275
|
+
return limit.limit;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The limit for this namespace/extension. A registry too old to report one and a registry that could
|
|
280
|
+
* not answer are told apart: both publish anyway, but only the second one means a check the user is
|
|
281
|
+
* entitled to expect did not happen.
|
|
282
|
+
*/
|
|
283
|
+
type SizeLimitLookup =
|
|
284
|
+
| { kind: 'resolved'; limit: number }
|
|
285
|
+
| { kind: 'unsupported' }
|
|
286
|
+
| { kind: 'failed'; reason: string };
|
|
287
|
+
|
|
288
|
+
async function resolveSizeLimit(
|
|
289
|
+
registry: Registry,
|
|
290
|
+
options: InternalPublishOptions,
|
|
291
|
+
manifest: Manifest
|
|
292
|
+
): Promise<SizeLimitLookup> {
|
|
293
|
+
const reported = await registry.getRegistryVersion().catch(() => undefined);
|
|
294
|
+
const version = reported?.version ? semver.coerce(reported.version) : undefined;
|
|
295
|
+
if (!version || semver.lt(version, MIN_SIZE_LIMIT_REGISTRY_VERSION)) {
|
|
296
|
+
return { kind: 'unsupported' };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const { maxSize } = await registry.getSizeLimit(manifest.publisher, manifest.name, options.pat!);
|
|
301
|
+
return { kind: 'resolved', limit: maxSize };
|
|
302
|
+
} catch (err) {
|
|
303
|
+
// The registry claims to be new enough but could not answer. It enforces the limit itself
|
|
304
|
+
// regardless, so publish rather than refusing on a failed lookup.
|
|
305
|
+
return { kind: 'failed', reason: err instanceof Error ? err.message : String(err) };
|
|
306
|
+
}
|
|
165
307
|
}
|
|
166
308
|
|
|
167
309
|
async function packageExtension(options: InternalPublishOptions, registry: Registry): Promise<void> {
|
|
@@ -169,7 +311,7 @@ async function packageExtension(options: InternalPublishOptions, registry: Regis
|
|
|
169
311
|
await checkLicense(options.packagePath!);
|
|
170
312
|
}
|
|
171
313
|
|
|
172
|
-
options.extensionFile =
|
|
314
|
+
options.extensionFile = getTempFilePath('.vsix');
|
|
173
315
|
const packageOptions: IPackageOptions = {
|
|
174
316
|
packagePath: options.extensionFile,
|
|
175
317
|
target: options.target,
|
|
@@ -208,8 +350,9 @@ interface InternalPublishOptions extends PublishCommonOptions {
|
|
|
208
350
|
dependencies?: boolean;
|
|
209
351
|
|
|
210
352
|
/**
|
|
211
|
-
* The registry's
|
|
212
|
-
* shared across every target/package being published.
|
|
353
|
+
* The registry's default extension size limit in bytes, looked up once via `/api/version` and
|
|
354
|
+
* shared across every target/package being published. Advisory: a namespace override can allow
|
|
355
|
+
* more. `undefined` when it couldn't be determined.
|
|
213
356
|
*/
|
|
214
357
|
maxExtensionSize?: number;
|
|
215
358
|
}
|