ovsx 0.9.4 → 0.10.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/lib/verify-pat.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * SPDX-License-Identifier: EPL-2.0
10
10
  ********************************************************************************/
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.verifyPat = void 0;
12
+ exports.doVerifyPat = exports.verifyPat = void 0;
13
13
  const registry_1 = require("./registry");
14
14
  const util_1 = require("./util");
15
15
  /**
@@ -17,9 +17,6 @@ const util_1 = require("./util");
17
17
  */
18
18
  async function verifyPat(options) {
19
19
  (0, util_1.addEnvOptions)(options);
20
- if (!options.pat) {
21
- throw new Error("A personal access token must be given with the option '--pat'.");
22
- }
23
20
  if (!options.namespace) {
24
21
  let error;
25
22
  try {
@@ -33,12 +30,19 @@ async function verifyPat(options) {
33
30
  (error ? `\n\n${error}` : ''));
34
31
  }
35
32
  }
33
+ options.pat = await (0, util_1.getPAT)(options.namespace, options, false);
34
+ await doVerifyPat(options);
35
+ }
36
+ exports.verifyPat = verifyPat;
37
+ async function doVerifyPat(options) {
36
38
  const registry = new registry_1.Registry(options);
37
- const result = await registry.verifyPat(options.namespace, options.pat);
39
+ const namespace = options.namespace;
40
+ const pat = options.pat;
41
+ const result = await registry.verifyPat(namespace, pat);
38
42
  if (result.error) {
39
43
  throw new Error(result.error);
40
44
  }
41
- console.log(`\ud83d\ude80 PAT valid to publish at ${options.namespace}`);
45
+ console.log(`\ud83d\ude80 PAT valid to publish at ${namespace}`);
42
46
  }
43
- exports.verifyPat = verifyPat;
47
+ exports.doVerifyPat = doVerifyPat;
44
48
  //# sourceMappingURL=verify-pat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"verify-pat.js","sourceRoot":"","sources":["../src/verify-pat.ts"],"names":[],"mappings":";AAAA;;;;;;;;kFAQkF;;;AAElF,yCAAuD;AACvD,iCAAqD;AAErD;;GAEG;AACI,KAAK,UAAU,SAAS,CAAC,OAAyB;IACrD,IAAA,oBAAa,EAAC,OAAO,CAAC,CAAC;IACvB,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;QACd,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;KACrF;IAED,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE;QACtB,IAAI,KAAK,CAAC;QACV,IAAI;YACF,OAAO,CAAC,SAAS,GAAG,CAAC,MAAM,IAAA,mBAAY,GAAE,CAAC,CAAC,SAAS,CAAC;SACtD;QAAC,OAAO,CAAC,EAAE;YACV,KAAK,GAAG,CAAC,CAAC;SACX;QAED,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE;YACtB,MAAM,IAAI,KAAK,CACb,6GAA6G;gBAC7G,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC9B,CAAC;SACH;KACF;IAED,MAAM,QAAQ,GAAG,IAAI,mBAAQ,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACxE,IAAI,MAAM,CAAC,KAAK,EAAE;QACd,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;KACjC;IACD,OAAO,CAAC,GAAG,CAAC,yCAAyC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;AAC9E,CAAC;AA5BD,8BA4BC"}
1
+ {"version":3,"file":"verify-pat.js","sourceRoot":"","sources":["../src/verify-pat.ts"],"names":[],"mappings":";AAAA;;;;;;;;kFAQkF;;;AAElF,yCAAuD;AACvD,iCAA6D;AAE7D;;GAEG;AACI,KAAK,UAAU,SAAS,CAAC,OAAyB;IACrD,IAAA,oBAAa,EAAC,OAAO,CAAC,CAAC;IACvB,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE;QACpB,IAAI,KAAK,CAAC;QACV,IAAI;YACA,OAAO,CAAC,SAAS,GAAG,CAAC,MAAM,IAAA,mBAAY,GAAE,CAAC,CAAC,SAAS,CAAC;SACxD;QAAC,OAAO,CAAC,EAAE;YACR,KAAK,GAAG,CAAC,CAAC;SACb;QAED,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE;YACpB,MAAM,IAAI,KAAK,CACX,6GAA6G;gBAC7G,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAChC,CAAC;SACL;KACJ;IAED,OAAO,CAAC,GAAG,GAAG,MAAM,IAAA,aAAM,EAAC,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAC9D,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC;AApBD,8BAoBC;AAEM,KAAK,UAAU,WAAW,CAAC,OAAyB;IACvD,MAAM,QAAQ,GAAG,IAAI,mBAAQ,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAmB,CAAC;IAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAa,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,KAAK,EAAE;QACd,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;KACjC;IACD,OAAO,CAAC,GAAG,CAAC,yCAAyC,SAAS,EAAE,CAAC,CAAC;AACtE,CAAC;AATD,kCASC"}
package/lib/zip.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /// <reference types="node" />
2
+ import { Manifest } from './util';
3
+ export declare function readZip(packagePath: string, filter: (name: string) => boolean): Promise<Map<string, Buffer>>;
4
+ export declare function readVSIXPackage(packagePath: string): Promise<Manifest>;
5
+ //# sourceMappingURL=zip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip.d.ts","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAWlC,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,CA+BlH;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQ5E"}
package/lib/zip.js ADDED
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readVSIXPackage = exports.readZip = void 0;
4
+ const yauzl_1 = require("yauzl");
5
+ async function bufferStream(stream) {
6
+ return await new Promise((c, e) => {
7
+ const buffers = [];
8
+ stream.on('data', buffer => buffers.push(buffer));
9
+ stream.once('error', e);
10
+ stream.once('end', () => c(Buffer.concat(buffers)));
11
+ });
12
+ }
13
+ async function readZip(packagePath, filter) {
14
+ const zipfile = await new Promise((c, e) => (0, yauzl_1.open)(packagePath, { lazyEntries: true }, (err, zipfile) => (err ? e(err) : c(zipfile))));
15
+ return await new Promise((c, e) => {
16
+ const result = new Map();
17
+ zipfile.once('close', () => c(result));
18
+ zipfile.readEntry();
19
+ zipfile.on('entry', (entry) => {
20
+ const name = entry.fileName.toLowerCase();
21
+ if (filter(name)) {
22
+ zipfile.openReadStream(entry, (err, stream) => {
23
+ if (err) {
24
+ zipfile.close();
25
+ return e(err);
26
+ }
27
+ bufferStream(stream).then(buffer => {
28
+ result.set(name, buffer);
29
+ zipfile.readEntry();
30
+ });
31
+ });
32
+ }
33
+ else {
34
+ zipfile.readEntry();
35
+ }
36
+ });
37
+ });
38
+ }
39
+ exports.readZip = readZip;
40
+ async function readVSIXPackage(packagePath) {
41
+ const map = await readZip(packagePath, name => /^extension\/package\.json$/i.test(name));
42
+ const rawManifest = map.get('extension/package.json');
43
+ if (!rawManifest) {
44
+ throw new Error('Manifest not found.');
45
+ }
46
+ return JSON.parse(rawManifest.toString('utf8'));
47
+ }
48
+ exports.readVSIXPackage = readVSIXPackage;
49
+ //# sourceMappingURL=zip.js.map
package/lib/zip.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip.js","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":";;;AAAA,iCAA6C;AAI7C,KAAK,UAAU,YAAY,CAAC,MAAgB;IAC3C,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,OAAO,CAAC,WAAmB,EAAE,MAAiC;IACnF,MAAM,OAAO,GAAG,MAAM,IAAI,OAAO,CAAU,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACnD,IAAA,YAAI,EAAC,WAAW,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAQ,CAAC,CAAC,CAAC,CACxF,CAAC;IAEF,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QAEzC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAEvC,OAAO,CAAC,SAAS,EAAE,CAAC;QACpB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;YACpC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAE1C,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE;gBACjB,OAAO,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;oBAC7C,IAAI,GAAG,EAAE;wBACR,OAAO,CAAC,KAAK,EAAE,CAAC;wBAChB,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;qBACd;oBAED,YAAY,CAAC,MAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;wBACnC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;wBACzB,OAAO,CAAC,SAAS,EAAE,CAAC;oBACrB,CAAC,CAAC,CAAC;gBACJ,CAAC,CAAC,CAAC;aACH;iBAAM;gBACN,OAAO,CAAC,SAAS,EAAE,CAAC;aACpB;QACF,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC;AA/BD,0BA+BC;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;QACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;KACvC;IAEE,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa,CAAC;AAChE,CAAC;AARD,0CAQC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ovsx",
3
- "version": "0.9.4",
3
+ "version": "0.10.0",
4
4
  "description": "Command line interface for Eclipse Open VSX",
5
5
  "keywords": [
6
6
  "cli",
@@ -31,23 +31,25 @@
31
31
  "types": "lib/index",
32
32
  "bin": "lib/ovsx",
33
33
  "engines": {
34
- "node": ">= 16"
34
+ "node": ">= 20"
35
35
  },
36
36
  "dependencies": {
37
- "@vscode/vsce": "^2.25.0",
38
- "commander": "^6.1.0",
37
+ "@vscode/vsce": "^3.1.0",
38
+ "commander": "^6.2.1",
39
39
  "follow-redirects": "^1.14.6",
40
40
  "is-ci": "^2.0.0",
41
41
  "leven": "^3.1.0",
42
42
  "semver": "^7.6.0",
43
- "tmp": "^0.2.1"
43
+ "tmp": "^0.2.3",
44
+ "yauzl": "^3.1.3"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@types/follow-redirects": "^1.13.1",
47
48
  "@types/is-ci": "^2.0.0",
48
- "@types/node": "^16.11.7",
49
+ "@types/node": "^20.14.8",
49
50
  "@types/semver": "^7.5.8",
50
- "@types/tmp": "^0.1.0",
51
+ "@types/tmp": "^0.2.2",
52
+ "@types/yauzl": "^2.10.3",
51
53
  "@typescript-eslint/eslint-plugin": "^5.44.0",
52
54
  "@typescript-eslint/parser": "^5.44.0",
53
55
  "eslint": "^8.28.0",
@@ -9,7 +9,7 @@
9
9
  ********************************************************************************/
10
10
 
11
11
  import { Registry, RegistryOptions } from './registry';
12
- import { addEnvOptions } from './util';
12
+ import { addEnvOptions, getPAT } from './util';
13
13
 
14
14
  /**
15
15
  * Creates a namespace (corresponds to `publisher` in package.json).
@@ -19,9 +19,8 @@ export async function createNamespace(options: CreateNamespaceOptions = {}): Pro
19
19
  if (!options.name) {
20
20
  throw new Error('The namespace name is mandatory.');
21
21
  }
22
- if (!options.pat) {
23
- throw new Error("A personal access token must be given with the option '--pat'.");
24
- }
22
+
23
+ options.pat = await getPAT(options.name, options, false);
25
24
 
26
25
  const registry = new Registry(options);
27
26
  const result = await registry.createNamespace(options.name, options.pat);
package/src/login.ts ADDED
@@ -0,0 +1,41 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2024 Precies. Software and others
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * SPDX-License-Identifier: EPL-2.0
9
+ ********************************************************************************/
10
+ import { } from '@vscode/vsce';
11
+ import { addEnvOptions, getUserInput, requestPAT } from './util';
12
+ import { openDefaultStore } from './store';
13
+ import { RegistryOptions } from './registry';
14
+
15
+ export default async function login(options: LoginOptions) {
16
+ addEnvOptions(options);
17
+ if (!options.namespace) {
18
+ throw new Error('Missing namespace name.');
19
+ }
20
+
21
+ const store = await openDefaultStore();
22
+ let pat = store.get(options.namespace);
23
+ if (pat) {
24
+ console.log(`Namespace '${options.namespace}' is already known.`);
25
+ const answer = await getUserInput('Do you want to overwrite its PAT? [y/N] ');
26
+
27
+ if (!/^y$/i.test(answer)) {
28
+ throw new Error('Aborted.');
29
+ }
30
+ }
31
+
32
+ pat = await requestPAT(options.namespace, options);
33
+ await store.add(options.namespace, pat);
34
+ }
35
+
36
+ export interface LoginOptions extends RegistryOptions {
37
+ /**
38
+ * Name of the namespace.
39
+ */
40
+ namespace?: string
41
+ }
package/src/logout.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { openDefaultStore } from "./store";
2
+
3
+ export default async function logout(namespaceName: string) {
4
+ if (!namespaceName) {
5
+ throw new Error('Missing namespace name.');
6
+ }
7
+
8
+ const store = await openDefaultStore();
9
+ if (!store.get(namespaceName)) {
10
+ throw new Error(`Unknown namespace '${namespaceName}'.`);
11
+ }
12
+
13
+ await store.delete(namespaceName);
14
+ console.log(`\ud83d\ude80 ${namespaceName} removed from the list of known namespaces`);
15
+ }
package/src/main.ts CHANGED
@@ -15,6 +15,8 @@ import { verifyPat } from './verify-pat';
15
15
  import { publish } from './publish';
16
16
  import { handleError } from './util';
17
17
  import { getExtension } from './get';
18
+ import login from './login';
19
+ import logout from './logout';
18
20
 
19
21
  const pkg = require('../package.json');
20
22
 
@@ -52,7 +54,8 @@ module.exports = function (argv: string[]): void {
52
54
  .option('--pre-release', 'Mark this package as a pre-release')
53
55
  .option('--no-dependencies', 'Disable dependency detection via npm or yarn')
54
56
  .option('--skip-duplicate', 'Fail silently if version already exists on the marketplace')
55
- .action((extensionFile: string, { target, packagePath, baseContentUrl, baseImagesUrl, yarn, preRelease, dependencies, skipDuplicate }) => {
57
+ .option('--packageVersion <version>', 'Version of the provided VSIX packages.')
58
+ .action((extensionFile: string, { target, packagePath, baseContentUrl, baseImagesUrl, yarn, preRelease, dependencies, skipDuplicate, packageVersion }) => {
56
59
  if (extensionFile !== undefined && packagePath !== undefined) {
57
60
  console.error('\u274c Please specify either a package file or a package path, but not both.\n');
58
61
  publishCmd.help();
@@ -67,8 +70,10 @@ module.exports = function (argv: string[]): void {
67
70
  console.warn("Ignoring option '--baseImagesUrl' for prepackaged extension.");
68
71
  if (extensionFile !== undefined && yarn !== undefined)
69
72
  console.warn("Ignoring option '--yarn' for prepackaged extension.");
73
+ if (extensionFile !== undefined && packageVersion !== undefined)
74
+ console.warn("Ignoring option '--packageVersion' for prepackaged extension.");
70
75
  const { registryUrl, pat } = program.opts();
71
- publish({ extensionFile, registryUrl, pat, targets: typeof target === 'string' ? [target] : target, packagePath: typeof packagePath === 'string' ? [packagePath] : packagePath, baseContentUrl, baseImagesUrl, yarn, preRelease, dependencies, skipDuplicate })
76
+ publish({ extensionFile, registryUrl, pat, targets: typeof target === 'string' ? [target] : target, packagePath: typeof packagePath === 'string' ? [packagePath] : packagePath, baseContentUrl, baseImagesUrl, yarn, preRelease, dependencies, skipDuplicate, packageVersion })
72
77
  .then(results => {
73
78
  const reasons = results.filter(result => result.status === 'rejected')
74
79
  .map(result => result as PromiseRejectedResult)
@@ -76,7 +81,7 @@ module.exports = function (argv: string[]): void {
76
81
 
77
82
  if (reasons.length > 0) {
78
83
  const message = 'See the documentation for more information:\n'
79
- + 'https://github.com/eclipse/openvsx/wiki/Publishing-Extensions';
84
+ + 'https://github.com/eclipse/openvsx/wiki/Publishing-Extensions';
80
85
  const errorHandler = handleError(program.debug, message, false);
81
86
  for (const reason of reasons) {
82
87
  errorHandler(reason);
@@ -99,6 +104,19 @@ module.exports = function (argv: string[]): void {
99
104
  .catch(handleError(program.debug));
100
105
  });
101
106
 
107
+ const loginCmd = program.command('login <namespace>');
108
+ loginCmd.description('Adds a namespace to the list of known namespaces')
109
+ .action((namespace: string) => {
110
+ const { registryUrl, pat } = program.opts();
111
+ login({ namespace, registryUrl, pat }).catch(handleError(program.debug));
112
+ });
113
+
114
+ const logoutCmd = program.command('logout <namespace>');
115
+ logoutCmd.description('Removes a namespace from the list of known namespaces')
116
+ .action((namespace: string) => {
117
+ logout(namespace).catch(handleError(program.debug));
118
+ });
119
+
102
120
  program
103
121
  .command('*', '', { noHelp: true })
104
122
  .action((cmd: commander.Command) => {
package/src/ovsx CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  const semver = require('semver');
4
4
 
5
- if (semver.lt(process.versions.node, '18.0.0')) {
6
- console.error('ovsx requires at least NodeJS version 18. Check your installed version with `node --version`.');
5
+ if (semver.lt(process.versions.node, '20.0.0')) {
6
+ console.error('ovsx requires at least NodeJS version 20. Check your installed version with `node --version`.');
7
7
  process.exit(1);
8
8
  }
9
9
 
package/src/publish.ts CHANGED
@@ -8,32 +8,29 @@
8
8
  * SPDX-License-Identifier: EPL-2.0
9
9
  ********************************************************************************/
10
10
  import { createVSIX, IPackageOptions } from '@vscode/vsce';
11
- import { createTempFile, addEnvOptions } from './util';
11
+ import { createTempFile, addEnvOptions, getPAT } from './util';
12
12
  import { Extension, Registry, RegistryOptions } from './registry';
13
13
  import { checkLicense } from './check-license';
14
+ import { readVSIXPackage } from './zip';
14
15
 
15
16
  /**
16
17
  * Publishes an extension.
17
18
  */
18
19
  export async function publish(options: PublishOptions = {}): Promise<PromiseSettledResult<void>[]> {
19
- addEnvOptions(options);
20
- const internalPublishOptions: InternalPublishOptions[] = [];
21
- const packagePaths = options.packagePath || [undefined];
22
- const targets = options.targets || [undefined];
23
- for (const packagePath of packagePaths) {
24
- for (const target of targets) {
25
- internalPublishOptions.push({ ... options, packagePath: packagePath, target: target });
26
- }
20
+ addEnvOptions(options);
21
+ const internalPublishOptions: InternalPublishOptions[] = [];
22
+ const packagePaths = options.packagePath || [undefined];
23
+ const targets = options.targets || [undefined];
24
+ for (const packagePath of packagePaths) {
25
+ for (const target of targets) {
26
+ internalPublishOptions.push({ ...options, packagePath: packagePath, target: target });
27
27
  }
28
+ }
28
29
 
29
- return Promise.allSettled(internalPublishOptions.map(publishOptions => doPublish(publishOptions)));
30
+ return Promise.allSettled(internalPublishOptions.map(publishOptions => doPublish(publishOptions)));
30
31
  }
31
32
 
32
33
  async function doPublish(options: InternalPublishOptions = {}): Promise<void> {
33
- if (!options.pat) {
34
- throw new Error("A personal access token must be given with the option '--pat'.");
35
- }
36
-
37
34
  // if the packagePath is a link to a vsix, don't need to package it
38
35
  if (options.packagePath?.endsWith('.vsix')) {
39
36
  options.extensionFile = options.packagePath;
@@ -48,6 +45,11 @@ async function doPublish(options: InternalPublishOptions = {}): Promise<void> {
48
45
  console.warn("Ignoring option '--pre-release' for prepackaged extension.");
49
46
  }
50
47
 
48
+ if (!options.pat) {
49
+ const namespace = (await readVSIXPackage(options.extensionFile!)).publisher;
50
+ options.pat = await getPAT(namespace, options);
51
+ }
52
+
51
53
  let extension: Extension | undefined;
52
54
  try {
53
55
  extension = await registry.publish(options.extensionFile!, options.pat);
@@ -100,6 +102,10 @@ interface PublishCommonOptions extends RegistryOptions {
100
102
  * Whether to fail silently if version already exists on the marketplace
101
103
  */
102
104
  skipDuplicate?: boolean;
105
+ /**
106
+ * Extension version. Only valid with `packagePath`.
107
+ */
108
+ packageVersion?: string;
103
109
  }
104
110
 
105
111
  // Interface used by top level CLI
@@ -141,7 +147,7 @@ interface InternalPublishOptions extends PublishCommonOptions {
141
147
  /**
142
148
  * Whether to do dependency detection via npm or yarn
143
149
  */
144
- dependencies?: boolean;
150
+ dependencies?: boolean;
145
151
  }
146
152
 
147
153
  async function packageExtension(options: InternalPublishOptions, registry: Registry): Promise<void> {
@@ -158,7 +164,8 @@ async function packageExtension(options: InternalPublishOptions, registry: Regis
158
164
  baseImagesUrl: options.baseImagesUrl,
159
165
  useYarn: options.yarn,
160
166
  dependencies: options.dependencies,
161
- preRelease: options.preRelease
167
+ preRelease: options.preRelease,
168
+ version: options.packageVersion
162
169
  };
163
170
  await createVSIX(packageOptions);
164
171
  }
package/src/store.ts ADDED
@@ -0,0 +1,142 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { homedir } from 'os';
4
+
5
+ interface StoreEntry {
6
+ name: string
7
+ value: string
8
+ }
9
+
10
+ export interface Store extends Iterable<StoreEntry> {
11
+ readonly size: number;
12
+ get(name: string): string | undefined;
13
+ add(name: string, value: string): Promise<void>;
14
+ delete(name: string): Promise<void>;
15
+ }
16
+
17
+ export class FileStore implements Store {
18
+ private static readonly DefaultPath = path.join(homedir(), '.ovsx');
19
+
20
+ static async open(path: string = FileStore.DefaultPath): Promise<FileStore> {
21
+ try {
22
+ const rawStore = await fs.promises.readFile(path, 'utf8');
23
+ return new FileStore(path, JSON.parse(rawStore).entries);
24
+ } catch (err: any) {
25
+ if (err.code === 'ENOENT') {
26
+ return new FileStore(path, []);
27
+ } else if (/SyntaxError/.test(err)) {
28
+ throw new Error(`Error parsing file store: ${path}.`);
29
+ }
30
+
31
+ throw err;
32
+ }
33
+ }
34
+
35
+ get size(): number {
36
+ return this.entries.length;
37
+ }
38
+
39
+ private constructor(readonly path: string, private entries: StoreEntry[]) { }
40
+
41
+ private async save(): Promise<void> {
42
+ await fs.promises.writeFile(this.path, JSON.stringify({ entries: this.entries }), { mode: '0600' });
43
+ }
44
+
45
+ async deleteStore(): Promise<void> {
46
+ try {
47
+ await fs.promises.unlink(this.path);
48
+ } catch {
49
+ // noop
50
+ }
51
+ }
52
+
53
+ get(name: string): string | undefined {
54
+ return this.entries.find(p => p.name === name)?.value;
55
+ }
56
+
57
+ async add(name: string, value: string): Promise<void> {
58
+ const newEntry: StoreEntry = { name, value };
59
+ this.entries = [...this.entries.filter(p => p.name !== name), newEntry];
60
+ await this.save();
61
+ }
62
+
63
+ async delete(name: string): Promise<void> {
64
+ this.entries = this.entries.filter(p => p.name !== name);
65
+ await this.save();
66
+ }
67
+
68
+ [Symbol.iterator]() {
69
+ return this.entries[Symbol.iterator]();
70
+ }
71
+ }
72
+
73
+ export class KeytarStore implements Store {
74
+ static async open(serviceName = 'ovsx'): Promise<KeytarStore> {
75
+ const keytar = await import('keytar');
76
+ const creds = await keytar.findCredentials(serviceName);
77
+
78
+ return new KeytarStore(
79
+ keytar,
80
+ serviceName,
81
+ creds.map(({ account, password }) => ({ name: account, value: password }))
82
+ );
83
+ }
84
+
85
+ get size(): number {
86
+ return this.entries.length;
87
+ }
88
+
89
+ private constructor(
90
+ private readonly keytar: typeof import('keytar'),
91
+ private readonly serviceName: string,
92
+ private entries: StoreEntry[]
93
+ ) { }
94
+
95
+ get(name: string): string | undefined {
96
+ return this.entries.find(p => p.name === name)?.value;
97
+ }
98
+
99
+ async add(name: string, value: string): Promise<void> {
100
+ const newEntry: StoreEntry = { name, value };
101
+ this.entries = [...this.entries.filter(p => p.name !== name), newEntry];
102
+ await this.keytar.setPassword(this.serviceName, name, value);
103
+ }
104
+
105
+ async delete(name: string): Promise<void> {
106
+ this.entries = this.entries.filter(p => p.name !== name);
107
+ await this.keytar.deletePassword(this.serviceName, name);
108
+ }
109
+
110
+ [Symbol.iterator](): Iterator<StoreEntry, any, undefined> {
111
+ return this.entries[Symbol.iterator]();
112
+ }
113
+ }
114
+
115
+ export async function openDefaultStore(): Promise<Store> {
116
+ if (/^file$/i.test(process.env['OVSX_STORE'] ?? '')) {
117
+ return await FileStore.open();
118
+ }
119
+
120
+ let keytarStore: Store;
121
+ try {
122
+ keytarStore = await KeytarStore.open();
123
+ } catch (err) {
124
+ const store = await FileStore.open();
125
+ console.warn(`Failed to open credential store. Falling back to storing secrets clear-text in: ${store.path}.`);
126
+ return store;
127
+ }
128
+
129
+ const fileStore = await FileStore.open();
130
+
131
+ // migrate from file store
132
+ if (fileStore.size) {
133
+ for (const { name, value } of fileStore) {
134
+ await keytarStore.add(name, value);
135
+ }
136
+
137
+ await fileStore.deleteStore();
138
+ console.info(`Migrated ${fileStore.size} publishers to system credential manager. Deleted local store '${fileStore.path}'.`);
139
+ }
140
+
141
+ return keytarStore;
142
+ }
package/src/util.ts CHANGED
@@ -14,6 +14,10 @@ import * as tmp from 'tmp';
14
14
  import * as http from 'http';
15
15
  import * as readline from 'readline';
16
16
  import { RegistryOptions } from './registry';
17
+ import { VerifyPatOptions, doVerifyPat } from './verify-pat';
18
+ import { PublishOptions } from './publish';
19
+ import { openDefaultStore } from './store';
20
+ import { CreateNamespaceOptions } from './create-namespace';
17
21
 
18
22
  export { promisify } from 'util';
19
23
 
@@ -183,3 +187,29 @@ export async function getUserChoice<R extends string>(text: string, values: R[],
183
187
  }
184
188
  return defaultValue;
185
189
  }
190
+
191
+ export async function requestPAT(namespace: string, options: CreateNamespaceOptions | PublishOptions | VerifyPatOptions, verify: boolean = true): Promise<string> {
192
+ const pat = await getUserInput(`Personal Access Token for namespace '${namespace}':`);
193
+ if (verify) {
194
+ await doVerifyPat({ ...options, namespace, pat });
195
+ }
196
+
197
+ return pat;
198
+ }
199
+
200
+ export async function getPAT(namespace: string, options: CreateNamespaceOptions | PublishOptions | VerifyPatOptions, verify: boolean = true): Promise<string> {
201
+ if (options?.pat) {
202
+ return options.pat;
203
+ }
204
+
205
+ const store = await openDefaultStore();
206
+ let pat = store.get(namespace);
207
+ if (pat) {
208
+ return pat;
209
+ }
210
+
211
+ pat = await requestPAT(namespace, options, verify);
212
+ await store.add(namespace, pat);
213
+
214
+ return pat;
215
+ }
package/src/verify-pat.ts CHANGED
@@ -9,39 +9,42 @@
9
9
  ********************************************************************************/
10
10
 
11
11
  import { Registry, RegistryOptions } from './registry';
12
- import { readManifest, addEnvOptions } from './util';
12
+ import { readManifest, addEnvOptions, getPAT } from './util';
13
13
 
14
14
  /**
15
15
  * Validates that a Personal Access Token can publish to a namespace.
16
16
  */
17
17
  export async function verifyPat(options: VerifyPatOptions): Promise<void> {
18
18
  addEnvOptions(options);
19
- if (!options.pat) {
20
- throw new Error("A personal access token must be given with the option '--pat'.");
21
- }
22
-
23
19
  if (!options.namespace) {
24
- let error;
25
- try {
26
- options.namespace = (await readManifest()).publisher;
27
- } catch (e) {
28
- error = e;
29
- }
20
+ let error;
21
+ try {
22
+ options.namespace = (await readManifest()).publisher;
23
+ } catch (e) {
24
+ error = e;
25
+ }
30
26
 
31
- if (!options.namespace) {
32
- throw new Error(
33
- `Unable to read the namespace's name. Please supply it as an argument or run ovsx from the extension folder.` +
34
- (error ? `\n\n${error}` : '')
35
- );
36
- }
27
+ if (!options.namespace) {
28
+ throw new Error(
29
+ `Unable to read the namespace's name. Please supply it as an argument or run ovsx from the extension folder.` +
30
+ (error ? `\n\n${error}` : '')
31
+ );
32
+ }
37
33
  }
38
34
 
35
+ options.pat = await getPAT(options.namespace, options, false);
36
+ await doVerifyPat(options);
37
+ }
38
+
39
+ export async function doVerifyPat(options: VerifyPatOptions) {
39
40
  const registry = new Registry(options);
40
- const result = await registry.verifyPat(options.namespace, options.pat);
41
+ const namespace = options.namespace as string;
42
+ const pat = options.pat as string;
43
+ const result = await registry.verifyPat(namespace, pat);
41
44
  if (result.error) {
42
45
  throw new Error(result.error);
43
46
  }
44
- console.log(`\ud83d\ude80 PAT valid to publish at ${options.namespace}`);
47
+ console.log(`\ud83d\ude80 PAT valid to publish at ${namespace}`);
45
48
  }
46
49
 
47
50
  export interface VerifyPatOptions extends RegistryOptions {