ovsx 0.1.0-next.e000fdb → 0.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/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/lib/check-license.d.ts +13 -0
- package/lib/check-license.d.ts.map +1 -0
- package/lib/check-license.js +129 -0
- package/lib/check-license.js.map +1 -0
- package/lib/create-namespace.d.ts +2 -9
- package/lib/create-namespace.d.ts.map +1 -1
- package/lib/create-namespace.js +4 -8
- package/lib/create-namespace.js.map +1 -1
- package/lib/get.d.ts +2 -5
- package/lib/get.d.ts.map +1 -1
- package/lib/get.js +2 -4
- package/lib/get.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -1
- package/lib/main.js +28 -25
- package/lib/main.js.map +1 -1
- package/lib/ovsx +4 -2
- package/lib/publish.d.ts +10 -14
- package/lib/publish.d.ts.map +1 -1
- package/lib/publish.js +37 -16
- package/lib/publish.js.map +1 -1
- package/lib/registry.d.ts +27 -3
- package/lib/registry.d.ts.map +1 -1
- package/lib/registry.js +30 -8
- package/lib/registry.js.map +1 -1
- package/lib/util.d.ts +16 -1
- package/lib/util.d.ts.map +1 -1
- package/lib/util.js +107 -1
- package/lib/util.js.map +1 -1
- package/package.json +10 -5
- package/src/check-license.ts +119 -0
- package/src/create-namespace.ts +7 -18
- package/src/get.ts +9 -11
- package/src/index.ts +2 -0
- package/src/main.ts +30 -27
- package/src/ovsx +4 -2
- package/src/publish.ts +64 -34
- package/src/registry.ts +57 -11
- package/src/util.ts +110 -1
package/src/main.ts
CHANGED
|
@@ -19,31 +19,27 @@ const pkg = require('../package.json');
|
|
|
19
19
|
|
|
20
20
|
module.exports = function (argv: string[]): void {
|
|
21
21
|
const program = new commander.Command();
|
|
22
|
-
program.usage('<command> [options]')
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.action(() => console.log(`Eclipse Open VSX CLI version ${pkg.version}`));
|
|
22
|
+
program.usage('<command> [options]')
|
|
23
|
+
.option('-r, --registryUrl <url>', 'Use the registry API at this base URL.')
|
|
24
|
+
.option('-p, --pat <token>', 'Personal access token.')
|
|
25
|
+
.option('--debug', 'Include debug information on error')
|
|
26
|
+
.version(pkg.version, '-V, --version', 'Print the Eclipse Open VSX CLI version');
|
|
28
27
|
|
|
29
28
|
const createNamespaceCmd = program.command('create-namespace <name>');
|
|
30
29
|
createNamespaceCmd.description('Create a new namespace')
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
.action((name: string, { registryUrl, pat }) => {
|
|
30
|
+
.action((name: string) => {
|
|
31
|
+
const { registryUrl, pat } = program.opts();
|
|
34
32
|
createNamespace({ name, registryUrl, pat })
|
|
35
33
|
.catch(handleError(program.debug));
|
|
36
34
|
});
|
|
37
35
|
|
|
38
36
|
const publishCmd = program.command('publish [extension.vsix]');
|
|
39
37
|
publishCmd.description('Publish an extension, packaging it first if necessary.')
|
|
40
|
-
.option('-
|
|
41
|
-
.option('-p, --pat <token>', 'Personal access token (required).')
|
|
42
|
-
.option('--packagePath <path>', 'Package and publish the extension at the specified path.')
|
|
38
|
+
.option('-i, --packagePath <paths...>', 'Publish the provided VSIX packages.')
|
|
43
39
|
.option('--baseContentUrl <url>', 'Prepend all relative links in README.md with this URL.')
|
|
44
40
|
.option('--baseImagesUrl <url>', 'Prepend all relative image links in README.md with this URL.')
|
|
45
41
|
.option('--yarn', 'Use yarn instead of npm while packing extension files.')
|
|
46
|
-
.action((extensionFile: string, {
|
|
42
|
+
.action((extensionFile: string, { packagePath, baseContentUrl, baseImagesUrl, yarn }) => {
|
|
47
43
|
if (extensionFile !== undefined && packagePath !== undefined) {
|
|
48
44
|
console.error('\u274c Please specify either a package file or a package path, but not both.\n');
|
|
49
45
|
publishCmd.help();
|
|
@@ -54,32 +50,39 @@ module.exports = function (argv: string[]): void {
|
|
|
54
50
|
console.warn("Ignoring option '--baseImagesUrl' for prepackaged extension.");
|
|
55
51
|
if (extensionFile !== undefined && yarn !== undefined)
|
|
56
52
|
console.warn("Ignoring option '--yarn' for prepackaged extension.");
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
const { registryUrl, pat } = program.opts();
|
|
54
|
+
publish({ extensionFile, registryUrl, pat, packagePath: typeof packagePath === 'string' ? [packagePath] : packagePath, baseContentUrl, baseImagesUrl, yarn })
|
|
55
|
+
.catch(handleError(program.debug,
|
|
56
|
+
'See the documentation for more information:\n'
|
|
57
|
+
+ 'https://github.com/eclipse/openvsx/wiki/Publishing-Extensions'
|
|
58
|
+
));
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const getCmd = program.command('get <namespace.extension>');
|
|
62
62
|
getCmd.description('Download an extension or its metadata.')
|
|
63
|
-
.option('-v, --
|
|
64
|
-
.option('-r, --registryUrl <url>', 'Use the registry API at this base URL.')
|
|
63
|
+
.option('-v, --versionRange <version>', 'Specify an exact version or a version range.')
|
|
65
64
|
.option('-o, --output <path>', 'Save the output in the specified file or directory.')
|
|
66
65
|
.option('--metadata', 'Print the extension\'s metadata instead of downloading it.')
|
|
67
|
-
.action((extensionId: string, {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
getExtension({ extensionId, version, registryUrl, output, metadata })
|
|
66
|
+
.action((extensionId: string, { versionRange, output, metadata }) => {
|
|
67
|
+
const { registryUrl } = program.opts();
|
|
68
|
+
getExtension({ extensionId, version: versionRange, registryUrl, output, metadata })
|
|
71
69
|
.catch(handleError(program.debug));
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
program
|
|
75
73
|
.command('*', '', { noHelp: true })
|
|
76
|
-
.action((cmd:
|
|
74
|
+
.action((cmd: commander.Command) => {
|
|
77
75
|
const availableCommands = program.commands.map((c: any) => c._name) as string[];
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
const actualCommand = cmd.args[0];
|
|
77
|
+
if (actualCommand) {
|
|
78
|
+
const suggestion = availableCommands.find(c => leven(c, actualCommand) < c.length * 0.4);
|
|
79
|
+
if (suggestion)
|
|
80
|
+
console.error(`Unknown command '${actualCommand}', did you mean '${suggestion}'?\n`);
|
|
81
|
+
else
|
|
82
|
+
console.error(`Unknown command '${actualCommand}'.\n`);
|
|
83
|
+
} else {
|
|
84
|
+
console.error('Unknown command.');
|
|
85
|
+
}
|
|
83
86
|
program.help();
|
|
84
87
|
});
|
|
85
88
|
|
package/src/ovsx
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const semver = require('semver');
|
|
4
|
+
|
|
5
|
+
if (semver.lt(process.versions.node, '14.0.0')) {
|
|
6
|
+
console.error('ovsx requires at least NodeJS version 14. Check your installed version with `node --version`.');
|
|
5
7
|
process.exit(1);
|
|
6
8
|
}
|
|
7
9
|
|
package/src/publish.ts
CHANGED
|
@@ -8,60 +8,53 @@
|
|
|
8
8
|
* SPDX-License-Identifier: EPL-2.0
|
|
9
9
|
********************************************************************************/
|
|
10
10
|
|
|
11
|
-
import { createVSIX } from 'vsce';
|
|
12
|
-
import { createTempFile } from './util';
|
|
13
|
-
import { Registry } from './registry';
|
|
11
|
+
import { createVSIX, ICreateVSIXOptions } from 'vsce';
|
|
12
|
+
import { createTempFile, addEnvOptions } from './util';
|
|
13
|
+
import { Registry, RegistryOptions } from './registry';
|
|
14
|
+
import { checkLicense } from './check-license';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Publishes an extension.
|
|
17
18
|
*/
|
|
18
19
|
export async function publish(options: PublishOptions = {}): Promise<void> {
|
|
19
|
-
|
|
20
|
-
options.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
throw new Error("A personal access token must be given with the option '--pat'.");
|
|
20
|
+
addEnvOptions(options);
|
|
21
|
+
if (options.packagePath) {
|
|
22
|
+
// call the publish command for every package path
|
|
23
|
+
await Promise.all(options.packagePath.map(path => doPublish({ ...options, packagePath: path })));
|
|
24
|
+
} else {
|
|
25
|
+
return doPublish({ ... options, packagePath: undefined });
|
|
26
26
|
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function doPublish(options: InternalPublishOptions = {}): Promise<void> {
|
|
30
|
+
if (!options.pat) {
|
|
31
|
+
throw new Error("A personal access token must be given with the option '--pat'.");
|
|
27
32
|
}
|
|
33
|
+
|
|
34
|
+
// if the packagePath is a link to a vsix, don't need to package it
|
|
35
|
+
if (options.packagePath && options.packagePath.endsWith('.vsix')) {
|
|
36
|
+
options.extensionFile = options.packagePath;
|
|
37
|
+
delete options.packagePath;
|
|
38
|
+
}
|
|
39
|
+
const registry = new Registry(options);
|
|
28
40
|
if (!options.extensionFile) {
|
|
29
|
-
|
|
30
|
-
await createVSIX({
|
|
31
|
-
cwd: options.packagePath,
|
|
32
|
-
packagePath: options.extensionFile,
|
|
33
|
-
baseContentUrl: options.baseContentUrl,
|
|
34
|
-
baseImagesUrl: options.baseImagesUrl,
|
|
35
|
-
useYarn: options.yarn
|
|
36
|
-
});
|
|
41
|
+
await packageExtension(options, registry);
|
|
37
42
|
console.log(); // new line
|
|
38
43
|
}
|
|
39
|
-
|
|
40
|
-
const extension = await registry.publish(options.extensionFile
|
|
44
|
+
|
|
45
|
+
const extension = await registry.publish(options.extensionFile!, options.pat);
|
|
41
46
|
if (extension.error) {
|
|
42
47
|
throw new Error(extension.error);
|
|
43
48
|
}
|
|
44
49
|
console.log(`\ud83d\ude80 Published ${extension.namespace}.${extension.name} v${extension.version}`);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* The base URL of the registry API.
|
|
50
|
-
*/
|
|
51
|
-
registryUrl?: string;
|
|
52
|
-
/**
|
|
53
|
-
* Personal access token.
|
|
54
|
-
*/
|
|
55
|
-
pat?: string;
|
|
52
|
+
interface PublishCommonOptions extends RegistryOptions {
|
|
56
53
|
/**
|
|
57
54
|
* Path to the vsix file to be published. Cannot be used together with `packagePath`.
|
|
58
55
|
*/
|
|
59
56
|
extensionFile?: string;
|
|
60
|
-
|
|
61
|
-
* Path to the extension to be packaged and published. Cannot be used together
|
|
62
|
-
* with `extensionFile`.
|
|
63
|
-
*/
|
|
64
|
-
packagePath?: string;
|
|
57
|
+
|
|
65
58
|
/**
|
|
66
59
|
* The base URL for links detected in Markdown files. Only valid with `packagePath`.
|
|
67
60
|
*/
|
|
@@ -75,3 +68,40 @@ export interface PublishOptions {
|
|
|
75
68
|
*/
|
|
76
69
|
yarn?: boolean;
|
|
77
70
|
}
|
|
71
|
+
|
|
72
|
+
// Interface used by top level CLI
|
|
73
|
+
export interface PublishOptions extends PublishCommonOptions {
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Paths to the extension to be packaged and published. Cannot be used together
|
|
77
|
+
* with `extensionFile`.
|
|
78
|
+
*/
|
|
79
|
+
packagePath?: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Interface used internally by the doPublish method
|
|
83
|
+
interface InternalPublishOptions extends PublishCommonOptions {
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Only one path for our internal command.
|
|
87
|
+
* Path to the extension to be packaged and published. Cannot be used together
|
|
88
|
+
* with `extensionFile`.
|
|
89
|
+
*/
|
|
90
|
+
packagePath?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function packageExtension(options: InternalPublishOptions, registry: Registry): Promise<void> {
|
|
94
|
+
if (registry.requiresLicense) {
|
|
95
|
+
await checkLicense(options.packagePath!);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
options.extensionFile = await createTempFile({ postfix: '.vsix' });
|
|
99
|
+
const createVSIXOptions: ICreateVSIXOptions = {
|
|
100
|
+
cwd: options.packagePath,
|
|
101
|
+
packagePath: options.extensionFile,
|
|
102
|
+
baseContentUrl: options.baseContentUrl,
|
|
103
|
+
baseImagesUrl: options.baseImagesUrl,
|
|
104
|
+
useYarn: options.yarn
|
|
105
|
+
};
|
|
106
|
+
await createVSIX(createVSIXOptions);
|
|
107
|
+
}
|
package/src/registry.ts
CHANGED
|
@@ -24,16 +24,25 @@ export class Registry {
|
|
|
24
24
|
readonly url: string;
|
|
25
25
|
readonly maxNamespaceSize: number;
|
|
26
26
|
readonly maxPublishSize: number;
|
|
27
|
+
readonly username?: string;
|
|
28
|
+
readonly password?: string;
|
|
27
29
|
|
|
28
30
|
constructor(options: RegistryOptions = {}) {
|
|
29
|
-
if (options.
|
|
30
|
-
this.url = options.
|
|
31
|
-
else if (options.
|
|
32
|
-
this.url = options.
|
|
31
|
+
if (options.registryUrl && options.registryUrl.endsWith('/'))
|
|
32
|
+
this.url = options.registryUrl.substring(0, options.registryUrl.length - 1);
|
|
33
|
+
else if (options.registryUrl)
|
|
34
|
+
this.url = options.registryUrl;
|
|
33
35
|
else
|
|
34
36
|
this.url = DEFAULT_URL;
|
|
35
37
|
this.maxNamespaceSize = options.maxNamespaceSize || DEFAULT_NAMESPACE_SIZE;
|
|
36
38
|
this.maxPublishSize = options.maxPublishSize || DEFAULT_PUBLISH_SIZE;
|
|
39
|
+
this.username = options.username;
|
|
40
|
+
this.password = options.password;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get requiresLicense(): boolean {
|
|
44
|
+
const url = new URL(this.url);
|
|
45
|
+
return url.hostname === 'open-vsx.org' || url.hostname.endsWith('.open-vsx.org');
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
createNamespace(name: string, pat: string): Promise<Response> {
|
|
@@ -73,8 +82,9 @@ export class Registry {
|
|
|
73
82
|
download(file: string, url: URL): Promise<void> {
|
|
74
83
|
return new Promise((resolve, reject) => {
|
|
75
84
|
const stream = fs.createWriteStream(file);
|
|
85
|
+
const requestOptions = this.getRequestOptions();
|
|
76
86
|
const request = this.getProtocol(url)
|
|
77
|
-
.request(url, response => {
|
|
87
|
+
.request(url, requestOptions, response => {
|
|
78
88
|
response.on('end', () => {
|
|
79
89
|
if (response.statusCode !== undefined && (response.statusCode < 200 || response.statusCode > 299)) {
|
|
80
90
|
reject(statusError(response));
|
|
@@ -98,8 +108,9 @@ export class Registry {
|
|
|
98
108
|
|
|
99
109
|
getJson<T extends Response>(url: URL): Promise<T> {
|
|
100
110
|
return new Promise((resolve, reject) => {
|
|
111
|
+
const requestOptions = this.getRequestOptions();
|
|
101
112
|
const request = this.getProtocol(url)
|
|
102
|
-
.request(url, this.getJsonResponse<T>(resolve, reject));
|
|
113
|
+
.request(url, requestOptions, this.getJsonResponse<T>(resolve, reject));
|
|
103
114
|
request.on('error', reject);
|
|
104
115
|
request.end();
|
|
105
116
|
});
|
|
@@ -107,7 +118,7 @@ export class Registry {
|
|
|
107
118
|
|
|
108
119
|
post<T extends Response>(content: string | Buffer | Uint8Array, url: URL, headers?: http.OutgoingHttpHeaders, maxBodyLength?: number): Promise<T> {
|
|
109
120
|
return new Promise((resolve, reject) => {
|
|
110
|
-
const requestOptions =
|
|
121
|
+
const requestOptions = this.getRequestOptions('POST', headers, maxBodyLength);
|
|
111
122
|
const request = this.getProtocol(url)
|
|
112
123
|
.request(url, requestOptions, this.getJsonResponse<T>(resolve, reject));
|
|
113
124
|
request.on('error', reject);
|
|
@@ -119,7 +130,7 @@ export class Registry {
|
|
|
119
130
|
postFile<T extends Response>(file: string, url: URL, headers?: http.OutgoingHttpHeaders, maxBodyLength?: number): Promise<T> {
|
|
120
131
|
return new Promise((resolve, reject) => {
|
|
121
132
|
const stream = fs.createReadStream(file);
|
|
122
|
-
const requestOptions =
|
|
133
|
+
const requestOptions = this.getRequestOptions('POST', headers, maxBodyLength);
|
|
123
134
|
const request = this.getProtocol(url)
|
|
124
135
|
.request(url, requestOptions, this.getJsonResponse<T>(resolve, reject));
|
|
125
136
|
stream.on('error', err => {
|
|
@@ -150,6 +161,21 @@ export class Registry {
|
|
|
150
161
|
return followRedirects.http as typeof http;
|
|
151
162
|
}
|
|
152
163
|
|
|
164
|
+
private getRequestOptions(method?: string, headers?: http.OutgoingHttpHeaders, maxBodyLength?: number): http.RequestOptions {
|
|
165
|
+
if (this.username && this.password) {
|
|
166
|
+
if (!headers) {
|
|
167
|
+
headers = {};
|
|
168
|
+
}
|
|
169
|
+
const credentials = Buffer.from(this.username + ':' + this.password).toString('base64');
|
|
170
|
+
headers['Authorization'] = 'Basic ' + credentials;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
method,
|
|
174
|
+
headers,
|
|
175
|
+
maxBodyLength
|
|
176
|
+
} as http.RequestOptions;
|
|
177
|
+
}
|
|
178
|
+
|
|
153
179
|
private getJsonResponse<T extends Response>(resolve: (value: T) => void, reject: (reason: any) => void): (res: http.IncomingMessage) => void {
|
|
154
180
|
return response => {
|
|
155
181
|
response.setEncoding('UTF-8');
|
|
@@ -186,8 +212,29 @@ export class Registry {
|
|
|
186
212
|
}
|
|
187
213
|
|
|
188
214
|
export interface RegistryOptions {
|
|
189
|
-
|
|
215
|
+
/**
|
|
216
|
+
* The base URL of the registry API.
|
|
217
|
+
*/
|
|
218
|
+
registryUrl?: string;
|
|
219
|
+
/**
|
|
220
|
+
* Personal access token.
|
|
221
|
+
*/
|
|
222
|
+
pat?: string;
|
|
223
|
+
/**
|
|
224
|
+
* User name for basic authentication.
|
|
225
|
+
*/
|
|
226
|
+
username?: string;
|
|
227
|
+
/**
|
|
228
|
+
* Password for basic authentication.
|
|
229
|
+
*/
|
|
230
|
+
password?: string;
|
|
231
|
+
/**
|
|
232
|
+
* Maximal request body size for creating namespaces.
|
|
233
|
+
*/
|
|
190
234
|
maxNamespaceSize?: number;
|
|
235
|
+
/**
|
|
236
|
+
* Maximal request body size for publishing.
|
|
237
|
+
*/
|
|
191
238
|
maxPublishSize?: number;
|
|
192
239
|
}
|
|
193
240
|
|
|
@@ -206,8 +253,7 @@ export interface Extension extends Response {
|
|
|
206
253
|
namespace: string;
|
|
207
254
|
version: string;
|
|
208
255
|
publishedBy: UserData;
|
|
209
|
-
|
|
210
|
-
namespaceAccess: 'public' | 'restricted';
|
|
256
|
+
verified: boolean;
|
|
211
257
|
// key: version, value: url
|
|
212
258
|
allVersions: { [version: string]: string };
|
|
213
259
|
|
package/src/util.ts
CHANGED
|
@@ -9,11 +9,29 @@
|
|
|
9
9
|
********************************************************************************/
|
|
10
10
|
|
|
11
11
|
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
12
13
|
import * as tmp from 'tmp';
|
|
13
14
|
import * as http from 'http';
|
|
15
|
+
import * as readline from 'readline';
|
|
16
|
+
import { RegistryOptions } from './registry';
|
|
14
17
|
|
|
15
18
|
export { promisify } from 'util';
|
|
16
19
|
|
|
20
|
+
export function addEnvOptions(options: RegistryOptions): void {
|
|
21
|
+
if (!options.registryUrl) {
|
|
22
|
+
options.registryUrl = process.env.OVSX_REGISTRY_URL;
|
|
23
|
+
}
|
|
24
|
+
if (!options.pat) {
|
|
25
|
+
options.pat = process.env.OVSX_PAT;
|
|
26
|
+
}
|
|
27
|
+
if (!options.username) {
|
|
28
|
+
options.username = process.env.OVSX_USERNAME;
|
|
29
|
+
}
|
|
30
|
+
if (!options.password) {
|
|
31
|
+
options.password = process.env.OVSX_PASSWORD;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
17
35
|
export function matchExtensionId(id: string): RegExpExecArray | null {
|
|
18
36
|
return /^([\w-]+)(?:\.|\/)([\w-]+)$/.exec(id);
|
|
19
37
|
}
|
|
@@ -50,10 +68,13 @@ export function createTempFile(options: tmp.TmpNameOptions): Promise<string> {
|
|
|
50
68
|
});
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
export function handleError(debug?: boolean): (reason: any) => void {
|
|
71
|
+
export function handleError(debug?: boolean, additionalMessage?: string): (reason: any) => void {
|
|
54
72
|
return reason => {
|
|
55
73
|
if (reason instanceof Error && !debug) {
|
|
56
74
|
console.error(`\u274c ${reason.message}`);
|
|
75
|
+
if (additionalMessage) {
|
|
76
|
+
console.error(additionalMessage);
|
|
77
|
+
}
|
|
57
78
|
} else if (typeof reason === 'string') {
|
|
58
79
|
console.error(`\u274c ${reason}`);
|
|
59
80
|
} else if (reason !== undefined) {
|
|
@@ -71,3 +92,91 @@ export function statusError(response: http.IncomingMessage): Error {
|
|
|
71
92
|
else
|
|
72
93
|
return new Error(`The server responded with status ${response.statusCode}.`);
|
|
73
94
|
}
|
|
95
|
+
|
|
96
|
+
export function readFile(name: string, packagePath?: string, encoding = 'utf-8'): Promise<string> {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
fs.readFile(
|
|
99
|
+
path.join(packagePath || process.cwd(), name),
|
|
100
|
+
{ encoding },
|
|
101
|
+
(err, content) => {
|
|
102
|
+
if (err) {
|
|
103
|
+
reject(err);
|
|
104
|
+
} else {
|
|
105
|
+
resolve(content);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function readManifest(packagePath?: string): Promise<Manifest> {
|
|
113
|
+
const content = await readFile('package.json', packagePath);
|
|
114
|
+
return JSON.parse(content);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function validateManifest(manifest: Manifest): void {
|
|
118
|
+
if (!manifest.publisher) {
|
|
119
|
+
throw new Error("Missing required field 'publisher'.");
|
|
120
|
+
}
|
|
121
|
+
if (!manifest.name) {
|
|
122
|
+
throw new Error("Missing required field 'name'.");
|
|
123
|
+
}
|
|
124
|
+
if (!manifest.version) {
|
|
125
|
+
throw new Error("Missing required field 'version'.");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function writeFile(name: string, content: string, packagePath?: string, encoding = 'utf-8'): Promise<void> {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
fs.writeFile(
|
|
132
|
+
path.join(packagePath || process.cwd(), name),
|
|
133
|
+
content,
|
|
134
|
+
{ encoding },
|
|
135
|
+
err => {
|
|
136
|
+
if (err) {
|
|
137
|
+
reject(err);
|
|
138
|
+
} else {
|
|
139
|
+
resolve();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function writeManifest(manifest: Manifest, packagePath?: string): Promise<void> {
|
|
147
|
+
const content = JSON.stringify(manifest, null, 4);
|
|
148
|
+
return writeFile('package.json', content, packagePath);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface Manifest {
|
|
152
|
+
publisher: string;
|
|
153
|
+
name: string;
|
|
154
|
+
version: string;
|
|
155
|
+
license?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getUserInput(text: string): Promise<string> {
|
|
159
|
+
return new Promise(resolve => {
|
|
160
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
161
|
+
rl.question(text, answer => {
|
|
162
|
+
resolve(answer);
|
|
163
|
+
rl.close();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function getUserChoice<R extends string>(text: string, values: R[],
|
|
169
|
+
defaultValue: R, lowerCase = true): Promise<R> {
|
|
170
|
+
const prompt = text + '\n' + values.map(v => v === defaultValue ? `[${v}]` : v).join('/') + ': ';
|
|
171
|
+
const answer = await getUserInput(prompt);
|
|
172
|
+
if (!answer) {
|
|
173
|
+
return defaultValue;
|
|
174
|
+
}
|
|
175
|
+
const lcAnswer = lowerCase ? answer.toLowerCase() : answer;
|
|
176
|
+
for (const value of values) {
|
|
177
|
+
if (value.startsWith(lcAnswer)) {
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return defaultValue;
|
|
182
|
+
}
|