ovsx 1.1.1 → 1.3.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 +73 -1
- package/lib/check-license.d.ts.map +1 -1
- package/lib/check-license.js +3 -2
- package/lib/check-license.js.map +1 -1
- package/lib/get.js +6 -6
- 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 +4 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -1
- package/lib/list-options.d.ts +24 -0
- package/lib/list-options.d.ts.map +1 -0
- package/lib/list-options.js +15 -0
- package/lib/list-options.js.map +1 -0
- package/lib/list.d.ts +18 -0
- package/lib/list.d.ts.map +1 -0
- package/lib/list.js +46 -0
- package/lib/list.js.map +1 -0
- package/lib/main.js +111 -32
- package/lib/main.js.map +1 -1
- package/lib/oidc.d.ts +22 -0
- package/lib/oidc.d.ts.map +1 -0
- package/lib/oidc.js +59 -0
- package/lib/oidc.js.map +1 -0
- package/lib/publish-options.d.ts +8 -1
- package/lib/publish-options.d.ts.map +1 -1
- package/lib/publish.d.ts.map +1 -1
- package/lib/publish.js +150 -13
- package/lib/publish.js.map +1 -1
- package/lib/registry-options.d.ts +8 -0
- package/lib/registry-options.d.ts.map +1 -1
- package/lib/registry.d.ts +161 -5
- package/lib/registry.d.ts.map +1 -1
- package/lib/registry.js +259 -100
- package/lib/registry.js.map +1 -1
- package/lib/search-options.d.ts +48 -0
- package/lib/search-options.d.ts.map +1 -0
- package/lib/search-options.js +15 -0
- package/lib/search-options.js.map +1 -0
- package/lib/search.d.ts +22 -0
- package/lib/search.d.ts.map +1 -0
- package/lib/search.js +86 -0
- package/lib/search.js.map +1 -0
- package/lib/show-options.d.ts +33 -0
- package/lib/show-options.d.ts.map +1 -0
- package/lib/show-options.js +15 -0
- package/lib/show-options.js.map +1 -0
- package/lib/show.d.ts +18 -0
- package/lib/show.d.ts.map +1 -0
- package/lib/show.js +289 -0
- package/lib/show.js.map +1 -0
- package/lib/table.d.ts +27 -0
- package/lib/table.d.ts.map +1 -0
- package/lib/table.js +56 -0
- package/lib/table.js.map +1 -0
- package/lib/trusted-publishing-options.d.ts +30 -0
- package/lib/trusted-publishing-options.d.ts.map +1 -0
- package/lib/trusted-publishing-options.js +15 -0
- package/lib/trusted-publishing-options.js.map +1 -0
- package/lib/trusted-publishing.d.ts +35 -0
- package/lib/trusted-publishing.d.ts.map +1 -0
- package/lib/trusted-publishing.js +112 -0
- package/lib/trusted-publishing.js.map +1 -0
- package/lib/unpublish-options.d.ts +34 -0
- package/lib/unpublish-options.d.ts.map +1 -0
- package/lib/unpublish-options.js +15 -0
- package/lib/unpublish-options.js.map +1 -0
- package/lib/unpublish.d.ts +22 -0
- package/lib/unpublish.d.ts.map +1 -0
- package/lib/unpublish.js +137 -0
- package/lib/unpublish.js.map +1 -0
- package/lib/util.d.ts +51 -9
- package/lib/util.d.ts.map +1 -1
- package/lib/util.js +130 -67
- package/lib/util.js.map +1 -1
- package/lib/verify-options.d.ts +24 -0
- package/lib/verify-options.d.ts.map +1 -0
- package/lib/verify-options.js +15 -0
- package/lib/verify-options.js.map +1 -0
- package/lib/verify-signature-options.d.ts +32 -0
- package/lib/verify-signature-options.d.ts.map +1 -0
- package/lib/verify-signature-options.js +15 -0
- package/lib/verify-signature-options.js.map +1 -0
- package/lib/verify-signature.d.ts +28 -0
- package/lib/verify-signature.d.ts.map +1 -0
- package/lib/verify-signature.js +105 -0
- package/lib/verify-signature.js.map +1 -0
- package/lib/verify.d.ts +21 -0
- package/lib/verify.d.ts.map +1 -0
- package/lib/verify.js +97 -0
- package/lib/verify.js.map +1 -0
- package/lib/zip.d.ts +10 -0
- package/lib/zip.d.ts.map +1 -1
- package/lib/zip.js +33 -10
- package/lib/zip.js.map +1 -1
- package/package.json +21 -14
- package/src/check-license.ts +4 -3
- package/src/get.ts +7 -7
- package/src/http.ts +176 -0
- package/src/index.ts +4 -0
- package/src/list-options.ts +25 -0
- package/src/list.ts +48 -0
- package/src/main.ts +125 -34
- package/src/oidc.ts +65 -0
- package/src/publish-options.ts +8 -1
- package/src/publish.ts +208 -14
- package/src/registry-options.ts +8 -0
- package/src/registry.ts +379 -98
- package/src/search-options.ts +49 -0
- package/src/search.ts +95 -0
- package/src/show-options.ts +34 -0
- package/src/show.ts +328 -0
- package/src/table.ts +53 -0
- package/src/trusted-publishing-options.ts +30 -0
- package/src/trusted-publishing.ts +148 -0
- package/src/unpublish-options.ts +35 -0
- package/src/unpublish.ts +160 -0
- package/src/util.ts +138 -70
- package/src/verify-options.ts +25 -0
- package/src/verify-signature-options.ts +32 -0
- package/src/verify-signature.ts +124 -0
- package/src/verify.ts +107 -0
- package/src/zip.ts +33 -13
- 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/lib/zip.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import { Manifest } from './util';
|
|
2
|
+
export declare function readZip(packagePath: string, filter: (name: string) => boolean): Promise<Map<string, Buffer>>;
|
|
3
|
+
/**
|
|
4
|
+
* Computes the SHA256 digest of every non-directory entry of a zip file, keyed by its exact
|
|
5
|
+
* (case-preserving) name - unlike {@link readZip}, which lowercases names for its
|
|
6
|
+
* case-insensitive-filter callers. Used where entry names and digests are compared against another
|
|
7
|
+
* source of truth, such as a signature manifest that records the original names, without also
|
|
8
|
+
* having to hold every entry's full content in memory at once alongside it - each entry's content
|
|
9
|
+
* is streamed straight into a hash and discarded, one entry at a time.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hashAllZipEntries(packagePath: string): Promise<Map<string, string>>;
|
|
2
12
|
export declare function readVSIXPackage(packagePath: string): Promise<Manifest>;
|
|
3
13
|
//# sourceMappingURL=zip.d.ts.map
|
package/lib/zip.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zip.d.ts","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"zip.d.ts","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,wBAAsB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgBlH;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkBzF;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQ5E"}
|
package/lib/zip.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readZip = readZip;
|
|
4
|
+
exports.hashAllZipEntries = hashAllZipEntries;
|
|
3
5
|
exports.readVSIXPackage = readVSIXPackage;
|
|
4
6
|
/********************************************************************************
|
|
5
7
|
* Copyright (c) 2024 Precies. Software and others
|
|
@@ -10,15 +12,10 @@ exports.readVSIXPackage = readVSIXPackage;
|
|
|
10
12
|
*
|
|
11
13
|
* SPDX-License-Identifier: EPL-2.0
|
|
12
14
|
********************************************************************************/
|
|
15
|
+
const crypto = require("crypto");
|
|
13
16
|
const yauzl = require("yauzl-promise");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const buffers = [];
|
|
17
|
-
stream.on('data', buffer => buffers.push(buffer));
|
|
18
|
-
stream.once('error', reject);
|
|
19
|
-
stream.once('end', () => resolve(Buffer.concat(buffers)));
|
|
20
|
-
});
|
|
21
|
-
}
|
|
17
|
+
const consumers_1 = require("stream/consumers");
|
|
18
|
+
const promises_1 = require("stream/promises");
|
|
22
19
|
async function readZip(packagePath, filter) {
|
|
23
20
|
const result = new Map();
|
|
24
21
|
const zipfile = await yauzl.open(packagePath);
|
|
@@ -27,9 +24,35 @@ async function readZip(packagePath, filter) {
|
|
|
27
24
|
const name = entry.filename.toLowerCase();
|
|
28
25
|
if (filter(name)) {
|
|
29
26
|
const stream = await zipfile.openReadStream(entry);
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
result.set(name, await (0, consumers_1.buffer)(stream));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
await zipfile.close();
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Computes the SHA256 digest of every non-directory entry of a zip file, keyed by its exact
|
|
38
|
+
* (case-preserving) name - unlike {@link readZip}, which lowercases names for its
|
|
39
|
+
* case-insensitive-filter callers. Used where entry names and digests are compared against another
|
|
40
|
+
* source of truth, such as a signature manifest that records the original names, without also
|
|
41
|
+
* having to hold every entry's full content in memory at once alongside it - each entry's content
|
|
42
|
+
* is streamed straight into a hash and discarded, one entry at a time.
|
|
43
|
+
*/
|
|
44
|
+
async function hashAllZipEntries(packagePath) {
|
|
45
|
+
const result = new Map();
|
|
46
|
+
const zipfile = await yauzl.open(packagePath);
|
|
47
|
+
try {
|
|
48
|
+
for await (const entry of zipfile) {
|
|
49
|
+
if (entry.filename.endsWith('/')) {
|
|
50
|
+
continue;
|
|
32
51
|
}
|
|
52
|
+
const stream = await zipfile.openReadStream(entry);
|
|
53
|
+
const hash = crypto.createHash('sha256');
|
|
54
|
+
await (0, promises_1.pipeline)(stream, hash);
|
|
55
|
+
result.set(entry.filename, hash.digest('base64'));
|
|
33
56
|
}
|
|
34
57
|
}
|
|
35
58
|
finally {
|
package/lib/zip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zip.js","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"zip.js","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":";;AAeA,0BAgBC;AAUD,8CAkBC;AAED,0CAQC;AArED;;;;;;;;kFAQkF;AAClF,iCAAiC;AACjC,uCAAuC;AACvC,gDAA0C;AAC1C,8CAA2C;AAGpC,KAAK,UAAU,OAAO,CAAC,WAAmB,EAAE,MAAiC;IACnF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,CAAC;QACJ,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;gBACnD,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,IAAA,kBAAM,EAAC,MAAM,CAAC,CAAC,CAAC;YACxC,CAAC;QACF,CAAC;IACF,CAAC;YAAS,CAAC;QACV,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IAC1D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,CAAC;QACJ,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YACnC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClC,SAAS;YACV,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YACnD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YACzC,MAAM,IAAA,mBAAQ,EAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC7B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnD,CAAC;IACF,CAAC;YAAS,CAAC;QACV,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,WAAmB;IACxD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACzF,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtD,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa,CAAC;AAC7D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ovsx",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Command line interface for Eclipse Open VSX",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
"name": "Miro Spönemann",
|
|
22
22
|
"email": "miro.spoenemann@typefox.io",
|
|
23
23
|
"url": "https://www.typefox.io"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "Thomas Neidhart",
|
|
27
|
+
"email": "thomas.neidhart@eclipse-foundation.org"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "Jordi Gómez Hidalgo",
|
|
31
|
+
"email": "jordi.gomez@eclipse-foundation.org"
|
|
24
32
|
}
|
|
25
33
|
],
|
|
26
34
|
"files": [
|
|
@@ -31,44 +39,43 @@
|
|
|
31
39
|
"types": "lib/index",
|
|
32
40
|
"bin": "bin/ovsx",
|
|
33
41
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
42
|
+
"node": ">=22.0.0"
|
|
35
43
|
},
|
|
36
44
|
"dependencies": {
|
|
37
45
|
"@inquirer/prompts": "^7.10.1",
|
|
38
|
-
"@vscode/vsce": "^
|
|
39
|
-
"commander": "^
|
|
46
|
+
"@vscode/vsce": "^4.0.0",
|
|
47
|
+
"commander": "^14.0.3",
|
|
40
48
|
"cross-keychain": "^1.1.0",
|
|
41
|
-
"follow-redirects": "^1.16.0",
|
|
42
49
|
"is-ci": "^2.0.0",
|
|
43
|
-
"leven": "^3.1.0",
|
|
44
50
|
"semver": "^7.6.0",
|
|
45
|
-
"tmp": "^0.2.3",
|
|
46
51
|
"yauzl-promise": "^4.0.0"
|
|
47
52
|
},
|
|
48
53
|
"devDependencies": {
|
|
49
54
|
"@eslint/eslintrc": "^3.2.0",
|
|
50
55
|
"@eslint/js": "^9.15.0",
|
|
51
56
|
"@stylistic/eslint-plugin": "^2.11.0",
|
|
52
|
-
"@types/follow-redirects": "^1.13.1",
|
|
53
57
|
"@types/is-ci": "^2.0.0",
|
|
54
|
-
"@types/node": "^
|
|
58
|
+
"@types/node": "^22.0.0",
|
|
55
59
|
"@types/semver": "^7.5.8",
|
|
56
|
-
"@types/tmp": "^0.2.2",
|
|
57
60
|
"@types/yauzl-promise": "^4",
|
|
61
|
+
"@types/yazl": "^3.3.1",
|
|
58
62
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
|
59
63
|
"@typescript-eslint/parser": "^8.15.0",
|
|
60
64
|
"eslint": "^9.15.0",
|
|
61
65
|
"limiter": "^2.1.0",
|
|
62
66
|
"rimraf": "^6.0.1",
|
|
63
|
-
"typescript": "^5.6.3"
|
|
67
|
+
"typescript": "^5.6.3",
|
|
68
|
+
"vite": "^8.3.2",
|
|
69
|
+
"vitest": "^5.0.3",
|
|
70
|
+
"yazl": "^3.3.1"
|
|
64
71
|
},
|
|
65
72
|
"scripts": {
|
|
66
73
|
"clean": "rimraf lib",
|
|
67
|
-
"prebuild": "node -p \"'export const LIB_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts",
|
|
68
74
|
"build": "tsc -p ./tsconfig.json && yarn run lint",
|
|
69
75
|
"watch": "tsc -w -p ./tsconfig.json",
|
|
70
|
-
"
|
|
71
|
-
"
|
|
76
|
+
"test": "vitest run",
|
|
77
|
+
"lint": "eslint -c ./configs/eslintrc.mjs src test",
|
|
78
|
+
"prepare": "yarn run clean && yarn run build",
|
|
72
79
|
"publish:next": "yarn npm publish --tag next",
|
|
73
80
|
"publish:latest": "yarn npm publish --tag latest",
|
|
74
81
|
"load-extensions": "node scripts/load-test-extensions.js"
|
package/src/check-license.ts
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
********************************************************************************/
|
|
10
10
|
|
|
11
11
|
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
12
13
|
import * as isCI from 'is-ci';
|
|
13
14
|
import { input, select } from '@inquirer/prompts';
|
|
14
15
|
import {
|
|
15
|
-
readManifest, writeManifest, Manifest,
|
|
16
|
+
readManifest, writeManifest, Manifest, validateManifest
|
|
16
17
|
} from './util';
|
|
17
18
|
|
|
18
19
|
async function addLicense(packagePath: string, manifest: Manifest): Promise<void> {
|
|
@@ -87,14 +88,14 @@ async function useMITLicense(manifest: Manifest, packagePath?: string) {
|
|
|
87
88
|
manifest.license = 'MIT';
|
|
88
89
|
await writeManifest(manifest, packagePath);
|
|
89
90
|
const license = MIT_LICENSE_TEXT.replace('<YEAR> <COPYRIGHT HOLDER>', copyright);
|
|
90
|
-
await writeFile('LICENSE', license,
|
|
91
|
+
await fs.promises.writeFile(path.join(packagePath ?? process.cwd(), 'LICENSE'), license, 'utf-8');
|
|
91
92
|
console.log('LICENSE file has been written. Please commit it to the source repository.');
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
const LICENSE_FILE_NAMES = ['license.md', 'license', 'license.txt', 'licence.md', 'licence', 'licence.txt'];
|
|
95
96
|
|
|
96
97
|
async function hasLicenseFile(packagePath?: string): Promise<boolean> {
|
|
97
|
-
const fileNames = await
|
|
98
|
+
const fileNames = await fs.promises.readdir(packagePath ?? '.');
|
|
98
99
|
for (const fileName of fileNames) {
|
|
99
100
|
for (const licFileName of LICENSE_FILE_NAMES) {
|
|
100
101
|
if (fileName.toLowerCase() === licFileName) {
|
package/src/get.ts
CHANGED
|
@@ -12,7 +12,7 @@ import * as fs from 'fs';
|
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import * as semver from 'semver';
|
|
14
14
|
import { Registry, Extension } from "./registry";
|
|
15
|
-
import {
|
|
15
|
+
import { matchExtensionId, addEnvOptions, rejectError } from './util';
|
|
16
16
|
import { GetOptions } from './get-options';
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -23,7 +23,7 @@ export async function getExtension(options: GetOptions): Promise<void> {
|
|
|
23
23
|
const registry = new Registry(options);
|
|
24
24
|
const match = matchExtensionId(options.extensionId);
|
|
25
25
|
if (!match) {
|
|
26
|
-
throw new Error('The extension identifier must have the form `namespace.extension`.');
|
|
26
|
+
throw new Error('The extension identifier must have the form `namespace.extension` or `namespace/extension`.');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const extension = await registry.getMetadata(match[1], match[2], options.target);
|
|
@@ -70,15 +70,15 @@ async function printMetadata(registry: Registry, extension: Extension, output?:
|
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
let filePath: string | undefined;
|
|
73
|
-
const stats =
|
|
73
|
+
const stats = fs.statSync(output, { throwIfNoEntry: false });
|
|
74
74
|
if (stats?.isDirectory() || !stats && output.endsWith(path.sep)) {
|
|
75
75
|
const fileName = `${extension.namespace}.${extension.name}-${extension.version}.json`;
|
|
76
76
|
filePath = path.resolve(process.cwd(), output, fileName);
|
|
77
77
|
} else {
|
|
78
78
|
filePath = path.resolve(process.cwd(), output);
|
|
79
79
|
}
|
|
80
|
-
await
|
|
81
|
-
await
|
|
80
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
81
|
+
await fs.promises.writeFile(filePath, metadata);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async function download(registry: Registry, extension: Extension, output?: string): Promise<void> {
|
|
@@ -90,7 +90,7 @@ async function download(registry: Registry, extension: Extension, output?: strin
|
|
|
90
90
|
const fileName = decodeURIComponent(downloadUrl.substring(fileNameIndex + 1));
|
|
91
91
|
let filePath: string | undefined;
|
|
92
92
|
if (output) {
|
|
93
|
-
const stats =
|
|
93
|
+
const stats = fs.statSync(output, { throwIfNoEntry: false });
|
|
94
94
|
if (stats?.isDirectory() || !stats && output.endsWith(path.sep)) {
|
|
95
95
|
filePath = path.resolve(process.cwd(), output, fileName);
|
|
96
96
|
} else {
|
|
@@ -99,7 +99,7 @@ async function download(registry: Registry, extension: Extension, output?: strin
|
|
|
99
99
|
} else {
|
|
100
100
|
filePath = path.resolve(process.cwd(), fileName);
|
|
101
101
|
}
|
|
102
|
-
await
|
|
102
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
103
103
|
const target = extension.targetPlatform !== 'universal' ? '@' + extension.targetPlatform : '';
|
|
104
104
|
console.log(`Downloading ${extension.namespace}.${extension.name}-${extension.version}${target} to ${filePath}`);
|
|
105
105
|
await registry.download(filePath, new URL(downloadUrl));
|
package/src/http.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
|
|
3
|
+
*
|
|
4
|
+
* See the NOTICE file(s) distributed with this work for additional
|
|
5
|
+
* information regarding copyright ownership.
|
|
6
|
+
*
|
|
7
|
+
* This program and the accompanying materials are made available under the
|
|
8
|
+
* terms of the Eclipse Public License 2.0 which is available at
|
|
9
|
+
* https://www.eclipse.org/legal/epl-2.0.
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
12
|
+
*****************************************************************************/
|
|
13
|
+
|
|
14
|
+
import * as http from 'http';
|
|
15
|
+
import * as https from 'https';
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import { redactUrl, statusError, withStatus } from './util';
|
|
18
|
+
|
|
19
|
+
// Hop limit the fetch standard uses.
|
|
20
|
+
const MAX_REDIRECTS = 20;
|
|
21
|
+
|
|
22
|
+
// Dropped on a redirect to another origin; X-OpenVSX-Token carries the PAT just like Authorization.
|
|
23
|
+
const CREDENTIAL_HEADERS = /^(?:authorization|cookie|proxy-authorization|x-openvsx-token)$/i;
|
|
24
|
+
|
|
25
|
+
export type RequestBody = () => string | Buffer | Uint8Array | fs.ReadStream;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sends a request and follows redirects to `onResponse`'s final response, reporting any failure
|
|
29
|
+
* to `fail`. A GET follows every redirect; a request with a body follows only 307/308, sending
|
|
30
|
+
* the body again, because 301/302/303 turn it into a GET without one. Credentials are dropped
|
|
31
|
+
* once a redirect leaves the origin. Only the headers in `options` are sent, so a caller that
|
|
32
|
+
* passes none carries no credentials.
|
|
33
|
+
*/
|
|
34
|
+
export function send(
|
|
35
|
+
url: URL,
|
|
36
|
+
options: http.RequestOptions,
|
|
37
|
+
body: RequestBody | undefined,
|
|
38
|
+
onResponse: (response: http.IncomingMessage) => void,
|
|
39
|
+
fail: (err: Error) => void,
|
|
40
|
+
hops = 0
|
|
41
|
+
): void {
|
|
42
|
+
const protocol = url.protocol === 'https:' ? https : http;
|
|
43
|
+
const request = protocol.request(url, options, response => {
|
|
44
|
+
const status = response.statusCode ?? 0;
|
|
45
|
+
const location = response.headers.location;
|
|
46
|
+
if (status < 300 || status > 399 || !location) {
|
|
47
|
+
onResponse(response);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
response.resume();
|
|
51
|
+
|
|
52
|
+
let next: URL;
|
|
53
|
+
try {
|
|
54
|
+
next = new URL(location, url);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
fail(err as Error);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (hops >= MAX_REDIRECTS) {
|
|
60
|
+
fail(new Error(`The request to ${redactUrl(url)} was redirected more than ${MAX_REDIRECTS} times.`));
|
|
61
|
+
} else if (next.protocol !== 'https:' && next.protocol !== 'http:') {
|
|
62
|
+
fail(new Error(`The request to ${redactUrl(url)} was redirected to unsupported URL ${redactUrl(next)}.`));
|
|
63
|
+
} else if (body && status !== 307 && status !== 308) {
|
|
64
|
+
fail(withStatus(new Error(`The ${options.method} request to ${redactUrl(url)} was redirected to ${redactUrl(next)} `
|
|
65
|
+
+ `with status ${status}, which drops the request body. Use the redirect target as the registry URL.`), status));
|
|
66
|
+
} else {
|
|
67
|
+
// The server may answer before reading the body: abandon this hop so its stream
|
|
68
|
+
// releases the file and a late reset on its socket can't fail the next hop.
|
|
69
|
+
request.removeListener('error', fail);
|
|
70
|
+
request.on('error', () => { /* abandoned */ });
|
|
71
|
+
if (content instanceof fs.ReadStream) {
|
|
72
|
+
content.unpipe(request);
|
|
73
|
+
content.destroy();
|
|
74
|
+
}
|
|
75
|
+
request.destroy();
|
|
76
|
+
const headers = next.origin === url.origin ? options.headers : withoutCredentials(options.headers as http.OutgoingHttpHeaders);
|
|
77
|
+
send(next, { ...options, headers }, body, onResponse, fail, hops + 1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
request.on('error', fail);
|
|
81
|
+
failOnTimeout(request, url, options.timeout, fail);
|
|
82
|
+
|
|
83
|
+
const content = body?.();
|
|
84
|
+
if (content instanceof fs.ReadStream) {
|
|
85
|
+
content.on('error', (err: Error) => {
|
|
86
|
+
request.destroy();
|
|
87
|
+
fail(err);
|
|
88
|
+
});
|
|
89
|
+
request.on('error', () => content.close());
|
|
90
|
+
content.on('open', () => content.pipe(request));
|
|
91
|
+
} else {
|
|
92
|
+
request.end(content);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Sends a GET with only the given headers and resolves with the parsed JSON response. */
|
|
97
|
+
export function getJson<T>(url: URL, headers: http.OutgoingHttpHeaders, timeout: number): Promise<T> {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
send(url, { method: 'GET', headers, timeout }, undefined, jsonResponse<T>(resolve, reject), reject);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Reads a response as JSON. A non-2xx status rejects with the `message` or `error` of a JSON
|
|
105
|
+
* error body when there is one, and with the status otherwise. An HTML page rejects with an
|
|
106
|
+
* Error rather than its markup, since it usually means the URL points at a website, not an API.
|
|
107
|
+
*/
|
|
108
|
+
export function jsonResponse<T>(resolve: (value: T) => void, reject: (reason: any) => void): (res: http.IncomingMessage) => void {
|
|
109
|
+
return response => {
|
|
110
|
+
response.setEncoding('utf-8');
|
|
111
|
+
let json = '';
|
|
112
|
+
// A connection lost after the headers have arrived ends the response without 'end' ever
|
|
113
|
+
// firing, so without this the promise is never settled and the command waits on a body
|
|
114
|
+
// that is not coming. The configured timeout would eventually rescue it, but rejecting
|
|
115
|
+
// here reports what actually happened instead of thirty seconds of silence.
|
|
116
|
+
response.on('error', reject);
|
|
117
|
+
response.on('data', chunk => json += chunk);
|
|
118
|
+
response.on('end', () => {
|
|
119
|
+
if (response.statusCode !== undefined && (response.statusCode < 200 || response.statusCode > 299)) {
|
|
120
|
+
if (json.trimStart().startsWith('{')) {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(json) as { message?: string; error?: string };
|
|
123
|
+
const message = parsed.message || parsed.error;
|
|
124
|
+
if (message) {
|
|
125
|
+
// keep the status: the message alone cannot say whether retrying is worth it
|
|
126
|
+
reject(withStatus(new Error(message), response.statusCode));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
// Ignore the error and reject with response status
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
reject(statusError(response));
|
|
134
|
+
} else if (isHtml(response, json)) {
|
|
135
|
+
reject(withStatus(new Error(`The server responded with status ${response.statusCode} and an HTML page instead of JSON.`),
|
|
136
|
+
response.statusCode));
|
|
137
|
+
} else {
|
|
138
|
+
try {
|
|
139
|
+
resolve(JSON.parse(json));
|
|
140
|
+
} catch (err) {
|
|
141
|
+
reject(err);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Node's `timeout` option only raises an event - the request stays open, which is why a server
|
|
150
|
+
* that accepts a connection and then says nothing used to hold a command open indefinitely.
|
|
151
|
+
* Destroying the request with an error routes through the error handling each caller already
|
|
152
|
+
* has, so a stalled request rejects the way any other failure does. With a timeout of zero the
|
|
153
|
+
* event never fires and this does nothing.
|
|
154
|
+
*/
|
|
155
|
+
function failOnTimeout(request: http.ClientRequest, url: URL, timeout: number | undefined, fail: (err: Error) => void): void {
|
|
156
|
+
request.on('timeout', () => {
|
|
157
|
+
// Reported before the request is torn down, rather than by destroying it with the error.
|
|
158
|
+
// Destroying raises errors of its own - ECONNRESET on the response, a premature close
|
|
159
|
+
// from pipeline - and node guarantees no order between those and the request's own. In
|
|
160
|
+
// practice the request's arrives first, so this is belt and braces rather than the thing
|
|
161
|
+
// that stops the wrong error being reported; that is the caller claiming the failure
|
|
162
|
+
// before it does any asynchronous cleanup.
|
|
163
|
+
fail(new Error(`No response from ${redactUrl(url)} for ${timeout} ms.`));
|
|
164
|
+
request.destroy();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The body check covers servers that send HTML without a text/html Content-Type.
|
|
169
|
+
function isHtml(response: http.IncomingMessage, body: string): boolean {
|
|
170
|
+
const contentType = response.headers['content-type']?.split(';')[0].trim().toLowerCase();
|
|
171
|
+
return contentType === 'text/html' || /^\s*<(?:!doctype\s+html|html)[\s>]/i.test(body);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function withoutCredentials(headers: http.OutgoingHttpHeaders = {}): http.OutgoingHttpHeaders {
|
|
175
|
+
return Object.fromEntries(Object.entries(headers).filter(([name]) => !CREDENTIAL_HEADERS.test(name)));
|
|
176
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,10 @@ export * from './publish';
|
|
|
19
19
|
export * from './publish-options';
|
|
20
20
|
export * from './registry';
|
|
21
21
|
export * from './registry-options';
|
|
22
|
+
export * from './trusted-publishing';
|
|
23
|
+
export * from './trusted-publishing-options';
|
|
24
|
+
export * from './unpublish';
|
|
25
|
+
export * from './unpublish-options';
|
|
22
26
|
export * from './verify-pat';
|
|
23
27
|
export * from './verify-pat-options';
|
|
24
28
|
export { isLicenseOk } from './check-license';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
|
|
3
|
+
*
|
|
4
|
+
* See the NOTICE file(s) distributed with this work for additional
|
|
5
|
+
* information regarding copyright ownership.
|
|
6
|
+
*
|
|
7
|
+
* This program and the accompanying materials are made available under the
|
|
8
|
+
* terms of the Eclipse Public License 2.0 which is available at
|
|
9
|
+
* https://www.eclipse.org/legal/epl-2.0.
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
12
|
+
*****************************************************************************/
|
|
13
|
+
|
|
14
|
+
import { RegistryOptions } from './registry-options';
|
|
15
|
+
|
|
16
|
+
export interface ListOptions extends RegistryOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Namespace whose extensions should be listed.
|
|
19
|
+
*/
|
|
20
|
+
namespace: string;
|
|
21
|
+
/**
|
|
22
|
+
* Print the raw namespace metadata as JSON instead of a list.
|
|
23
|
+
*/
|
|
24
|
+
json?: boolean;
|
|
25
|
+
}
|
package/src/list.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
|
|
3
|
+
*
|
|
4
|
+
* See the NOTICE file(s) distributed with this work for additional
|
|
5
|
+
* information regarding copyright ownership.
|
|
6
|
+
*
|
|
7
|
+
* This program and the accompanying materials are made available under the
|
|
8
|
+
* terms of the Eclipse Public License 2.0 which is available at
|
|
9
|
+
* https://www.eclipse.org/legal/epl-2.0.
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
12
|
+
*****************************************************************************/
|
|
13
|
+
|
|
14
|
+
import { ListOptions } from './list-options';
|
|
15
|
+
import { Registry } from './registry';
|
|
16
|
+
import { formatCount } from './table';
|
|
17
|
+
import { addEnvOptions } from './util';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Lists the extensions published in a namespace.
|
|
21
|
+
*/
|
|
22
|
+
export async function list(options: ListOptions): Promise<void> {
|
|
23
|
+
addEnvOptions(options);
|
|
24
|
+
const registry = new Registry(options);
|
|
25
|
+
const namespace = await registry.getNamespace(options.namespace);
|
|
26
|
+
if (namespace.error) {
|
|
27
|
+
throw new Error(namespace.error);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (options.json) {
|
|
31
|
+
console.log(JSON.stringify(namespace, null, 4));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Sorted here rather than relying on the response's key order, so the output is stable and
|
|
36
|
+
// scriptable regardless of how the registry happens to serialise the map.
|
|
37
|
+
const names = Object.keys(namespace.extensions ?? {}).sort((a, b) => a.localeCompare(b));
|
|
38
|
+
const verified = namespace.verified ? ' (verified)' : '';
|
|
39
|
+
console.log(`${namespace.name}${verified} - ${formatCount(names.length, 'extension')}`);
|
|
40
|
+
if (names.length === 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log();
|
|
45
|
+
for (const name of names) {
|
|
46
|
+
console.log(` ${namespace.name}.${name}`);
|
|
47
|
+
}
|
|
48
|
+
}
|