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.
Files changed (76) hide show
  1. package/README.md +7 -1
  2. package/lib/check-license.d.ts.map +1 -1
  3. package/lib/check-license.js +5 -4
  4. package/lib/check-license.js.map +1 -1
  5. package/lib/create-namespace.d.ts.map +1 -1
  6. package/lib/create-namespace.js +3 -1
  7. package/lib/create-namespace.js.map +1 -1
  8. package/lib/get.js +5 -5
  9. package/lib/get.js.map +1 -1
  10. package/lib/http.d.ts +32 -0
  11. package/lib/http.d.ts.map +1 -0
  12. package/lib/http.js +170 -0
  13. package/lib/http.js.map +1 -0
  14. package/lib/index.d.ts +0 -3
  15. package/lib/index.d.ts.map +1 -1
  16. package/lib/index.js +0 -3
  17. package/lib/index.js.map +1 -1
  18. package/lib/main.js +50 -61
  19. package/lib/main.js.map +1 -1
  20. package/lib/oidc.d.ts.map +1 -1
  21. package/lib/oidc.js +5 -47
  22. package/lib/oidc.js.map +1 -1
  23. package/lib/pat.d.ts +6 -1
  24. package/lib/pat.d.ts.map +1 -1
  25. package/lib/pat.js +16 -7
  26. package/lib/pat.js.map +1 -1
  27. package/lib/publish.d.ts +6 -0
  28. package/lib/publish.d.ts.map +1 -1
  29. package/lib/publish.js +147 -40
  30. package/lib/publish.js.map +1 -1
  31. package/lib/registry.d.ts +50 -12
  32. package/lib/registry.d.ts.map +1 -1
  33. package/lib/registry.js +131 -126
  34. package/lib/registry.js.map +1 -1
  35. package/lib/trusted-publishing-options.d.ts +5 -0
  36. package/lib/trusted-publishing-options.d.ts.map +1 -1
  37. package/lib/trusted-publishing.js +28 -4
  38. package/lib/trusted-publishing.js.map +1 -1
  39. package/lib/unpublish.d.ts.map +1 -1
  40. package/lib/unpublish.js +5 -8
  41. package/lib/unpublish.js.map +1 -1
  42. package/lib/util.d.ts +19 -15
  43. package/lib/util.d.ts.map +1 -1
  44. package/lib/util.js +51 -71
  45. package/lib/util.js.map +1 -1
  46. package/lib/verify-pat.d.ts.map +1 -1
  47. package/lib/verify-pat.js +3 -1
  48. package/lib/verify-pat.js.map +1 -1
  49. package/lib/verify.js +2 -2
  50. package/lib/verify.js.map +1 -1
  51. package/lib/zip.d.ts.map +1 -1
  52. package/lib/zip.js +2 -10
  53. package/lib/zip.js.map +1 -1
  54. package/package.json +19 -14
  55. package/src/check-license.ts +5 -4
  56. package/src/create-namespace.ts +3 -1
  57. package/src/get.ts +6 -6
  58. package/src/http.ts +176 -0
  59. package/src/index.ts +0 -3
  60. package/src/main.ts +54 -63
  61. package/src/oidc.ts +5 -47
  62. package/src/pat.ts +22 -9
  63. package/src/publish.ts +187 -44
  64. package/src/registry.ts +153 -126
  65. package/src/trusted-publishing-options.ts +5 -0
  66. package/src/trusted-publishing.ts +33 -5
  67. package/src/unpublish.ts +4 -7
  68. package/src/util.ts +47 -73
  69. package/src/verify-pat.ts +3 -1
  70. package/src/verify.ts +3 -3
  71. package/src/zip.ts +2 -12
  72. package/lib/version.d.ts +0 -2
  73. package/lib/version.d.ts.map +0 -1
  74. package/lib/version.js +0 -5
  75. package/lib/version.js.map +0 -1
  76. package/src/version.ts +0 -1
package/src/util.ts CHANGED
@@ -8,15 +8,15 @@
8
8
  * SPDX-License-Identifier: EPL-2.0
9
9
  ********************************************************************************/
10
10
 
11
+ import * as crypto from 'crypto';
11
12
  import * as fs from 'fs';
13
+ import * as os from 'os';
12
14
  import * as path from 'path';
13
- import * as tmp from 'tmp';
14
15
  import * as http from 'http';
16
+ import { InvalidArgumentError } from 'commander';
15
17
  import { RegistryOptions } from './registry-options';
16
18
  import { TrustedPublishingOptions } from './trusted-publishing-options';
17
19
 
18
- export { promisify } from 'util';
19
-
20
20
  export function addEnvOptions(options: RegistryOptions): void {
21
21
  options.registryUrl ??= process.env.OVSX_REGISTRY_URL;
22
22
  options.pat ??= process.env.OVSX_PAT;
@@ -29,6 +29,7 @@ export function addTrustedPublishingEnvOptions(options: TrustedPublishingOptions
29
29
  options.trustedPublishing ??= parseBooleanEnv(process.env.OVSX_TRUSTED_PUBLISHING);
30
30
  options.idToken ??= process.env.OVSX_ID_TOKEN;
31
31
  options.oidcAudience ??= process.env.OVSX_OIDC_AUDIENCE;
32
+ options.registrationCode ??= process.env.OVSX_REGISTRATION_CODE;
32
33
  }
33
34
 
34
35
  /** How long a request may make no progress before it is abandoned; see RegistryOptions.timeout. */
@@ -67,7 +68,8 @@ function parseBooleanEnv(value?: string): boolean | undefined {
67
68
  export function parseNonNegativeInt(value: string): number {
68
69
  const parsed = Number(value);
69
70
  if (!Number.isInteger(parsed) || parsed < 0) {
70
- throw new Error(`Expected a non-negative whole number, got '${value}'.`);
71
+ // Commander prints only this error type as a usage message; anything else escapes as a stack trace.
72
+ throw new InvalidArgumentError(`Expected a non-negative whole number, got '${value}'.`);
71
73
  }
72
74
  return parsed;
73
75
  }
@@ -76,36 +78,39 @@ export function matchExtensionId(id: string): RegExpExecArray | null {
76
78
  return /^([\w-]+)(?:\.|\/)([\w-]+)$/.exec(id);
77
79
  }
78
80
 
79
- export function optionalStat(path: fs.PathLike): Promise<fs.Stats | undefined> {
80
- return new Promise((resolve, reject) => {
81
- fs.stat(path, (err, stats) => resolve(stats));
82
- });
83
- }
84
-
85
- export function makeDirs(path: fs.PathLike): Promise<void> {
86
- return new Promise((resolve, reject) => {
87
- if (fs.existsSync(path)) {
88
- resolve();
89
- } else {
90
- fs.mkdir(path, { recursive: true }, (err: NodeJS.ErrnoException | null) => {
91
- if (err)
92
- reject(err);
93
- else
94
- resolve();
95
- });
81
+ /**
82
+ * Levenshtein distance between `a` and `b`: the minimum number of single-character insertions,
83
+ * deletions or substitutions needed to turn one into the other. Used to suggest the closest known
84
+ * command for a typo.
85
+ */
86
+ export function levenshtein(a: string, b: string): number {
87
+ if (a === b) {
88
+ return 0;
89
+ }
90
+ // The distance is symmetric, so keep the shorter string in `b` to bound the row size.
91
+ if (b.length > a.length) {
92
+ [a, b] = [b, a];
93
+ }
94
+ // A single row of the edit distance matrix, seeded with the distance between the empty prefix
95
+ // of `a` and every prefix of `b`.
96
+ const row = Array.from({ length: b.length + 1 }, (_, j) => j);
97
+ for (let i = 1; i <= a.length; i++) {
98
+ let diagonal = row[0];
99
+ row[0] = i;
100
+ for (let j = 1; j <= b.length; j++) {
101
+ const above = row[j];
102
+ row[j] = Math.min(row[j - 1] + 1, above + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));
103
+ diagonal = above;
96
104
  }
97
- });
105
+ }
106
+ return row[b.length];
98
107
  }
99
108
 
100
- export function createTempFile(options: tmp.TmpNameOptions): Promise<string> {
101
- return new Promise((resolve, reject) => {
102
- tmp.tmpName(options, (err: Error | null, name: string) => {
103
- if (err)
104
- reject(err);
105
- else
106
- resolve(name);
107
- });
108
- });
109
+ /**
110
+ * Returns a random, not-yet-created file path ending in `postfix`, directly in the OS temp directory.
111
+ */
112
+ export function getTempFilePath(postfix = ''): string {
113
+ return path.join(os.tmpdir(), `ovsx-${crypto.randomUUID()}${postfix}`);
109
114
  }
110
115
 
111
116
  export function rejectError(err: any) {
@@ -135,9 +140,9 @@ export function handleError(debug?: boolean, additionalMessage?: string, exit: b
135
140
  }
136
141
 
137
142
  /**
138
- * Formats a byte count for display, e.g. `1536` -> `1.5 KB`. Mirrors the registry's own
139
- * `FileUtils.byteCountToDisplaySize` formatting so client- and server-side size limit messages read
140
- * the same way.
143
+ * Formats a byte count for display, rounding down, e.g. `1536` -> `1 KB` and `1048577` -> `1 MB`.
144
+ * Mirrors the registry's own `FileUtils.byteCountToDisplaySize` formatting so client- and
145
+ * server-side size limit messages read the same way.
141
146
  */
142
147
  export function formatBytes(bytes: number): string {
143
148
  const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
@@ -167,10 +172,12 @@ export function withStatus(error: Error, status?: number): StatusError {
167
172
  /**
168
173
  * A URL in the form it can safely be printed: no user info and no query string.
169
174
  *
170
- * `createNamespace`, `verifyPat`, `publish` and `delete` all put the personal access token in a
171
- * `token` query parameter, and an error message goes straight to stderr and from there into CI logs.
172
- * The query carries nothing an error needs, so it is dropped whole rather than filtered key by key -
173
- * a filter has to be kept in step with every parameter anyone ever adds.
175
+ * The personal access token travels in a header now, but `createNamespace`, `verifyPat`, `publish`
176
+ * and `delete` still carry it in the query string too when talking to a registry too old to resolve
177
+ * it from the header (see `Registry.tokenQuery`, #1344). An error message goes straight to stderr
178
+ * and from there into CI logs, and the query carries nothing an error needs - so it stays dropped
179
+ * whole rather than filtered key by key, a filter having to be kept in step with every parameter
180
+ * anyone ever adds.
174
181
  */
175
182
  export function redactUrl(url: URL | string): string {
176
183
  try {
@@ -189,24 +196,8 @@ export function statusError(response: http.IncomingMessage): StatusError {
189
196
  return withStatus(new Error(message), response.statusCode);
190
197
  }
191
198
 
192
- export function readFile(name: string, packagePath?: string, encoding: BufferEncoding = 'utf-8'): Promise<string> {
193
- return new Promise((resolve, reject) => {
194
- fs.readFile(
195
- path.join(packagePath ?? process.cwd(), name),
196
- { encoding },
197
- (err: NodeJS.ErrnoException | null, content: string) => {
198
- if (err) {
199
- reject(err);
200
- } else {
201
- resolve(content);
202
- }
203
- }
204
- );
205
- });
206
- }
207
-
208
199
  export async function readManifest(packagePath?: string): Promise<Manifest> {
209
- const content = await readFile('package.json', packagePath);
200
+ const content = await fs.promises.readFile(path.join(packagePath ?? process.cwd(), 'package.json'), 'utf-8');
210
201
  return JSON.parse(content);
211
202
  }
212
203
 
@@ -222,26 +213,9 @@ export function validateManifest(manifest: Manifest): void {
222
213
  }
223
214
  }
224
215
 
225
- export function writeFile(name: string, content: string, packagePath?: string, encoding: BufferEncoding = 'utf-8'): Promise<void> {
226
- return new Promise((resolve, reject) => {
227
- fs.writeFile(
228
- path.join(packagePath ?? process.cwd(), name),
229
- content,
230
- { encoding },
231
- (err: NodeJS.ErrnoException | null) => {
232
- if (err) {
233
- reject(err);
234
- } else {
235
- resolve();
236
- }
237
- }
238
- );
239
- });
240
- }
241
-
242
216
  export function writeManifest(manifest: Manifest, packagePath?: string): Promise<void> {
243
217
  const content = JSON.stringify(manifest, null, 4);
244
- return writeFile('package.json', content, packagePath);
218
+ return fs.promises.writeFile(path.join(packagePath ?? process.cwd(), 'package.json'), content, 'utf-8');
245
219
  }
246
220
 
247
221
  export interface Manifest {
package/src/verify-pat.ts CHANGED
@@ -33,6 +33,8 @@ export async function verifyPat(options: VerifyPatOptions): Promise<void> {
33
33
  }
34
34
  }
35
35
 
36
- options.pat = await getPAT(options.namespace, options, false);
36
+ const { pat, save } = await getPAT(options.namespace, options, false);
37
+ options.pat = pat;
37
38
  await doVerifyPat(options);
39
+ await save();
38
40
  }
package/src/verify.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  import * as crypto from 'crypto';
15
15
  import * as fs from 'fs';
16
16
  import { Registry, Extension } from './registry';
17
- import { addEnvOptions, createTempFile } from './util';
17
+ import { addEnvOptions, getTempFilePath } from './util';
18
18
  import { readVSIXPackage, readZip } from './zip';
19
19
  import { VerifyOptions } from './verify-options';
20
20
 
@@ -82,7 +82,7 @@ async function resolveVersion(registry: Registry, extension: Extension, version:
82
82
  }
83
83
 
84
84
  async function downloadSignature(registry: Registry, url: string): Promise<Buffer> {
85
- const sigzipPath = await createTempFile({ postfix: '.sigzip' });
85
+ const sigzipPath = getTempFilePath('.sigzip');
86
86
  try {
87
87
  await registry.download(sigzipPath, new URL(url));
88
88
  const entries = await readZip(sigzipPath, name => name === SIGNATURE_ENTRY);
@@ -97,7 +97,7 @@ async function downloadSignature(registry: Registry, url: string): Promise<Buffe
97
97
  }
98
98
 
99
99
  async function downloadPublicKey(registry: Registry, url: string): Promise<string> {
100
- const publicKeyPath = await createTempFile({ postfix: '.pem' });
100
+ const publicKeyPath = getTempFilePath('.pem');
101
101
  try {
102
102
  await registry.download(publicKeyPath, new URL(url));
103
103
  return await fs.promises.readFile(publicKeyPath, 'utf-8');
package/src/zip.ts CHANGED
@@ -9,19 +9,10 @@
9
9
  ********************************************************************************/
10
10
  import * as crypto from 'crypto';
11
11
  import * as yauzl from 'yauzl-promise';
12
- import { Readable } from 'stream';
12
+ import { buffer } from 'stream/consumers';
13
13
  import { pipeline } from 'stream/promises';
14
14
  import { Manifest } from './util';
15
15
 
16
- async function bufferStream(stream: Readable): Promise<Buffer> {
17
- return await new Promise((resolve, reject) => {
18
- const buffers: Buffer[] = [];
19
- stream.on('data', buffer => buffers.push(buffer));
20
- stream.once('error', reject);
21
- stream.once('end', () => resolve(Buffer.concat(buffers)));
22
- });
23
- }
24
-
25
16
  export async function readZip(packagePath: string, filter: (name: string) => boolean): Promise<Map<string, Buffer>> {
26
17
  const result = new Map<string, Buffer>();
27
18
  const zipfile = await yauzl.open(packagePath);
@@ -30,8 +21,7 @@ export async function readZip(packagePath: string, filter: (name: string) => boo
30
21
  const name = entry.filename.toLowerCase();
31
22
  if (filter(name)) {
32
23
  const stream = await zipfile.openReadStream(entry);
33
- const buffer = await bufferStream(stream);
34
- result.set(name, buffer);
24
+ result.set(name, await buffer(stream));
35
25
  }
36
26
  }
37
27
  } finally {
package/lib/version.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export declare const LIB_VERSION = "1.2.0";
2
- //# sourceMappingURL=version.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,UAAU,CAAC"}
package/lib/version.js DELETED
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LIB_VERSION = void 0;
4
- exports.LIB_VERSION = "1.2.0";
5
- //# sourceMappingURL=version.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":";;;AAAa,QAAA,WAAW,GAAG,OAAO,CAAC"}
package/src/version.ts DELETED
@@ -1 +0,0 @@
1
- export const LIB_VERSION = "1.2.0";