Compare commits

...
Sign in to create a new pull request.

183 commits

Author SHA1 Message Date
9ccf7e2778 Version bump 2020-09-19 04:19:05 -04:00
0d3899d745 Missing property 2020-09-19 04:18:02 -04:00
ed9dceb8e8 Name change 2020-09-11 20:06:47 -04:00
ed4c3aaaec pnpm install --shamefully-hoist works 2020-09-05 01:05:43 -04:00
f2c83c1bc3 Added pnpm support. Sloppy and incomplete. Unsure of what to do
with the symlinks without modifying the project.

microsoft/vscode-vsce#421
2020-09-04 22:54:15 -04:00
Sandeep Somavarapu
5ade7bfa42 1.79.5 2020-08-31 13:01:47 +02:00
Sandeep Somavarapu
d971fb692b throw error if extension is not a web extension 2020-08-30 18:57:43 +02:00
Sandeep Somavarapu
6b9d1f3f11 1.79.4 2020-08-28 17:56:50 +02:00
Sandeep Somavarapu
2cd3c14d9d add __web_extension tag for web extensions 2020-08-28 17:56:35 +02:00
Sandeep Somavarapu
789e14b31a 1.79.3 2020-08-25 11:41:50 +02:00
Sandeep Somavarapu
d3c2ffc5f4 do not allow web publishing when package path is provided 2020-08-25 11:24:54 +02:00
Sandeep Somavarapu
d15456e941 1.79.2 2020-08-24 20:46:52 +02:00
Sandeep Somavarapu
4b42b6ce38 enable web flag only on publish 2020-08-24 20:46:10 +02:00
Sandeep Somavarapu
74172a1b80 1.79.1 2020-08-24 16:35:54 +02:00
Sandeep Somavarapu
fb9170a707 check for supported web extensions also while packing 2020-08-24 16:35:44 +02:00
Sandeep Somavarapu
665047b6d2 1.79.0 2020-08-24 16:06:48 +02:00
Sandeep Somavarapu
281482632c check for valid web extensions only while publishing 2020-08-24 16:05:02 +02:00
Sandeep Somavarapu
4a019da349 fix error message 2020-08-24 13:19:19 +02:00
Sandeep Somavarapu
bb205c01d0 fix ls command in error 2020-08-24 13:13:10 +02:00
Sandeep Somavarapu
dc49ab513c - add extension kind property
- add web extension property
- include all files as web resources
- cap web resources to 25
- add tests
2020-08-24 13:03:29 +02:00
Sandeep Somavarapu
3b10000ad4 support web extensions 2020-08-24 11:39:44 +02:00
João Moreno
9633988aa0
1.78.0 2020-08-11 20:06:23 +02:00
João Moreno
bf11c09458
Merge pull request #474 from JeffreyCA/jeffreyca/github-branch
Add githubBranch flag to control the branch for GitHub relative links
2020-08-11 10:53:27 +02:00
JeffreyCA
728b7930bc Update tests 2020-08-10 14:25:00 -04:00
JeffreyCA
fc75efa9ad Add test 2020-08-08 22:33:59 -04:00
JeffreyCA
16f35b3485 Add githubBranch flag to control the branch for GitHub relative links 2020-08-08 22:33:50 -04:00
João Moreno
638e34f818
Merge pull request #472 from JeffreyCA/jeffreyca/mailto-fix
Prevent mailto links from being joined with prefix
2020-08-03 17:00:23 +02:00
Jeffrey Chen
960c841778 Prevent mailto links from being joined with prefix 2020-08-03 10:36:09 -04:00
Greg Van Liew
b95a11bf0d
Fix typo in README
Fixes #470
2020-07-25 15:10:44 -07:00
João Moreno
f61ae38395
Merge pull request #467 from microsoft/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-07-17 10:12:51 +02:00
dependabot[bot]
2a6d88d7ba
Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-16 22:33:45 +00:00
João Moreno
820c01424e
Merge pull request #461 from viktomas/updated-launch
Update launch.json to be useful for extension debugging
2020-06-29 16:17:19 +02:00
Tomas Vik
ad67f3554a Update launch.json to be useful for extension debugging 2020-06-26 17:43:19 +02:00
João Moreno
41ef704b57
Merge pull request #462 from viktomas/fix-missing-out
Fix issues with missing out directory
2020-06-26 16:03:28 +02:00
Tomas Vik
b57d6a60a0 Fix issues with missing out directory 2020-06-26 14:36:59 +02:00
João Moreno
96e7e42bb4
1.77.0 2020-06-15 09:42:47 +02:00
João Moreno
600505a065
cleanup github badge validation 2020-06-15 09:41:46 +02:00
João Moreno
6adca5365f
Merge branch 'master' into pr/396 2020-06-15 09:35:01 +02:00
João Moreno
a4cca08746
1.76.1 2020-06-10 11:24:35 +02:00
João Moreno
1d582dccc9
fixes #455 2020-06-10 11:24:19 +02:00
João Moreno
2929c77b3a
add test task 2020-06-10 11:23:56 +02:00
João Moreno
d1609eea71
1.76.0 2020-06-05 13:59:33 +02:00
João Moreno
ba42d4cb40
Merge pull request #447 from owenfarrell/environmental-pat
Support injecting PAT from an environmental variable
2020-05-12 14:31:25 +01:00
Owen Farrell
07ea892302 Support injecting PAT from an environmental variable 2020-05-11 12:34:27 -04:00
João Moreno
4960c7c674 use spawn instead of exec 2020-04-20 13:52:52 +02:00
Ilia Pozdnyakov
dc36c1f16b print output of failed prepublish scripts #441 2020-04-17 16:11:22 +07:00
João Moreno
48e02a8773 1.75.0 2020-04-02 11:56:55 +02:00
João Moreno
68e143f621 upgrade mocha 2020-04-02 11:56:42 +02:00
João Moreno
33e72f99ca
Merge pull request #436 from jamesgeorge007/migrate-dep
Migrate to a better matching engine
2020-04-02 11:40:25 +02:00
João Moreno
3dc84fbd22
Merge pull request #437 from jamesgeorge007:update-dep
Fixed markdown-it security vulnerability
2020-04-02 09:48:12 +02:00
jamesgeorge007
48702544df chore: update lock file 2020-04-01 12:03:31 +05:30
jamesgeorge007
9a66cdca45 chore: update markdown-it 2020-04-01 12:01:50 +05:30
jamesgeorge007
18845b1ea5 refactor: better approach 2020-04-01 11:54:31 +05:30
jamesgeorge007
2dec836b11 refactor: inline approach 2020-04-01 11:52:13 +05:30
jamesgeorge007
e287784228 feat: recommend matching commands 2020-04-01 11:50:49 +05:30
jamesgeorge007
f3e7284f8e chore: update lockfile 2020-04-01 11:48:39 +05:30
jamesgeorge007
ff9ce089ec chore: add leven 2020-04-01 11:48:03 +05:30
jamesgeorge007
03a95d4886 chore: drop type defnition lib for didyoumean 2020-04-01 11:47:34 +05:30
jamesgeorge007
2be9d7def6 chore: drop didyoumean 2020-04-01 11:47:00 +05:30
João Moreno
3d7afdded1
Merge pull request #434 from bobbrow/master
Change a console.warn to console.log for an informational message
2020-03-31 17:32:30 +02:00
Bob Brown (DEVDIV)
fbebf95182 Change a console.warn to console.log for an informational message 2020-03-31 08:29:57 -07:00
João Moreno
2a6d2e6cea fixes #423 2020-03-31 16:58:44 +02:00
Joao Moreno
66a41f19ab
1.74.0 2020-02-27 08:24:17 +01:00
João Moreno
acd1c3c96b
Merge pull request #424 from rjmholt/stop-issue-expansion
Add --noGitHubIssueLinking to stop issue link expansion in package step
2020-02-27 08:23:51 +01:00
Robert Holt
368ffbd535 Add --noGitHubIssueLinking to stop issue link expansion in package step 2020-02-20 13:22:50 -08:00
Joao Moreno
ee42cf42c0
upgrade lodash
fixes #422
2020-02-18 10:03:19 +01:00
Joao Moreno
032cca7738
setup default build task 2020-02-18 09:59:58 +01:00
Joao Moreno
32d7b80e1d
1.73.0 2020-02-03 09:23:13 +01:00
Joao Moreno
285c1844d0
Revert "fixes #415"
This reverts commit e4a8df59d3.
2020-02-03 09:23:05 +01:00
Joao Moreno
2ca36aa7fc 1.72.0 2020-01-31 10:42:28 +01:00
Joao Moreno
e4a8df59d3
fixes #415 2020-01-21 15:09:10 +01:00
Joao Moreno
7eba451e35
Revert "Merge pull request #411 from felixhao28/master"
This reverts commit 2a02458bad, reversing
changes made to 2844e5bed5.
2020-01-21 15:08:42 +01:00
João Moreno
6e5c2d5488
Merge pull request #412 from xontab/bugfix/changed-log-level-error-exec-npm
Log level for npm list command changed to error to increase compatibility
2020-01-13 15:18:08 +01:00
João Moreno
ef3322b35a
Merge branch 'master' into bugfix/changed-log-level-error-exec-npm 2020-01-13 15:17:14 +01:00
João Moreno
2a02458bad
Merge pull request #411 from felixhao28/master
Support linked npm modules
2020-01-13 12:22:49 +01:00
João Moreno
2844e5bed5
Merge pull request #409 from wraith13/patch-1
Modifying messages to match the current situation
2020-01-13 12:21:13 +01:00
João Moreno
845eb79541
Update README.md 2020-01-13 11:56:24 +01:00
Shaun Tabone
e0d73c9b8e Minor refactoring 2019-12-29 14:55:04 +01:00
Shaun Tabone
2e003b0dc5 Changed loglevel to error when listing deps using npm list command 2019-12-29 14:41:11 +01:00
Yiyang Hao
4302d01253 Support linked npm modules
Fixes #203
2019-12-25 11:30:43 +08:00
道化師
2d8dc608e8
Modifying messages to match the current situation
I know it was a few seconds before. But now it is a few minutes.
2019-12-19 14:14:01 +09:00
Joao Moreno
f2c29b5440 1.71.0 2019-12-09 08:35:05 +01:00
Joao Moreno
3bcc6039da
async function unpublish 2019-12-02 10:18:09 +01:00
Joao Moreno
aa73aacb0e
Merge commit 'refs/pull/405/head' of github.com:microsoft/vscode-vsce into pr/405 2019-12-02 09:57:43 +01:00
Joao Moreno
3fb175aa64
1.70.0 2019-12-02 09:54:50 +01:00
Joao Moreno
392778b924
fix buffer ctors 2019-12-02 09:54:38 +01:00
Joao Moreno
e90ffca8ba
update link 2019-12-02 09:53:33 +01:00
James
955e3df4d3 fixes #400 allow force to unpublish an extension 2019-11-30 14:06:59 -05:00
Joao Moreno
facdf0ac8d fixes #403 2019-11-30 11:48:02 +01:00
João Moreno
8ae7985365
Merge pull request #295 from ndrake/bugfix/dot-vsce-permissions-230
Set permissions on .vsce to 0600; fix for #230
2019-11-28 17:03:29 +01:00
Joao Moreno
b044011b63 ts: noUnusedLocals, noUnusedParameters 2019-11-28 16:50:49 +01:00
Joao Moreno
56f916b4fc Merge commit 'refs/pull/294/head' of github.com:microsoft/vscode-vsce into pr/294 2019-11-28 16:41:35 +01:00
Joao Moreno
c5a0e103fc cleanup getPackagePath 2019-11-28 16:32:06 +01:00
Joao Moreno
33e3227970 Merge commit 'refs/pull/248/head' of github.com:microsoft/vscode-vsce into pr/248 2019-11-28 16:31:40 +01:00
Joao Moreno
157c01c4a7 fixes #402 2019-11-28 16:13:46 +01:00
Joao Moreno
d0693862cf update watch task 2019-11-01 21:02:03 +01:00
Joao Moreno
282b740301
1.69.0 2019-10-29 15:30:02 +01:00
Joao Moreno
022b175b0c
Merge commit 'refs/pull/208/head' of github.com:Microsoft/vscode-vsce into pr/208 2019-10-29 15:16:56 +01:00
Adam S
8455460dbc
add test ensuring non-workflow SVGs aren't allowed 2019-10-18 21:24:32 -05:00
Adam S
e80056d64e
add feature #389 2019-10-17 18:27:55 -05:00
Joao Moreno
fca4c7ce19
1.68.0 2019-10-16 15:54:18 +02:00
Joao Moreno
107f68bfbe
Revert "fixes #385"
This reverts commit 84f537a4fa.

fixes #395
2019-10-16 15:52:01 +02:00
João Moreno
a154e8ce27
Merge pull request #392 from tzfrs/master
Improve error message when packaging an extension with an unchanged README.md
2019-10-11 06:11:47 -07:00
Theodoros Tzaferis
4903798070 Add test for catching unchanged README.md 2019-10-11 13:58:02 +02:00
Theodoros Tzaferis
a87a725c30 Improve error message when packaging an extension with an unchanged README.md 2019-10-11 13:57:38 +02:00
Joao Moreno
226554637c
remove cpx 2019-10-01 14:42:10 +02:00
João Moreno
1bc8c65547
Merge pull request #388 from microsoft/dependabot/npm_and_yarn/mixin-deep-1.3.2
Bump mixin-deep from 1.3.1 to 1.3.2
2019-10-01 10:01:13 +02:00
dependabot[bot]
c6fe5fd5e4
Bump mixin-deep from 1.3.1 to 1.3.2
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-10-01 07:41:07 +00:00
Joao Moreno
6bb1d40a1b
1.67.1 2019-10-01 09:39:36 +02:00
João Moreno
830843f54a
Merge pull request #387 from MajhiRockzZ/minor-fix
Update README.md
2019-09-23 10:16:00 +02:00
Sumesh Majhi
f6be2f032f Update README.md 2019-09-22 16:11:17 +05:30
Joao Moreno
84f537a4fa fixes #385 2019-09-02 09:16:48 +02:00
Joao Moreno
2303cfafa3 fixes #384 2019-08-30 09:07:16 +02:00
Joao Moreno
c770da590b 1.67.0 2019-08-09 08:37:40 +02:00
Joao Moreno
fecd9f311d fixes #211 2019-08-09 08:36:39 +02:00
Joao Moreno
6b344de515 1.66.0 2019-07-23 11:39:58 +02:00
João Moreno
188c304780
Merge pull request #376 from eamodio/prepublish-yarn
Honors --yarn flag for prepublish step
2019-07-23 11:39:38 +02:00
Eric Amodio
bef8e1c7ac Honors --yarn flag for prepublish step 2019-07-19 02:16:29 -04:00
Joao Moreno
79289884af fixes #372 2019-07-11 17:37:11 +02:00
Joao Moreno
82ce52726f fixes #371 2019-07-11 15:57:44 +02:00
João Moreno
416ed1929b
Merge pull request #373 from microsoft/dependabot/npm_and_yarn/lodash-4.17.13
Bump lodash from 4.17.11 to 4.17.13
2019-07-11 11:38:57 +02:00
dependabot[bot]
e794552b45
Bump lodash from 4.17.11 to 4.17.13
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.13.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.13)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-11 09:37:46 +00:00
Joao Moreno
29b7072a88 1.65.0 2019-07-11 11:34:32 +02:00
João Moreno
d999140b44
Merge pull request #345 from HaaLeo/master
Support GitHub Issue Links
2019-07-11 11:33:21 +02:00
João Moreno
e8d8304384
Merge pull request #370 from jamesgeorge007/feat/refactor
Minor refactor resulting in concise code
2019-07-03 09:10:50 +02:00
jamesgeorge007
6a64c4f294 Minor refactor 2019-07-01 19:58:30 +05:30
João Moreno
5d2af9c38a
Merge pull request #369 from atsutton/master
Changed log level to info.
2019-06-27 10:22:59 +02:00
atsutton
fba5037db2 Changed log level to info. 2019-06-26 14:39:07 -04:00
Joao Moreno
744a8c64a0 add npm badge 2019-06-20 11:29:57 +02:00
Joao Moreno
d90d09919f update docs 2019-06-20 11:23:47 +02:00
Joao Moreno
2bf0f3317a improve node and yarn versions 2019-06-20 11:23:41 +02:00
Joao Moreno
ec8398369c update build badge 2019-06-20 11:05:03 +02:00
Joao Moreno
cff60534fc 1.64.0 2019-06-20 10:58:40 +02:00
Joao Moreno
18b4584abe automatically publish to NPM 2019-06-20 10:57:18 +02:00
Joao Moreno
4097508a9b update scripts 2019-06-20 10:52:36 +02:00
Joao Moreno
029073abaf use azure pipelines 2019-06-20 10:30:23 +02:00
Joao Moreno
88f63c0232 💄 2019-06-20 10:23:51 +02:00
Joao Moreno
6a1d2e10a0 Merge commit 'refs/pull/365/head' of github.com:Microsoft/vscode-vsce into pr/365 2019-06-20 10:22:18 +02:00
João Moreno
5581573021
Merge pull request #366 from jamesgeorge007/hotfix/refactor
fix(chore): Minor refactor
2019-06-20 09:57:00 +02:00
jamesgeorge007
5420934808 Minor refactor
use process.platform instead of os.platform()
2019-06-19 18:25:57 +05:30
Jonathan Nagy
3975d49c0c Allow commit message to be specified 2019-06-17 12:03:40 +10:00
Joao Moreno
d054e81629 1.63.0 2019-06-12 12:19:35 +02:00
Joao Moreno
1bd9a31a16 hide * command 2019-06-12 12:19:29 +02:00
Joao Moreno
f4a1a66c0b cleanup help 2019-06-12 12:17:01 +02:00
Joao Moreno
99978276f6 Merge commit 'refs/pull/358/head' of github.com:Microsoft/vscode-vsce into pr/358 2019-06-12 12:05:48 +02:00
jamesgeorge007
3791361ddc Add type definitions for didyoumean.js 2019-06-07 07:40:00 +05:30
jamesgeorge007
f31fd51f86 yarn.lock 2019-06-05 18:22:39 +05:30
jamesgeorge007
e26f4d1db4 avoid dedicated method for command suggestion purpose 2019-06-05 18:12:49 +05:30
jamesgeorge007
98f9fb8282 Enclose suggested command within quotes 2019-06-04 22:44:49 +05:30
James George
ca1847ca06 Minor refactor
prettify

Co-Authored-By: João Moreno <mail@joaomoreno.com>
2019-06-04 22:39:32 +05:30
Joao Moreno
7c823caf48 Merge commit 'refs/pull/357/head' of github.com:Microsoft/vscode-vsce into pr/357 2019-06-04 11:31:48 +02:00
Joao Moreno
3a54891b07 forgot yarn.lock 2019-06-04 11:23:40 +02:00
jamesgeorge007
7cde772c1f Remove stub type definition for commander
commander provides its own type definitions
2019-06-04 11:24:19 +05:30
jamesgeorge007
1b84e233dd feat: suggest matching commands 2019-06-03 22:33:59 +05:30
jamesgeorge007
ac8c683044 Show up usage information the right way 2019-06-03 21:52:54 +05:30
João Moreno
e06834b9a1
Check @types/vscode version against engines.vscode (#354)
Check @types/vscode version against engines.vscode
2019-05-27 12:54:42 +02:00
Pine Wu
e6722a456e Address feedback 2019-05-24 10:40:18 -07:00
Joao Moreno
ff31611193 1.62.0 2019-05-24 12:11:18 +02:00
Joao Moreno
dd2b0efd98 add docs.rs to allowed badge list 2019-05-24 12:11:07 +02:00
Pine Wu
7722c8191b Various cleanup and fix edge cases 2019-05-23 11:13:25 -07:00
Pine Wu
e52ca9fb30 Check @types/vscode against engine for microsoft/vscode#71048 2019-05-23 10:53:50 -07:00
Joao Moreno
cd6d8a6e2e use aka.ms url 2019-05-23 11:13:41 +02:00
Adam Watters
be6d2fe835 added note about vscodeignore to bundle reminder 2019-05-17 14:25:20 -04:00
João Moreno
d4c270274f
Merge pull request #350 from allevato/buildkite-badge
Add badge.buildkite.com to trusted SVG sources
2019-05-16 14:25:17 -07:00
João Moreno
c7c8fca72e
Merge pull request #348 from kondratyev-nv/master
Move cpx package to devDependencies
2019-05-16 11:54:56 -07:00
Tony Allevato
48e1bf47bf Add badge.buildkite.com to trusted SVG sources 2019-05-15 15:03:34 -07:00
Nikolay Kondratyev
117583c371 Move cpx package to devDependencies 2019-05-11 19:57:15 +03:00
Christian
786118d097 if package path is a directory, use default file name to write vsix 2019-05-11 10:32:34 +02:00
João Moreno
a96a549c5a
Merge pull request #347 from JimiC/fix_task
Fix compile task
2019-05-09 16:28:58 +02:00
Jimi (Dimitris) Charalampidis
4b15f82be7
Fix compile task 2019-05-09 14:25:58 +03:00
unknown
bf41e290fc Added unit tests 2019-05-07 15:34:30 +02:00
unknown
c3cfc3a337 Microsoft/vscode-vsce#340:
* Enable to replace GitHub issue references with absolute urls
* Do not replace anything if it is no GitHub repository
2019-05-07 15:34:09 +02:00
Joao Moreno
1ee7a2bc0f 1.61.0 2019-05-03 15:24:53 +02:00
Joao Moreno
fe3de2e6f2 cleanup 2019-05-03 15:21:09 +02:00
Joao Moreno
2cfb898fd0 use basic handler 2019-05-03 13:04:00 +02:00
Nate Drake
b063a7ba7f Set permissions on .vsce to 0600; fix for #230 2018-10-09 09:32:47 -04:00
Alessandro Fragnani
5477a63e02 add --ignoreFile option to publish command 2018-10-01 21:18:45 -03:00
Alessandro Fragnani
778b942b38 add --ignoreFile option to package command 2018-10-01 20:54:39 -03:00
Alessandro Fragnani
fcbfc91cad add --ignoreFile option to ls command 2018-10-01 19:33:14 -03:00
Alessandro Fragnani
4c26522fde
Merge pull request #1 from Microsoft/master
Update from original
2018-10-01 18:35:07 -03:00
Ritwick Dey
00b07a2b38 small fixes 2017-10-03 18:49:48 +05:30
Ritwick Dey
0c526b3e4a test added 2017-10-03 18:36:53 +05:30
Ritwick Dey
ec03eac9eb Relative sources in <img> tags added 2017-10-03 18:36:45 +05:30
33 changed files with 3289 additions and 1813 deletions

View file

@ -1,7 +0,0 @@
language: node_js
node_js:
- '8'
sudo: false
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.5.1
- export PATH="$HOME/.yarn/bin:$PATH"

5
.vscode/launch.json vendored
View file

@ -8,13 +8,14 @@
"type": "node",
"request": "launch",
"name": "Launch Program",
// "cwd": "<absolute path to your extension>",
"program": "${workspaceFolder}/out/vsce",
"args": [
"--version"
// "ls", "package", "publish"
],
"sourceMaps": true,
"outputCapture": "std",
"preLaunchTask": "compile"
"outputCapture": "std"
}
]
}

17
.vscode/tasks.json vendored
View file

@ -1,16 +1,23 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "gulp",
"task": "compile",
"label": "compile",
"type": "npm",
"script": "watch",
"problemMatcher": [
"$tsc-watch"
],
"group": {
"kind": "build",
"isDefault": true
}
},
"isBackground": true
},
{
"type": "npm",
"script": "test"
}
]
}

View file

@ -1,13 +1,44 @@
# vsce
> *The Visual Studio Code Extension Manager*
[![build status](https://travis-ci.org/Microsoft/vscode-vsce.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-vsce)
[![Build Status](https://dev.azure.com/vscode/VSCE/_apis/build/status/VSCE?branchName=master)](https://dev.azure.com/vscode/VSCE/_build/latest?definitionId=16&branchName=master) [![npm version](https://badge.fury.io/js/vsce.svg)](https://badge.fury.io/js/vsce)
### Requirements
## Requirements
- [Node.js](https://nodejs.org/en/) at least `8.x.x`
### About
## Usage
`vsce` is meant to be mainly used as a command line tool. It can also be used a library since it exposes a small [API](https://github.com/microsoft/vscode-vsce/blob/master/src/api.ts).
> **Warning:** When using vsce as a library be sure to sanitize any user input used in API calls, as a security measure.
## Development
First clone this repository, then:
```sh
yarn
yarn watch # or `watch-test` to also run tests
```
Once the watcher is up and running, you can run out of sources with:
```sh
yarn vsce
```
### Publish to NPM
Simply push a new tag and the CI will automatically publish to NPM. The usual flow is:
```sh
npm version [minor|patch]
git push --follow-tags
```
## About
This tool assists in packaging and publishing Visual Studio Code extensions.

31
azure-pipelines.yml Normal file
View file

@ -0,0 +1,31 @@
trigger:
branches:
include: ['*']
tags:
include: ['*']
steps:
- task: NodeTool@0
inputs:
versionSpec: "8.x"
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: "1.x"
- script: yarn
displayName: Install Dependencies
- script: yarn compile
displayName: Compile
- script: yarn test
displayName: Run Tests
- task: Npm@1
displayName: 'Publish to NPM'
inputs:
command: publish
verbose: false
publishEndpoint: 'NPM'
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))

View file

@ -1,6 +1,6 @@
{
"name": "vsce",
"version": "1.60.0",
"name": "@entan.gl/vsce",
"version": "1.79.6",
"description": "VSCode Extension Manager",
"repository": {
"type": "git",
@ -24,11 +24,13 @@
"vsce": "out/vsce"
},
"scripts": {
"compile": "concurrently \"tsc\" \"cpx src/vsce out\"",
"watch": "concurrently \"tsc --watch\" \"cpx --watch src/vsce out\"",
"watch-test": "concurrently \"tsc --watch\" \"cpx --watch src/vsce out\" \"mocha --watch\"",
"test": "concurrently \"tsc\" \"cpx src/vsce out\" && mocha",
"prepublishOnly": "concurrently \"tsc\" \"cpx src/vsce out\" && mocha"
"copy-vsce": "mkdir -p out && cp src/vsce out/vsce",
"compile": "tsc && npm run copy-vsce",
"watch": "npm run copy-vsce && tsc --watch",
"watch-test": "npm run copy-vsce && concurrently \"tsc --watch\" \"mocha --watch\"",
"test": "mocha",
"prepublishOnly": "tsc && npm run copy-vsce && mocha",
"vsce": "out/vsce"
},
"engines": {
"node": ">= 8"
@ -38,11 +40,11 @@
"chalk": "^2.4.2",
"cheerio": "^1.0.0-rc.1",
"commander": "^2.8.1",
"cpx": "^1.5.0",
"denodeify": "^1.2.1",
"glob": "^7.0.6",
"lodash": "^4.17.10",
"markdown-it": "^8.3.1",
"leven": "^3.1.0",
"lodash": "^4.17.15",
"markdown-it": "^10.0.0",
"mime": "^1.3.4",
"minimatch": "^3.0.3",
"osenv": "^0.1.3",
@ -57,23 +59,28 @@
},
"devDependencies": {
"@types/cheerio": "^0.22.1",
"@types/commander": "^2.12.2",
"@types/denodeify": "^1.2.31",
"@types/glob": "^7.1.1",
"@types/lodash": "^4.14.123",
"@types/markdown-it": "0.0.2",
"@types/mime": "^1",
"@types/minimatch": "^3.0.3",
"@types/mocha": "^5.2.6",
"@types/mocha": "^7.0.2",
"@types/node": "^8",
"@types/read": "^0.0.28",
"@types/semver": "^6.0.0",
"@types/tmp": "^0.1.0",
"@types/xml2js": "^0.4.4",
"concurrently": "^4.1.0",
"mocha": "^5.2.0",
"concurrently": "^5.1.0",
"mocha": "^7.1.1",
"source-map-support": "^0.4.2",
"typescript": "^3.4.3",
"xml2js": "^0.4.12"
},
"mocha": {
"require": [
"source-map-support/register"
],
"spec": "out/test"
}
}

1301
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@
<Property Id="Microsoft.VisualStudio.Code.Engine" Value="<%- engine %>" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="<%- extensionDependencies %>" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="<%- extensionPack %>" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionKind" Value="<%- extensionKind %>" />
<Property Id="Microsoft.VisualStudio.Code.LocalizedLanguages" Value="<%- localizedLanguages %>" />
<% if (links.repository) { %>
<Property Id="Microsoft.VisualStudio.Services.Links.Source" Value="<%- links.repository %>" />
@ -29,6 +30,7 @@
<Property Id="Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown" Value="<%- githubMarkdown %>" />
<% if (typeof enableMarketplaceQnA === 'boolean') { %><Property Id="Microsoft.VisualStudio.Services.EnableMarketplaceQnA" Value="<%- enableMarketplaceQnA %>" /><% } %>
<% if (customerQnALink) { %><Property Id="Microsoft.VisualStudio.Services.CustomerQnALink" Value="<%- customerQnALink %>" /><% } %>
<% if (typeof webExtension === 'boolean') { %><Property Id="Microsoft.VisualStudio.Code.WebExtension" Value="<%- webExtension %>" /><% } %>
</Properties>
<% if (license) { %><License><%- license %></License><% } %>
<% if (icon) { %><Icon><%- icon %></Icon><% } %>

View file

@ -30,6 +30,11 @@ export interface ICreateVSIXOptions {
* Should use Yarn instead of NPM.
*/
useYarn?: boolean;
/**
* Select the package manager to use
*/
usePackageManager?: "yarn" | "pnpm" | "npm";
}
export interface IPublishOptions {
@ -62,6 +67,11 @@ export interface IPublishOptions {
* Should use Yarn instead of NPM.
*/
useYarn?: boolean;
/**
* Select the package manager to use
*/
usePackageManager?: "yarn" | "pnpm" | "npm";
}
/**
@ -69,7 +79,8 @@ export interface IPublishOptions {
*/
export enum PackageManager {
Npm,
Yarn
Yarn,
Pnpm,
}
export interface IListFilesOptions {
@ -90,6 +101,13 @@ export interface IListFilesOptions {
* no dependencies will be included.
*/
packagedDependencies?: string[];
/**
* The location of an alternative .vscodeignore file to be used.
* The `.vscodeignore` file located at the root of the project will be taken
* instead, if none is specified.
*/
ignoreFile?: string;
}
export interface IPublishVSIXOptions {
@ -115,6 +133,11 @@ export interface IPublishVSIXOptions {
* Should use Yarn instead of NPM.
*/
useYarn?: boolean;
/**
* Select the package manager to use
*/
usePackageManager?: "yarn" | "pnpm" | "npm";
}
/**
@ -135,7 +158,7 @@ export function publish(options: IPublishOptions = {}): Promise<any> {
* Lists the files included in the extension's package.
*/
export function listFiles(options: IListFilesOptions = {}): Promise<string[]> {
return _listFiles(options.cwd, options.packageManager === PackageManager.Yarn, options.packagedDependencies);
return _listFiles(options.cwd, options.packageManager === PackageManager.Yarn, <any>({ [PackageManager.Yarn]: "yarn", [PackageManager.Npm]: "npm", [PackageManager.Pnpm]: "pnpm" }[options.packageManager]), options.packagedDependencies, options.ignoreFile);
}
/**

View file

@ -1,16 +1,18 @@
import * as program from 'commander';
import * as leven from 'leven';
import { packageCommand, ls } from './package';
import { publish, unpublish } from './publish';
import { show } from './show';
import { search } from './search';
import { listPublishers, createPublisher, deletePublisher, loginPublisher, logoutPublisher } from './store';
import { getLatestVersion } from './npm';
import { CancellationToken, isCancelledError, log } from './util';
import { CancellationToken, log } from './util';
import * as semver from 'semver';
import { isatty } from 'tty';
const pkg = require('../package.json');
function fatal<T>(message: any, ...args: any[]): void {
function fatal(message: any, ...args: any[]): void {
if (message instanceof Error) {
message = message.message;
@ -37,14 +39,14 @@ function main(task: Promise<any>): void {
if (isatty(1)) {
getLatestVersion(pkg.name, token)
.then(version => latestVersion = version)
.catch(err => !isCancelledError(err) && log.error(err));
.catch(_ => { /* noop */ });
}
task
.catch(fatal)
.then(() => {
if (latestVersion && semver.gt(latestVersion, pkg.version)) {
log.warn(`\nThe latest version of ${pkg.name} is ${latestVersion} and you have ${pkg.version}.\nUpdate it now: npm install -g ${pkg.name}`);
log.info(`\nThe latest version of ${pkg.name} is ${latestVersion} and you have ${pkg.version}.\nUpdate it now: npm install -g ${pkg.name}`);
} else {
token.cancel();
}
@ -53,40 +55,53 @@ function main(task: Promise<any>): void {
module.exports = function (argv: string[]): void {
program
.version(pkg.version);
.version(pkg.version)
.usage('<command> [options]');
program
.command('ls')
.description('Lists all the files that will be published')
.option('--yarn', 'Use yarn instead of npm')
.option('--packageManager [yarn|npm|pnpm]', 'Use yarn, npm, or pnpm package manager')
.option('--packagedDependencies <path>', 'Select packages that should be published only (includes dependencies)', (val, all) => all ? all.concat(val) : [val], undefined)
.action(({ yarn, packagedDependencies }) => main(ls(undefined, yarn, packagedDependencies)));
.option('--ignoreFile [path]', 'Indicate alternative .vscodeignore')
.action(({ yarn, packagedDependencies, ignoreFile, packageManager }) => main(ls(undefined, yarn, packagedDependencies, ignoreFile, packageManager)));
program
.command('package')
.description('Packages an extension')
.option('-o, --out [path]', 'Output .vsix extension file to [path] location')
.option('--githubBranch [branch]', 'The GitHub branch used to infer relative links in README.md. Can be overriden by --baseContentUrl and --baseImagesUrl.')
.option('--baseContentUrl [url]', 'Prepend all relative links in README.md with this url.')
.option('--baseImagesUrl [url]', 'Prepend all relative image links in README.md with this url.')
.option('--yarn', 'Use yarn instead of npm')
.action(({ out, baseContentUrl, baseImagesUrl, yarn }) => main(packageCommand({ packagePath: out, baseContentUrl, baseImagesUrl, useYarn: yarn })));
.option('--packageManager [yarn|npm|pnpm]', 'Use yarn, npm, or pnpm package manager')
.option('--ignoreFile [path]', 'Indicate alternative .vscodeignore')
.option('--noGitHubIssueLinking', 'Prevent automatic expansion of GitHub-style issue syntax into links')
.action(({ out, githubBranch, baseContentUrl, baseImagesUrl, yarn, ignoreFile, noGitHubIssueLinking, packageManager }) => main(packageCommand({ packagePath: out, githubBranch, baseContentUrl, baseImagesUrl, useYarn: yarn, ignoreFile, expandGitHubIssueLinks: noGitHubIssueLinking, usePackageManager: packageManager })));
program
.command('publish [<version>]')
.description('Publishes an extension')
.option('-p, --pat <token>', 'Personal Access Token')
.option('-p, --pat <token>', 'Personal Access Token', process.env['VSCE_PAT'])
.option('-m, --message <commit message>', 'Commit message used when calling `npm version`.')
.option('--packagePath [path]', 'Publish the VSIX package located at the specified path.')
.option('--githubBranch [branch]', 'The GitHub branch used to infer relative links in README.md. Can be overriden by --baseContentUrl and --baseImagesUrl.')
.option('--baseContentUrl [url]', 'Prepend all relative links in README.md with this url.')
.option('--baseImagesUrl [url]', 'Prepend all relative image links in README.md with this url.')
.option('--yarn', 'Use yarn instead of npm while packing extension files')
.option('--packageManager [yarn|npm|pnpm]', 'Use yarn, npm, or pnpm package manager')
.option('--noVerify')
.action((version, { pat, packagePath, baseContentUrl, baseImagesUrl, yarn, noVerify }) => main(publish({ pat, version, packagePath, baseContentUrl, baseImagesUrl, useYarn: yarn, noVerify })));
.option('--ignoreFile [path]', 'Indicate alternative .vscodeignore')
.option('--web', 'Experimental flag to enable publishing web extensions. Note: This is supported only for selected extensions.')
.action((version, { pat, message, packagePath, githubBranch, baseContentUrl, baseImagesUrl, yarn, noVerify, ignoreFile, web, packageManager }) => main(publish({ pat, commitMessage: message, version, packagePath, githubBranch, baseContentUrl, baseImagesUrl, useYarn: yarn, noVerify, ignoreFile, web, usePackageManager: packageManager })));
program
.command('unpublish [<extensionid>]')
.description('Unpublishes an extension. Example extension id: microsoft.csharp.')
.option('-p, --pat <token>', 'Personal Access Token')
.action((id, { pat }) => main(unpublish({ id, pat })));
.option('-f, --force', 'Forces Unpublished Extension')
.action((id, { pat, force }) => main(unpublish({ id, pat, force })));
program
.command('ls-publishers')
@ -126,8 +141,18 @@ module.exports = function (argv: string[]): void {
.action((text, { json }) => main(search(text, json)));
program
.command('*')
.action(() => program.help());
.command('*', '', { noHelp: true })
.action((cmd: string) => {
program.help(help => {
const availableCommands = program.commands.map(c => c._name);
const suggestion = availableCommands.find(c => leven(c, cmd) < c.length * 0.4);
help = `${help}
Unknown command '${cmd}'`;
return suggestion ? `${help}, did you mean '${suggestion}'?\n` : `${help}.\n`;
});
});
program.parse(argv);

View file

@ -21,6 +21,8 @@ export interface Contributions {
[contributionType: string]: any;
}
export type ExtensionKind = 'ui' | 'workspace' | 'web';
export interface Manifest {
// mandatory (npm)
name: string;
@ -42,6 +44,7 @@ export interface Manifest {
_testing?: string;
enableProposedApi?: boolean;
qna?: 'marketplace' | string | false;
extensionKind?: ExtensionKind | ExtensionKind[];
// optional (npm)
author?: string | Person;
@ -54,6 +57,7 @@ export interface Manifest {
license?: string;
contributors?: string | Person[];
main?: string;
browser?: string;
repository?: string | { type?: string; url?: string; };
scripts?: { [name: string]: string; };
dependencies?: { [name: string]: string; };

View file

@ -3,7 +3,7 @@ import * as fs from 'fs';
import * as cp from 'child_process';
import * as parseSemver from 'parse-semver';
import * as _ from 'lodash';
import { CancellationToken, log } from './util';
import { CancellationToken } from './util';
interface IOptions {
cwd?: string;
@ -54,7 +54,7 @@ function checkNPM(cancellationToken?: CancellationToken): Promise<void> {
function getNpmDependencies(cwd: string): Promise<string[]> {
return checkNPM()
.then(() => exec('npm list --production --parseable --depth=99999', { cwd, maxBuffer: 5000 * 1024 }))
.then(() => exec('npm list --production --parseable --depth=99999 --loglevel=error', { cwd, maxBuffer: 5000 * 1024 }))
.then(({ stdout }) => stdout
.split(/[\r\n]/)
.filter(dir => path.isAbsolute(dir)));
@ -67,25 +67,29 @@ interface YarnTreeNode {
export interface YarnDependency {
name: string;
version: string;
path: string;
children: YarnDependency[];
}
interface PnpmTreeNode {
name: string;
from: string;
version: string;
dependencies: { [key:string]:PnpmTreeNode };
}
function asYarnDependency(prefix: string, tree: YarnTreeNode, prune: boolean): YarnDependency | null {
if (prune && /@[\^~]/.test(tree.name)) {
return null;
}
let name: string, version: string;
let name: string;
try {
const parseResult = parseSemver(tree.name);
name = parseResult.name;
version = parseResult.version;
} catch (err) {
log.error('Failed to parse dependency:', tree.name);
return null;
name = tree.name.replace(/^([^@+])@.*$/, '$1');
}
const dependencyPath = path.join(prefix, name);
@ -99,7 +103,30 @@ function asYarnDependency(prefix: string, tree: YarnTreeNode, prune: boolean): Y
}
}
return { name, version, path: dependencyPath, children };
return { name, path: dependencyPath, children };
}
async function asPnpmDependency(prefix: string, tree: PnpmTreeNode, prune: boolean): Promise<YarnDependency | null> {
if (prune && /^[\^~]/.test(tree.version)) {
return null;
}
let name = tree.name || tree.from;
const dependencyPath = path.join(prefix, name);
const children: YarnDependency[] = [];
const deps = await Promise.all(
_.values(tree.dependencies || {})
.map(child => asPnpmDependency(prefix, child, prune))
);
for(const dep of deps) {
if (dep) {
children.push(dep);
}
}
return { name, path: dependencyPath, children };
}
function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: string[]): YarnDependency[] {
@ -148,6 +175,30 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st
return reached.values;
}
async function getPnpmProductionDependencies(cwd: string, packagedDependencies?: string[]): Promise<YarnDependency[]> {
const raw = await new Promise<string>((c, e) => cp.exec('pnpm list --depth 1000000 --prod --json --silent', { cwd, encoding: 'utf8', env: { ...process.env }, maxBuffer: 5000 * 1024 }, (err, stdout) => err ? e(err) : c(stdout)));
const match = /^\s*\[[\s\S]*\]\s*$/m.exec(raw);
if (!match || match.length !== 1) {
throw new Error('Could not parse result of `pnpm list --json`' + raw);
}
const usingPackagedDependencies = Array.isArray(packagedDependencies);
const trees = _.values(JSON.parse(match[0])[0].dependencies) as PnpmTreeNode[];
let result = (
await Promise.all(
trees.map(tree => asPnpmDependency(path.join(cwd, 'node_modules'), tree, !usingPackagedDependencies))
)
).filter(dep => !!dep);
if (usingPackagedDependencies) {
result = selectYarnDependencies(result, packagedDependencies);
}
return result;
}
async function getYarnProductionDependencies(cwd: string, packagedDependencies?: string[]): Promise<YarnDependency[]> {
const raw = await new Promise<string>((c, e) => cp.exec('yarn list --prod --json', { cwd, encoding: 'utf8', env: { ...process.env }, maxBuffer: 5000 * 1024 }, (err, stdout) => err ? e(err) : c(stdout)));
const match = /^{"type":"tree".*$/m.exec(raw);
@ -182,8 +233,34 @@ async function getYarnDependencies(cwd: string, packagedDependencies?: string[])
return _.uniq(result);
}
export function getDependencies(cwd: string, useYarn = false, packagedDependencies?: string[]): Promise<string[]> {
return useYarn ? getYarnDependencies(cwd, packagedDependencies) : getNpmDependencies(cwd);
async function getPnpmDependencies(cwd: string, packagedDependencies?: string[]): Promise<string[]> {
const result: string[] = [cwd];
if (await new Promise(c => fs.exists(path.join(cwd, 'pnpm-lock.yaml'), c))) {
const deps = await getPnpmProductionDependencies(cwd, packagedDependencies);
const flatten = (dep: YarnDependency) => { result.push(dep.path); dep.children.forEach(flatten); };
deps.forEach(flatten);
}
return _.uniq(result);
}
export function getDependencies(cwd: string, useYarn = false, usePackageManager: "yarn" | "npm" | "pnpm", packagedDependencies?: string[]): Promise<string[]> {
if(useYarn) {
return getYarnDependencies(cwd, packagedDependencies);
}
else if(usePackageManager == "npm") {
return getNpmDependencies(cwd);
}
else if(usePackageManager == "pnpm") {
return getPnpmDependencies(cwd, packagedDependencies);
}
else if(usePackageManager == "yarn") {
return getYarnDependencies(cwd, packagedDependencies);
}
else {
return getNpmDependencies(cwd);
}
}
export function getLatestVersion(name: string, cancellationToken?: CancellationToken): Promise<string> {

View file

@ -3,7 +3,7 @@ import * as path from 'path';
import * as cp from 'child_process';
import * as _ from 'lodash';
import * as yazl from 'yazl';
import { Manifest } from './manifest';
import { ExtensionKind, Manifest } from './manifest';
import { ITranslations, patchNLS } from './nls';
import * as util from './util';
import * as _glob from 'glob';
@ -14,18 +14,13 @@ import * as cheerio from 'cheerio';
import * as url from 'url';
import { lookup } from 'mime';
import * as urljoin from 'url-join';
import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility } from './validation';
import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility, validateVSCodeTypesCompatibility } from './validation';
import { getDependencies } from './npm';
interface IReadFile {
(filePath: string): Promise<Buffer>;
(filePath: string, encoding?: string): Promise<string>;
}
import { IExtensionsReport } from './publicgalleryapi';
const readFile = denodeify<string, string, string>(fs.readFile);
const unlink = denodeify<string, void>(fs.unlink as any);
const stat = denodeify(fs.stat);
const exec = denodeify<string, { cwd?: string; env?: any; maxBuffer?: number; }, { stdout: string; stderr: string; }>(cp.exec as any, (err, stdout, stderr) => [err, { stdout, stderr }]);
const glob = denodeify<string, _glob.IOptions, string[]>(_glob);
const resourcesPath = path.join(path.dirname(__dirname), 'resources');
@ -65,22 +60,29 @@ export interface IAsset {
export interface IPackageOptions {
cwd?: string;
packagePath?: string;
githubBranch?: string;
baseContentUrl?: string;
baseImagesUrl?: string;
useYarn?: boolean;
usePackageManager?: "yarn" | "npm" | "pnpm";
dependencyEntryPoints?: string[];
ignoreFile?: string;
expandGitHubIssueLinks?: boolean;
web?: boolean;
}
export interface IProcessor {
onFile(file: IFile): Promise<IFile>;
onEnd(): Promise<void>;
assets: IAsset[];
tags: string[];
vsix: any;
}
export class BaseProcessor implements IProcessor {
constructor(protected manifest: Manifest) { }
assets: IAsset[] = [];
tags: string[] = [];
vsix: any = Object.create(null);
onFile(file: IFile): Promise<IFile> { return Promise.resolve(file); }
onEnd() { return Promise.resolve(null); }
@ -135,6 +137,7 @@ const TrustedSVGSources = [
'api.travis-ci.com',
'api.travis-ci.org',
'app.fossa.io',
'badge.buildkite.com',
'badge.fury.io',
'badge.waffle.io',
'badgen.net',
@ -153,6 +156,7 @@ const TrustedSVGSources = [
'david-dm.org',
'deepscan.io',
'dev.azure.com',
'docs.rs',
'flat.badgen.net',
'gemnasium.com',
'githost.io',
@ -173,8 +177,16 @@ const TrustedSVGSources = [
'www.versioneye.com'
];
function isHostTrusted(host: string): boolean {
return TrustedSVGSources.indexOf(host.toLowerCase()) > -1;
function isGitHubRepository(repository: string): boolean {
return /^https:\/\/github\.com\/|^git@github\.com:/.test(repository || '');
}
function isGitHubBadge(href: string): boolean {
return /^https:\/\/github\.com\/[^/]+\/[^/]+\/workflows\/.*badge\.svg/.test(href || '');
}
function isHostTrusted(url: url.UrlWithStringQuery): boolean {
return TrustedSVGSources.indexOf(url.host.toLowerCase()) > -1 || isGitHubBadge(url.href);
}
class ManifestProcessor extends BaseProcessor {
@ -189,7 +201,7 @@ class ManifestProcessor extends BaseProcessor {
}
const repository = getRepositoryUrl(manifest.repository);
const isGitHub = /^https:\/\/github\.com\/|^git@github\.com:/.test(repository || '');
const isGitHub = isGitHubRepository(repository);
let enableMarketplaceQnA: boolean | undefined;
let customerQnALink: string | undefined;
@ -202,6 +214,8 @@ class ManifestProcessor extends BaseProcessor {
enableMarketplaceQnA = false;
}
const extensionKind = getExtensionKind(manifest);
this.vsix = {
...this.vsix,
id: manifest.name,
@ -224,6 +238,7 @@ class ManifestProcessor extends BaseProcessor {
customerQnALink,
extensionDependencies: _(manifest.extensionDependencies || []).uniq().join(','),
extensionPack: _(manifest.extensionPack || []).uniq().join(','),
extensionKind: extensionKind.join(','),
localizedLanguages: (manifest.contributes && manifest.contributes.localizations) ?
manifest.contributes.localizations.map(loc => loc.localizedLanguageName || loc.languageName || loc.languageId).join(',') : ''
};
@ -234,6 +249,10 @@ class ManifestProcessor extends BaseProcessor {
}
async onEnd(): Promise<void> {
if (typeof this.manifest.extensionKind === 'string') {
util.log.warn(`The 'extensionKind' property should be of type 'string[]'. Learn more at: https://aka.ms/vscode/api/incorrect-execution-location`);
}
if (this.manifest.publisher === 'vscode-samples') {
throw new Error('It\'s not allowed to use the \'vscode-samples\' publisher. Learn more at: https://code.visualstudio.com/api/working-with-extensions/publishing-extension.');
}
@ -335,10 +354,10 @@ export class TagsProcessor extends BaseProcessor {
...descriptionKeywords
];
this.vsix.tags = _(tags)
this.tags = _(tags)
.uniq() // deduplicate
.compact() // remove falsey values
.join(',');
.value();
return Promise.resolve(null);
}
@ -348,14 +367,20 @@ export class MarkdownProcessor extends BaseProcessor {
private baseContentUrl: string;
private baseImagesUrl: string;
private isGitHub: boolean;
private repositoryUrl: string;
private expandGitHubIssueLinks: boolean;
constructor(manifest: Manifest, private name: string, private regexp: RegExp, private assetType: string, options: IPackageOptions = {}) {
super(manifest);
const guess = this.guessBaseUrls();
const guess = this.guessBaseUrls(options.githubBranch);
this.baseContentUrl = options.baseContentUrl || (guess && guess.content);
this.baseImagesUrl = options.baseImagesUrl || options.baseContentUrl || (guess && guess.images);
this.repositoryUrl = (guess && guess.repository);
this.isGitHub = isGitHubRepository(this.repositoryUrl);
this.expandGitHubIssueLinks = typeof options.expandGitHubIssueLinks === 'boolean' ? options.expandGitHubIssueLinks : true;
}
async onFile(file: IFile): Promise<IFile> {
@ -370,11 +395,15 @@ export class MarkdownProcessor extends BaseProcessor {
let contents = await read(file);
if (/This is the README for your extension /.test(contents)) {
throw new Error(`Make sure to edit the README.md file before you publish your extension.`);
throw new Error(`Make sure to edit the README.md file before you package or publish your extension.`);
}
const markdownPathRegex = /(!?)\[([^\]\[]*|!\[[^\]\[]*]\([^\)]+\))\]\(([^\)]+)\)/g;
const urlReplace = (all, isImage, title, link) => {
const urlReplace = (_, isImage, title, link: string) => {
if (/^mailto:/i.test(link)) {
return `${isImage}[${title}](${link})`;
}
const isLinkRelative = !/^\w+:\/\//.test(link) && link[0] !== '#';
if (!this.baseContentUrl && !this.baseImagesUrl) {
@ -395,8 +424,52 @@ export class MarkdownProcessor extends BaseProcessor {
return `${isImage}[${title}](${urljoin(prefix, link)})`;
};
// Replace Markdown links with urls
contents = contents.replace(markdownPathRegex, urlReplace);
// Replace <img> links with urls
contents = contents.replace(/<img.+?src=["']([/.\w\s-]+)['"].*?>/g, (all, link) => {
const isLinkRelative = !/^\w+:\/\//.test(link) && link[0] !== '#';
if (!this.baseImagesUrl && isLinkRelative) {
throw new Error(`Couldn't detect the repository where this extension is published. The image will be broken in ${this.name}. Please provide the repository URL in package.json or use the --baseContentUrl and --baseImagesUrl options.`);
}
const prefix = this.baseImagesUrl;
if (!prefix || !isLinkRelative) {
return all;
}
return all.replace(link, urljoin(prefix, link));
});
if (this.isGitHub && this.expandGitHubIssueLinks) {
const markdownIssueRegex = /(\s|\n)([\w\d_-]+\/[\w\d_-]+)?#(\d+)\b/g
const issueReplace = (all: string, prefix: string, ownerAndRepositoryName: string, issueNumber: string): string => {
let result = all;
let owner: string;
let repositoryName: string;
if (ownerAndRepositoryName) {
[owner, repositoryName] = ownerAndRepositoryName.split('/', 2);
}
if (owner && repositoryName && issueNumber) {
// Issue in external repository
const issueUrl = urljoin('https://github.com', owner, repositoryName, 'issues', issueNumber);
result = prefix + `[${owner}/${repositoryName}#${issueNumber}](${issueUrl})`;
} else if (!owner && !repositoryName && issueNumber) {
// Issue in own repository
result = prefix + `[#${issueNumber}](${urljoin(this.repositoryUrl, 'issues', issueNumber)})`;
}
return result;
}
// Replace Markdown issue references with urls
contents = contents.replace(markdownIssueRegex, issueReplace);
}
const html = markdownit({ html: true }).render(contents);
const $ = cheerio.load(html);
@ -412,23 +485,23 @@ export class MarkdownProcessor extends BaseProcessor {
throw new Error(`Images in ${this.name} must come from an HTTPS source: ${src}`);
}
if (/\.svg$/i.test(srcUrl.pathname) && !isHostTrusted(srcUrl.host)) {
if (/\.svg$/i.test(srcUrl.pathname) && (!isHostTrusted(srcUrl))) {
throw new Error(`SVGs are restricted in ${this.name}; please use other file image formats, such as PNG: ${src}`);
}
});
$('svg').each((_, svg) => {
$('svg').each(() => {
throw new Error(`SVG tags are not allowed in ${this.name}.`);
});
return {
path: file.path,
contents: new Buffer(contents)
contents: Buffer.from(contents, 'utf8')
};
}
// GitHub heuristics
private guessBaseUrls(): { content: string; images: string; } {
private guessBaseUrls(githubBranch: string | undefined): { content: string; images: string; repository: string } {
let repository = null;
if (typeof this.manifest.repository === 'string') {
@ -450,10 +523,12 @@ export class MarkdownProcessor extends BaseProcessor {
const account = match[1];
const repositoryName = match[2].replace(/\.git$/i, '');
const branchName = githubBranch ? githubBranch : 'master';
return {
content: `https://github.com/${account}/${repositoryName}/blob/master`,
images: `https://github.com/${account}/${repositoryName}/raw/master`
content: `https://github.com/${account}/${repositoryName}/blob/${branchName}`,
images: `https://github.com/${account}/${repositoryName}/raw/${branchName}`,
repository: `https://github.com/${account}/${repositoryName}`
};
}
}
@ -474,7 +549,7 @@ export class ChangelogProcessor extends MarkdownProcessor {
class LicenseProcessor extends BaseProcessor {
private didFindLicense = false;
private filter: (name: string) => boolean;
filter: (name: string) => boolean;
constructor(manifest: Manifest) {
super(manifest);
@ -542,6 +617,92 @@ class IconProcessor extends BaseProcessor {
}
}
export function isSupportedWebExtension(manifest: Manifest, extensionsReport: IExtensionsReport): boolean {
const id = `${manifest.publisher}.${manifest.name}`;
return extensionsReport.web.publishers.some(publisher => manifest.publisher === publisher)
|| extensionsReport.web.extensions.some(extension => extension === id);
}
export function isWebKind(manifest: Manifest): boolean {
const extensionKind = getExtensionKind(manifest);
return extensionKind.some(kind => kind === 'web');
}
const workspaceExtensionPoints: string[] = ['terminal', 'debuggers', 'jsonValidation'];
function getExtensionKind(manifest: Manifest): ExtensionKind[] {
// check the manifest
if (manifest.extensionKind) {
return Array.isArray(manifest.extensionKind)
? manifest.extensionKind
: manifest.extensionKind === 'ui' ? ['ui', 'workspace'] : [manifest.extensionKind]
}
// Not an UI extension if it has main
if (manifest.main) {
if (manifest.browser) {
return ['workspace', 'web'];
}
return ['workspace'];
}
if (manifest.browser) {
return ['web'];
}
const isNonEmptyArray = obj => Array.isArray(obj) && obj.length > 0;
// Not an UI nor web extension if it has dependencies or an extension pack
if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) {
return ['workspace'];
}
if (manifest.contributes) {
// Not an UI nor web extension if it has workspace contributions
for (const contribution of Object.keys(manifest.contributes)) {
if (workspaceExtensionPoints.indexOf(contribution) !== -1) {
return ['workspace'];
}
}
}
return ['ui', 'workspace', 'web'];
}
export class WebExtensionProcessor extends BaseProcessor {
private readonly isWebKind: boolean = false;
constructor(manifest: Manifest, options: IPackageOptions) {
super(manifest);
this.isWebKind = options.web && isWebKind(manifest);
}
onFile(file: IFile): Promise<IFile> {
if (this.isWebKind) {
const path = util.normalize(file.path);
if (/\.svg$/i.test(path)) {
throw new Error(`SVGs can't be used in a web extension: ${path}`);
}
this.assets.push({ type: `Microsoft.VisualStudio.Code.WebResources/${path}`, path });
}
return Promise.resolve(file);
}
async onEnd(): Promise<void> {
if (this.assets.length > 25) {
throw new Error('Cannot pack more than 25 files in a web extension. Use `vsce ls` to see all the files that will be packed and exclude those which are not needed in .vscodeignore.');
}
if (this.isWebKind) {
this.vsix = {
...this.vsix,
webExtension: true
}
this.tags = ['__web_extension'];
}
}
}
export class NLSProcessor extends BaseProcessor {
private translations: { [path: string]: string } = Object.create(null);
@ -584,6 +745,42 @@ export class NLSProcessor extends BaseProcessor {
}
}
export class ValidationProcessor extends BaseProcessor {
private files = new Map<string, string[]>();
private duplicates = new Set<string>();
async onFile(file: IFile): Promise<IFile> {
const lower = file.path.toLowerCase();
const existing = this.files.get(lower);
if (existing) {
this.duplicates.add(lower);
existing.push(file.path);
} else {
this.files.set(lower, [file.path]);
}
return file;
}
async onEnd() {
if (this.duplicates.size === 0) {
return;
}
const messages = [`The following files have the same case insensitive path, which isn't supported by the VSIX format:`];
for (const lower of this.duplicates) {
for (const filePath of this.files.get(lower)) {
messages.push(` - ${filePath}`);
}
}
throw new Error(messages.join('\n'));
}
}
export function validateManifest(manifest: Manifest): Manifest {
validatePublisher(manifest.publisher);
validateExtensionName(manifest.name);
@ -604,6 +801,10 @@ export function validateManifest(manifest: Manifest): Manifest {
validateEngineCompatibility(manifest.engines['vscode']);
if (manifest.devDependencies && manifest.devDependencies['@types/vscode']) {
validateVSCodeTypesCompatibility(manifest.engines['vscode'], manifest.devDependencies['@types/vscode']);
}
if (/\.svg$/i.test(manifest.icon || '')) {
throw new Error(`SVGs can't be used as icons: ${manifest.icon}`);
}
@ -616,7 +817,7 @@ export function validateManifest(manifest: Manifest): Manifest {
throw new Error(`Badge URLs must come from an HTTPS source: ${badge.url}`);
}
if (/\.svg$/i.test(srcUrl.pathname) && !isHostTrusted(srcUrl.host)) {
if (/\.svg$/i.test(srcUrl.pathname) && (!isHostTrusted(srcUrl))) {
throw new Error(`Badge SVGs are restricted. Please use other file image formats, such as PNG: ${badge.url}`);
}
});
@ -665,7 +866,7 @@ export function readManifest(cwd = process.cwd(), nls = true): Promise<Manifest>
}
export function toVsixManifest(assets: IAsset[], vsix: any, options: IPackageOptions = {}): Promise<string> {
export function toVsixManifest(vsix: any): Promise<string> {
return readFile(vsixManifestTemplatePath, 'utf8')
.then(vsixManifestTemplateStr => _.template(vsixManifestTemplateStr))
.then(vsixManifestTemplate => vsixManifestTemplate(vsix));
@ -719,10 +920,10 @@ const defaultIgnore = [
'**/.vscode-test/**'
];
function collectAllFiles(cwd: string, useYarn = false, dependencyEntryPoints?: string[]): Promise<string[]> {
return getDependencies(cwd, useYarn, dependencyEntryPoints).then(deps => {
function collectAllFiles(cwd: string, useYarn = false, usePackageManager: "yarn" | "npm" | "pnpm" = "npm", dependencyEntryPoints?: string[]): Promise<string[]> {
return getDependencies(cwd, useYarn, usePackageManager, dependencyEntryPoints).then(deps => {
const promises: Promise<string[]>[] = deps.map(dep => {
return glob('**', { cwd: dep, nodir: true, dot: true, ignore: 'node_modules/**' })
return glob('**', { cwd: dep, nodir: true, dot: true, follow: usePackageManager == "pnpm", ignore: 'node_modules/**' })
.then(files => files
.map(f => path.relative(cwd, path.join(dep, f)))
.map(f => f.replace(/\\/g, '/')));
@ -732,12 +933,12 @@ function collectAllFiles(cwd: string, useYarn = false, dependencyEntryPoints?: s
});
}
function collectFiles(cwd: string, useYarn = false, dependencyEntryPoints?: string[]): Promise<string[]> {
return collectAllFiles(cwd, useYarn, dependencyEntryPoints).then(files => {
function collectFiles(cwd: string, useYarn = false, usePackageManager: "yarn" | "npm" | "pnpm" = "npm", dependencyEntryPoints?: string[], ignoreFile?: string): Promise<string[]> {
return collectAllFiles(cwd, useYarn, usePackageManager, dependencyEntryPoints).then(files => {
files = files.filter(f => !/\r$/m.test(f));
return readFile(path.join(cwd, '.vscodeignore'), 'utf8')
.catch<string>(err => err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve(''))
return readFile(ignoreFile ? ignoreFile : path.join(cwd, '.vscodeignore'), 'utf8')
.catch<string>(err => err.code !== 'ENOENT' ? Promise.reject(err) : ignoreFile ? Promise.reject(err) : Promise.resolve(''))
// Parse raw ignore by splitting output into lines and filtering out empty lines and comments
.then(rawIgnore => rawIgnore.split(/[\n\r]/).map(s => s.trim()).filter(s => !!s).filter(i => !/^\s*#/.test(i)))
@ -757,18 +958,22 @@ function collectFiles(cwd: string, useYarn = false, dependencyEntryPoints?: stri
});
}
export function processFiles(processors: IProcessor[], files: IFile[], options: IPackageOptions = {}): Promise<IFile[]> {
export function processFiles(processors: IProcessor[], files: IFile[]): Promise<IFile[]> {
const processedFiles = files.map(file => util.chain(file, processors, (file, processor) => processor.onFile(file)));
return Promise.all(processedFiles).then(files => {
return util.sequence(processors.map(p => () => p.onEnd())).then(() => {
const assets = _.flatten(processors.map(p => p.assets));
const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets });
const tags = _(_.flatten(processors.map(p => p.tags)))
.uniq() // deduplicate
.compact() // remove falsey values
.join(',');
const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets, tags });
return Promise.all([toVsixManifest(assets, vsix, options), toContentTypes(files)]).then(result => {
return Promise.all([toVsixManifest(vsix), toContentTypes(files)]).then(result => {
return [
{ path: 'extension.vsixmanifest', contents: new Buffer(result[0], 'utf8') },
{ path: '[Content_Types].xml', contents: new Buffer(result[1], 'utf8') },
{ path: 'extension.vsixmanifest', contents: Buffer.from(result[0], 'utf8') },
{ path: '[Content_Types].xml', contents: Buffer.from(result[1], 'utf8') },
...files
];
});
@ -784,29 +989,33 @@ export function createDefaultProcessors(manifest: Manifest, options: IPackageOpt
new ChangelogProcessor(manifest, options),
new LicenseProcessor(manifest),
new IconProcessor(manifest),
new NLSProcessor(manifest)
new NLSProcessor(manifest),
new WebExtensionProcessor(manifest, options),
new ValidationProcessor(manifest)
];
}
export function collect(manifest: Manifest, options: IPackageOptions = {}): Promise<IFile[]> {
const cwd = options.cwd || process.cwd();
const useYarn = options.useYarn || false;
const usePackageManager = options.usePackageManager || "npm";
const packagedDependencies = options.dependencyEntryPoints || undefined;
const ignoreFile = options.ignoreFile || undefined;
const processors = createDefaultProcessors(manifest, options);
return collectFiles(cwd, useYarn, packagedDependencies).then(fileNames => {
return collectFiles(cwd, useYarn, usePackageManager, packagedDependencies, ignoreFile).then(fileNames => {
const files = fileNames.map(f => ({ path: `extension/${f}`, localPath: path.join(cwd, f) }));
return processFiles(processors, files, options);
return processFiles(processors, files);
});
}
function writeVsix(files: IFile[], packagePath: string): Promise<string> {
function writeVsix(files: IFile[], packagePath: string): Promise<void> {
return unlink(packagePath)
.catch(err => err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve(null))
.then(() => new Promise<string>((c, e) => {
.then(() => new Promise((c, e) => {
const zip = new yazl.ZipFile();
files.forEach(f => f.contents ? zip.addBuffer(typeof f.contents === 'string' ? new Buffer(f.contents, 'utf8') : f.contents, f.path) : zip.addFile(f.localPath, f.path));
files.forEach(f => f.contents ? zip.addBuffer(typeof f.contents === 'string' ? Buffer.from(f.contents, 'utf8') : f.contents, f.path) : zip.addFile(f.localPath, f.path));
zip.end();
const zipStream = fs.createWriteStream(packagePath);
@ -814,41 +1023,76 @@ function writeVsix(files: IFile[], packagePath: string): Promise<string> {
zip.outputStream.once('error', e);
zipStream.once('error', e);
zipStream.once('finish', () => c(packagePath));
zipStream.once('finish', () => c());
}));
}
function defaultPackagePath(cwd: string, manifest: Manifest): string {
return path.join(cwd, `${manifest.name}-${manifest.version}.vsix`);
function getDefaultPackageName(manifest: Manifest): string {
return `${manifest.name}-${manifest.version}.vsix`;
}
function prepublish(cwd: string, manifest: Manifest): Promise<Manifest> {
async function prepublish(cwd: string, manifest: Manifest, useYarn: boolean = false, usePackageManager: "yarn" | "npm" | "pnpm" = "npm"): Promise<void> {
if (!manifest.scripts || !manifest.scripts['vscode:prepublish']) {
return Promise.resolve(manifest);
return;
}
console.warn(`Executing prepublish script 'npm run vscode:prepublish'...`);
let tool = 'npm';
if(useYarn) {
tool = 'yarn'
}
else if(usePackageManager == "npm") {
tool = 'npm';
}
else if(usePackageManager == "pnpm") {
tool = 'pnpm';
}
else if(usePackageManager == "yarn") {
tool = 'yarn'
}
return exec('npm run vscode:prepublish', { cwd, maxBuffer: 5000 * 1024 })
.then(({ stdout, stderr }) => {
process.stdout.write(stdout);
process.stderr.write(stderr);
return Promise.resolve(manifest);
})
.catch(err => Promise.reject(err.message));
console.log(`Executing prepublish script '${tool} run vscode:prepublish'...`);
await new Promise((c, e) => {
const child = cp.spawn(tool, ['run', 'vscode:prepublish'], { cwd, shell: true, stdio: 'inherit' });
child.on('exit', code => code === 0 ? c() : e(`${tool} failed with exit code ${code}`));
child.on('error', e);
});
}
async function getPackagePath(cwd: string, manifest: Manifest, options: IPackageOptions = {}): Promise<string> {
if (!options.packagePath) {
return path.join(cwd, getDefaultPackageName(manifest));
}
try {
const _stat = await stat(options.packagePath);
if (_stat.isDirectory()) {
return path.join(options.packagePath, getDefaultPackageName(manifest));
} else {
return options.packagePath;
}
} catch {
return options.packagePath;
}
}
export async function pack(options: IPackageOptions = {}): Promise<IPackageResult> {
const cwd = options.cwd || process.cwd();
let manifest = await readManifest(cwd);
manifest = await prepublish(cwd, manifest);
const manifest = await readManifest(cwd);
await prepublish(cwd, manifest, options.useYarn, options.usePackageManager);
const files = await collect(manifest, options);
if (files.length > 100) {
console.log(`This extension consists of ${files.length} separate files. For performance reasons, you should bundle your extension: https://aka.ms/vscode-bundle-extension`);
const jsFiles = files.filter(f => /\.js$/i.test(f.path));
if (files.length > 5000 || jsFiles.length > 100) {
console.log(`This extension consists of ${files.length} files, out of which ${jsFiles.length} are JavaScript files. For performance reasons, you should bundle your extension: https://aka.ms/vscode-bundle-extension . You should also exclude unnecessary files by adding them to your .vscodeignore: https://aka.ms/vscode-vscodeignore`);
}
const packagePath = await writeVsix(files, path.resolve(options.packagePath || defaultPackagePath(cwd, manifest)));
const packagePath = await getPackagePath(cwd, manifest, options);
await writeVsix(files, path.resolve(packagePath));
return { manifest, packagePath, files };
}
@ -874,17 +1118,17 @@ export async function packageCommand(options: IPackageOptions = {}): Promise<any
/**
* Lists the files included in the extension's package. Does not run prepublish.
*/
export function listFiles(cwd = process.cwd(), useYarn = false, packagedDependencies?: string[]): Promise<string[]> {
export function listFiles(cwd = process.cwd(), useYarn = false, usePackageManager: "yarn" | "npm" | "pnpm" = "npm", packagedDependencies?: string[], ignoreFile?: string): Promise<string[]> {
return readManifest(cwd)
.then(manifest => collectFiles(cwd, useYarn, packagedDependencies));
.then(() => collectFiles(cwd, useYarn, usePackageManager, packagedDependencies, ignoreFile));
}
/**
* Lists the files included in the extension's package. Runs prepublish.
*/
export function ls(cwd = process.cwd(), useYarn = false, packagedDependencies?: string[]): Promise<void> {
export function ls(cwd = process.cwd(), useYarn = false, usePackageManager?: "yarn" | "npm" | "pnpm", packagedDependencies?: string[], ignoreFile?: string): Promise<void> {
return readManifest(cwd)
.then(manifest => prepublish(cwd, manifest))
.then(manifest => collectFiles(cwd, useYarn, packagedDependencies))
.then(manifest => prepublish(cwd, manifest, useYarn, usePackageManager))
.then(() => collectFiles(cwd, useYarn, usePackageManager, packagedDependencies, ignoreFile))
.then(files => files.forEach(f => console.log(`${f}`)));
}

View file

@ -1,65 +1,66 @@
import { HttpClient, HttpClientResponse } from 'typed-rest-client/HttpClient';
import {
PublishedExtension, ExtensionQueryFlags, FilterCriteria, SortOrderType,
SortByType, ExtensionQueryFilterType, TypeInfo
} from 'azure-devops-node-api/interfaces/GalleryInterfaces';
import { PublishedExtension, ExtensionQueryFlags, FilterCriteria, ExtensionQueryFilterType, TypeInfo } from 'azure-devops-node-api/interfaces/GalleryInterfaces';
import { IHeaders } from 'azure-devops-node-api/interfaces/common/VsoBaseInterfaces';
import { ContractSerializer } from 'azure-devops-node-api/Serialization';
export interface ExtensionQuery {
pageNumber?: number;
pageSize?: number;
sortBy?: SortByType;
sortOrder?: SortOrderType;
flags?: ExtensionQueryFlags[];
criteria?: FilterCriteria[];
assetTypes?: string[];
readonly pageNumber?: number;
readonly pageSize?: number;
readonly flags?: ExtensionQueryFlags[];
readonly criteria?: FilterCriteria[];
readonly assetTypes?: string[];
}
export interface IExtensionsReport {
malicious: string[];
web: {
publishers: string[],
extensions: string[],
};
}
export class PublicGalleryAPI {
client: HttpClient;
constructor(public baseUrl: string, public apiVersion = '3.0-preview.1') {
this.client = new HttpClient('vsce');
}
private readonly extensionsReportUrl = 'https://az764295.vo.msecnd.net/extensions/marketplace.json';
private readonly client = new HttpClient('vsce');
constructor(private baseUrl: string, private apiVersion = '3.0-preview.1') { }
private post(url: string, data: string, additionalHeaders?: IHeaders): Promise<HttpClientResponse> {
return this.client.post(`${this.baseUrl}/_apis/public${url}`, data, additionalHeaders);
}
extensionQuery({
async extensionQuery({
pageNumber = 1,
pageSize = 1,
sortBy = SortByType.Relevance,
sortOrder = SortOrderType.Default,
flags = [],
criteria = [],
assetTypes = [],
}: ExtensionQuery): Promise<PublishedExtension[]> {
return this.post('/gallery/extensionquery', JSON.stringify({
const data = JSON.stringify({
filters: [{ pageNumber, pageSize, criteria }],
assetTypes,
flags: flags.reduce((memo, flag) => memo | flag, 0)
}), {
Accept: `application/json;api-version=${this.apiVersion}`,
'Content-Type': 'application/json',
})
.then(res => res.readBody())
.then(data => JSON.parse(data))
.then(({ results: [result = {}] = [] }) => result)
.then(({ extensions = [] }) =>
ContractSerializer.deserialize(extensions, TypeInfo.PublishedExtension, false, false)
);
});
const res = await this.post('/gallery/extensionquery', data, { Accept: `application/json;api-version=${this.apiVersion}`, 'Content-Type': 'application/json', });
const raw = JSON.parse(await res.readBody());
return ContractSerializer.deserialize(raw.results[0].extensions, TypeInfo.PublishedExtension, false, false);
}
getExtension(extensionId: string, flags: ExtensionQueryFlags[] = []): Promise<PublishedExtension> {
return this.extensionQuery({
criteria: [{ filterType: ExtensionQueryFilterType.Name, value: extensionId }],
flags,
})
.then(result => result.filter(({ publisher: { publisherName }, extensionName }) =>
extensionId.toLowerCase() === `${publisherName}.${extensionName}`.toLowerCase())
)
.then(([extension]) => extension);
async getExtension(extensionId: string, flags: ExtensionQueryFlags[] = []): Promise<PublishedExtension> {
const query = { criteria: [{ filterType: ExtensionQueryFilterType.Name, value: extensionId }], flags, };
const extensions = await this.extensionQuery(query);
return extensions.filter(({ publisher: { publisherName: publisher }, extensionName: name }) => extensionId.toLowerCase() === `${publisher}.${name}`.toLowerCase())[0];
}
async getExtensionsReport(): Promise<IExtensionsReport> {
const res = await this.client.get(this.extensionsReportUrl);
const raw = <Partial<IExtensionsReport>>JSON.parse(await res.readBody());
return {
malicious: raw.malicious || [],
web: raw.web || { publishers: [], extensions: [] }
}
}
}

View file

@ -1,10 +1,9 @@
import * as fs from 'fs';
import { ExtensionQueryFlags, PublishedExtension, ExtensionQueryFilterType, PagingDirection, SortByType, SortOrderType } from 'azure-devops-node-api/interfaces/GalleryInterfaces';
import { pack, readManifest, IPackage } from './package';
import { ExtensionQueryFlags, PublishedExtension } from 'azure-devops-node-api/interfaces/GalleryInterfaces';
import { pack, readManifest, IPackage, isWebKind, isSupportedWebExtension } from './package';
import * as tmp from 'tmp';
import { getPublisher } from './store';
import { getGalleryAPI, read, getPublishedUrl, log } from './util';
import { validatePublisher } from './validation';
import { getGalleryAPI, read, getPublishedUrl, log, getPublicGalleryAPI } from './util';
import { Manifest } from './manifest';
import * as denodeify from 'denodeify';
import * as yauzl from 'yauzl';
@ -74,7 +73,7 @@ async function _publish(packagePath: string, pat: string, manifest: Manifest): P
return promise
.catch(err => Promise.reject(err.statusCode === 409 ? `${fullName} already exists.` : err))
.then(() => log.done(`Published ${fullName}\nYour extension will live at ${getPublishedUrl(name)} (might take a few seconds for it to show up).`));
.then(() => log.done(`Published ${fullName}\nYour extension will live at ${getPublishedUrl(name)} (might take a few minutes for it to show up).`));
})
.catch(err => {
const message = err && err.message || '';
@ -90,19 +89,30 @@ async function _publish(packagePath: string, pat: string, manifest: Manifest): P
export interface IPublishOptions {
packagePath?: string;
version?: string;
commitMessage?: string;
cwd?: string;
pat?: string;
githubBranch?: string;
baseContentUrl?: string;
baseImagesUrl?: string;
useYarn?: boolean;
usePackageManager?: "yarn" | "npm" | "pnpm";
noVerify?: boolean;
ignoreFile?: string;
web?: boolean;
}
function versionBump(cwd: string = process.cwd(), version?: string): Promise<void> {
async function versionBump(cwd: string = process.cwd(), version?: string, commitMessage?: string): Promise<void> {
if (!version) {
return Promise.resolve(null);
}
const manifest = await readManifest(cwd);
if (manifest.version === version) {
return null;
}
switch (version) {
case 'major':
case 'minor':
@ -120,14 +130,21 @@ function versionBump(cwd: string = process.cwd(), version?: string): Promise<voi
}
}
// call `npm version` to do our dirty work
return exec(`npm version ${version}`, { cwd })
.then(({ stdout, stderr }) => {
process.stdout.write(stdout);
process.stderr.write(stderr);
return Promise.resolve(null);
})
.catch(err => Promise.reject(err.message));
let command = `npm version ${version}`;
if (commitMessage) {
command = `${command} -m "${commitMessage}"`;
}
try {
// call `npm version` to do our dirty work
const { stdout, stderr } = await exec(command, { cwd });
process.stdout.write(stdout);
process.stderr.write(stderr);
return null;
} catch (err) {
throw err.message;
}
}
export function publish(options: IPublishOptions = {}): Promise<any> {
@ -137,25 +154,42 @@ export function publish(options: IPublishOptions = {}): Promise<any> {
if (options.version) {
return Promise.reject(`Not supported: packagePath and version.`);
}
if (options.web) {
return Promise.reject(`Not supported: packagePath and web.`);
}
promise = readManifestFromPackage(options.packagePath)
.then(manifest => ({ manifest, packagePath: options.packagePath }));
} else {
const cwd = options.cwd;
const githubBranch = options.githubBranch;
const baseContentUrl = options.baseContentUrl;
const baseImagesUrl = options.baseImagesUrl;
const useYarn = options.useYarn;
const ignoreFile = options.ignoreFile;
const web = options.web;
const usePackageManager = options.usePackageManager;
promise = versionBump(options.cwd, options.version)
promise = versionBump(options.cwd, options.version, options.commitMessage)
.then(() => tmpName())
.then(packagePath => pack({ packagePath, cwd, baseContentUrl, baseImagesUrl, useYarn }));
.then(packagePath => pack({ packagePath, cwd, githubBranch, baseContentUrl, baseImagesUrl, useYarn, ignoreFile, web, usePackageManager }));
}
return promise.then(({ manifest, packagePath }) => {
return promise.then(async ({ manifest, packagePath }) => {
if (!options.noVerify && manifest.enableProposedApi) {
throw new Error('Extensions using proposed API (enableProposedApi: true) can\'t be published to the Marketplace');
}
if (options.web) {
if (!isWebKind(manifest)) {
throw new Error('Extensions which are not web kind can\'t be published to the Marketpalce as a web extension');
}
const extensionsReport = await getPublicGalleryAPI().getExtensionsReport();
if (!isSupportedWebExtension(manifest, extensionsReport)) {
throw new Error('Extensions which are not supported can\'t be published to the Marketpalce as a web extension');
}
}
const patPromise = options.pat
? Promise.resolve(options.pat)
: getPublisher(manifest.publisher).then(p => p.pat);
@ -166,29 +200,33 @@ export function publish(options: IPublishOptions = {}): Promise<any> {
export interface IUnpublishOptions extends IPublishOptions {
id?: string;
force?: boolean;
}
export function unpublish(options: IUnpublishOptions = {}): Promise<any> {
let promise: Promise<{ publisher: string; name: string; }>;
export async function unpublish(options: IUnpublishOptions = {}): Promise<any> {
let publisher: string, name: string;
if (options.id) {
const [publisher, name] = options.id.split('.');
promise = Promise.resolve(({ publisher, name }));
[publisher, name] = options.id.split('.');
} else {
promise = readManifest(options.cwd);
const manifest = await readManifest(options.cwd);
publisher = manifest.publisher;
name = manifest.name;
}
return promise.then(({ publisher, name }) => {
const fullName = `${publisher}.${name}`;
const pat = options.pat
? Promise.resolve(options.pat)
: getPublisher(publisher).then(p => p.pat);
const fullName = `${publisher}.${name}`;
return read(`This will FOREVER delete '${fullName}'! Are you sure? [y/N] `)
.then(answer => /^y$/i.test(answer) ? null : Promise.reject('Aborted'))
.then(() => pat)
.then(getGalleryAPI)
.then(api => api.deleteExtension(publisher, name))
.then(() => log.done(`Deleted extension: ${fullName}!`));
});
if (!options.force) {
const answer = await read(`This will FOREVER delete '${fullName}'! Are you sure? [y/N] `);
if (!/^y$/i.test(answer)) {
throw new Error('Aborted');
}
}
const pat = options.pat || (await getPublisher(publisher).then(p => p.pat));
const api = await getGalleryAPI(pat);
await api.deleteExtension(publisher, name);
log.done(`Deleted extension: ${fullName}!`);
}

View file

@ -4,7 +4,7 @@ import { tableView, wordTrim } from './viewutils';
const pageSize = 100;
export async function search(searchText: string, json: boolean = false, pageNumber: number = 1): Promise<any> {
export async function search(searchText: string, json: boolean = false): Promise<any> {
const flags = [];
const api = getPublicGalleryAPI();
const results = await api.extensionQuery({

View file

@ -6,7 +6,7 @@ import { validatePublisher } from './validation';
import * as denodeify from 'denodeify';
const readFile = denodeify<string, string, string>(fs.readFile);
const writeFile = denodeify<string, string, void>(fs.writeFile as any);
const writeFile = denodeify<string, string, object, void>(fs.writeFile as any);
const storePath = path.join(home(), '.vsce');
export interface IPublisher {
@ -40,7 +40,7 @@ function load(): Promise<IStore> {
}
function save(store: IStore): Promise<IStore> {
return writeFile(storePath, JSON.stringify(store))
return writeFile(storePath, JSON.stringify(store), {mode: '0600'})
.then(() => store);
}

View file

@ -0,0 +1,48 @@
# README
>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file.
This README covers off:
* [Functionality](#functionality)
* [Install](#install)
* [Run and Configure](#run-and-configure)
* [Known Issues/Bugs](#known-issuesbugs)
* [Backlog](#backlog)
* [How to Debug](#how-to-debug)
# Functionality
Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document.
![Underscores and hovers](https://github.com/username/repository/raw/main/images/SpellMDDemo1.gif)
The status bar lets you quickly navigate to any issue and you can see all positions in the gutter.
[![Jump to issues](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif)](http://shouldnottouchthis/)
[![Jump to issues](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/main/monkey)
![](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif)
<img src="https://github.com/username/repository/raw/main/images/myImage.gif">
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
![Add to dictionary](https://github.com/username/repository/raw/main/images/SpellMDDemo3.gif)
![issue](https://github.com/username/repository/raw/main/issue)
[mono](https://github.com/username/repository/blob/main/monkey)
[not](http://shouldnottouchthis/)
[Email me](mailto:example@example.com)
# Install
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.
To clone the extension and load locally...
```
git clone https://github.com/Microsoft/vscode-SpellMD.git
npm install
tsc
```
>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`.

View file

@ -0,0 +1,48 @@
# README
>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file.
This README covers off:
* [Functionality](#functionality)
* [Install](#install)
* [Run and Configure](#run-and-configure)
* [Known Issues/Bugs](#known-issuesbugs)
* [Backlog](#backlog)
* [How to Debug](#how-to-debug)
# Functionality
Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document.
![Underscores and hovers](https://github.com/base/images/SpellMDDemo1.gif)
The status bar lets you quickly navigate to any issue and you can see all positions in the gutter.
[![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](http://shouldnottouchthis/)
[![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](https://github.com/base/monkey)
![](https://github.com/base/images/SpellMDDemo2.gif)
<img src="https://github.com/base/images/myImage.gif">
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
![Add to dictionary](https://github.com/base/images/SpellMDDemo3.gif)
![issue](https://github.com/base/issue)
[mono](https://github.com/base/monkey)
[not](http://shouldnottouchthis/)
[Email me](mailto:example@example.com)
# Install
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.
To clone the extension and load locally...
```
git clone https://github.com/Microsoft/vscode-SpellMD.git
npm install
tsc
```
>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`.

View file

@ -0,0 +1,48 @@
# README
>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file.
This README covers off:
* [Functionality](#functionality)
* [Install](#install)
* [Run and Configure](#run-and-configure)
* [Known Issues/Bugs](#known-issuesbugs)
* [Backlog](#backlog)
* [How to Debug](#how-to-debug)
# Functionality
Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document.
![Underscores and hovers](https://github.com/base/images/SpellMDDemo1.gif)
The status bar lets you quickly navigate to any issue and you can see all positions in the gutter.
[![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](http://shouldnottouchthis/)
[![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/main/monkey)
![](https://github.com/base/images/SpellMDDemo2.gif)
<img src="https://github.com/base/images/myImage.gif">
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
![Add to dictionary](https://github.com/base/images/SpellMDDemo3.gif)
![issue](https://github.com/base/issue)
[mono](https://github.com/username/repository/blob/main/monkey)
[not](http://shouldnottouchthis/)
[Email me](mailto:example@example.com)
# Install
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.
To clone the extension and load locally...
```
git clone https://github.com/Microsoft/vscode-SpellMD.git
npm install
tsc
```
>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`.

View file

@ -21,6 +21,7 @@ The status bar lets you quickly navigate to any issue and you can see all positi
[![Jump to issues](https://github.com/username/repository/raw/master/images/SpellMDDemo2.gif)](http://shouldnottouchthis/)
[![Jump to issues](https://github.com/username/repository/raw/master/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/master/monkey)
![](https://github.com/username/repository/raw/master/images/SpellMDDemo2.gif)
<img src="https://github.com/username/repository/raw/master/images/myImage.gif">
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
@ -30,6 +31,7 @@ The `spellMD.json` config file is watched so you can add more ignores or change
[mono](https://github.com/username/repository/blob/master/monkey)
[not](http://shouldnottouchthis/)
[Email me](mailto:example@example.com)
# Install
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.

View file

@ -0,0 +1,15 @@
# Replace
[#8](https://github.com/username/repository/issues/8)
* Some issue in same repository: [#7](https://github.com/username/repository/issues/7)
* Some issue in other repository: [other/repositoryName#8](https://github.com/other/repositoryName/issues/8)
* Some issue in other repository with fancy name: [my_user-name/my-rep_o12#6](https://github.com/my_user-name/my-rep_o12/issues/6)
# Do not touch this:
* username#4 (no valid github link)
* /#7
* foo/$234/#7
* [#7](http://shouldnottouchthis/)
* [other/repositoryName#8](http://shouldnottouchthis/)
* [Email me](MAILTO:example@example.com)

View file

@ -0,0 +1,15 @@
# Replace
#8
* Some issue in same repository: #7
* Some issue in other repository: other/repositoryName#8
* Some issue in other repository with fancy name: my_user-name/my-rep_o12#6
# Do not touch this:
* username#4 (no valid github link)
* /#7
* foo/$234/#7
* [#7](http://shouldnottouchthis/)
* [other/repositoryName#8](http://shouldnottouchthis/)
* [Email me](MAILTO:example@example.com)

View file

@ -21,6 +21,7 @@ The status bar lets you quickly navigate to any issue and you can see all positi
[![Jump to issues](https://github.com/username/repository/path/to/images/SpellMDDemo2.gif)](http://shouldnottouchthis/)
[![Jump to issues](https://github.com/username/repository/path/to/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/master/monkey)
![](https://github.com/username/repository/path/to/images/SpellMDDemo2.gif)
<img src="https://github.com/username/repository/path/to/images/myImage.gif">
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
@ -30,6 +31,7 @@ The `spellMD.json` config file is watched so you can add more ignores or change
[mono](https://github.com/username/repository/blob/master/monkey)
[not](http://shouldnottouchthis/)
[Email me](mailto:example@example.com)
# Install
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.

View file

@ -21,6 +21,7 @@ The status bar lets you quickly navigate to any issue and you can see all positi
[![Jump to issues](images/SpellMDDemo2.gif)](http://shouldnottouchthis/)
[![Jump to issues](images/SpellMDDemo2.gif)](monkey)
![](images/SpellMDDemo2.gif)
<img src="/images/myImage.gif">
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
@ -30,6 +31,7 @@ The `spellMD.json` config file is watched so you can add more ignores or change
[mono](monkey)
[not](http://shouldnottouchthis/)
[Email me](mailto:example@example.com)
# Install
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.

View file

@ -1,7 +1,7 @@
import {
readManifest, collect, toContentTypes, ReadmeProcessor,
read, processFiles, createDefaultProcessors,
toVsixManifest, IFile, validateManifest
toVsixManifest, IFile, validateManifest, isSupportedWebExtension, WebExtensionProcessor, IAsset, IPackageOptions
} from '../package';
import { Manifest } from '../manifest';
import * as path from 'path';
@ -10,6 +10,7 @@ import * as assert from 'assert';
import { parseString } from 'xml2js';
import * as denodeify from 'denodeify';
import * as _ from 'lodash';
import { IExtensionsReport } from '../publicgalleryapi';
// don't warn in tests
console.warn = () => null;
@ -65,13 +66,14 @@ type ContentTypes = {
const parseXmlManifest = createXMLParser<XMLManifest>();
const parseContentTypes = createXMLParser<ContentTypes>();
function _toVsixManifest(manifest: Manifest, files: IFile[]): Promise<string> {
const processors = createDefaultProcessors(manifest);
function _toVsixManifest(manifest: Manifest, files: IFile[], options: IPackageOptions = {}): Promise<string> {
const processors = createDefaultProcessors(manifest, options);
return processFiles(processors, files).then(() => {
const assets = _.flatten(processors.map(p => p.assets));
const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets });
const tags = _(_.flatten(processors.map(p => p.tags))).join(',');
const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets, tags });
return toVsixManifest(assets, vsix);
return toVsixManifest(vsix);
});
}
@ -359,7 +361,7 @@ describe('toVsixManifest', () => {
};
const files = [
{ path: 'extension/readme.md', contents: new Buffer('') }
{ path: 'extension/readme.md', contents: Buffer.from('') }
];
return _toVsixManifest(manifest, files)
@ -381,7 +383,7 @@ describe('toVsixManifest', () => {
};
const files = [
{ path: 'extension/changelog.md', contents: new Buffer('') }
{ path: 'extension/changelog.md', contents: Buffer.from('') }
];
return _toVsixManifest(manifest, files)
@ -1018,8 +1020,8 @@ describe('toVsixManifest', () => {
};
const files = [
{ path: 'extension/de.json', contents: new Buffer('') },
{ path: 'extension/translations/pt.json', contents: new Buffer('') }
{ path: 'extension/de.json', contents: Buffer.from('') },
{ path: 'extension/translations/pt.json', contents: Buffer.from('') }
];
return _toVsixManifest(manifest, files)
@ -1242,84 +1244,215 @@ describe('toVsixManifest', () => {
});
});
describe('qna', () => {
it('should use marketplace qna by default', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null)
});
it('should error with files with same case insensitive name', async () => {
const manifest = {
name: 'test',
publisher: 'mocha',
version: '0.0.1',
description: 'test extension',
engines: Object.create(null)
};
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
const files = [
{ path: 'extension/file.txt' },
{ path: 'extension/FILE.txt' },
];
try {
await _toVsixManifest(manifest, files);
} catch (err) {
assert(/have the same case insensitive path/i.test(err.message));
return;
}
throw new Error('Should not reach here');
});
it('should expose web extension assets and properties', async () => {
const manifest = createManifest({
browser: 'browser.js',
extensionKind: ['web'],
});
const files = [
{ path: 'extension/browser.js', contents: Buffer.from('') },
];
const vsixManifest = await _toVsixManifest(manifest, files, { web: true })
const result = await parseXmlManifest(vsixManifest);
const assets = result.PackageManifest.Assets[0].Asset;
assert(assets.some(asset => asset.$.Type === 'Microsoft.VisualStudio.Code.WebResources/extension/browser.js' && asset.$.Path === 'extension/browser.js'));
const properties = result.PackageManifest.Metadata[0].Properties[0].Property;
const webExtensionProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.WebExtension');
assert.equal(webExtensionProps.length, 1);
assert.equal(webExtensionProps[0].$.Value, 'true');
});
it('should expose web extension assets and properties when extension kind is not provided', async () => {
const manifest = createManifest({
browser: 'browser.js',
});
const files = [
{ path: 'extension/browser.js', contents: Buffer.from('') },
];
const vsixManifest = await _toVsixManifest(manifest, files, { web: true })
const result = await parseXmlManifest(vsixManifest);
const assets = result.PackageManifest.Assets[0].Asset;
assert(assets.some(asset => asset.$.Type === 'Microsoft.VisualStudio.Code.WebResources/extension/browser.js' && asset.$.Path === 'extension/browser.js'));
const properties = result.PackageManifest.Metadata[0].Properties[0].Property;
const webExtensionProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.WebExtension');
assert.equal(webExtensionProps.length, 1);
assert.equal(webExtensionProps[0].$.Value, 'true');
});
it('should not expose web extension assets and properties for web extension when not asked for', async () => {
const manifest = createManifest({
browser: 'browser.js',
extensionKind: ['web'],
});
const files = [
{ path: 'extension/browser.js', contents: Buffer.from('') },
];
const vsixManifest = await _toVsixManifest(manifest, files)
const result = await parseXmlManifest(vsixManifest);
const assets = result.PackageManifest.Assets[0].Asset;
assert(assets.every(asset => !asset.$.Type.startsWith('Microsoft.VisualStudio.Code.WebResources')));
const properties = result.PackageManifest.Metadata[0].Properties[0].Property;
const webExtensionProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.WebExtension');
assert.equal(webExtensionProps.length, 0);
});
it('should not expose web extension assets and properties for non web extension', async () => {
const manifest = createManifest({
main: 'main.js',
});
const files = [
{ path: 'extension/main.js', contents: Buffer.from('') },
];
const vsixManifest = await _toVsixManifest(manifest, files, { web: true })
const result = await parseXmlManifest(vsixManifest);
const assets = result.PackageManifest.Assets[0].Asset;
assert(assets.every(asset => !asset.$.Type.startsWith('Microsoft.VisualStudio.Code.WebResources')));
const properties = result.PackageManifest.Metadata[0].Properties[0].Property;
const webExtensionProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.WebExtension');
assert.equal(webExtensionProps.length, 0);
});
it('should expose extension kind properties when providedd', async () => {
const manifest = createManifest({
extensionKind: ['ui', 'workspace', 'web'],
});
const files = [
{ path: 'extension/main.js', contents: Buffer.from('') },
];
const vsixManifest = await _toVsixManifest(manifest, files, { web: true })
const result = await parseXmlManifest(vsixManifest);
const properties = result.PackageManifest.Metadata[0].Properties[0].Property;
const extensionKindProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.ExtensionKind');
assert.equal(extensionKindProps[0].$.Value, ['ui', 'workspace', 'web'].join(','));
});
it('should expose extension kind properties when derived', async () => {
const manifest = createManifest({
main: 'main.js',
});
const files = [
{ path: 'extension/main.js', contents: Buffer.from('') },
];
const vsixManifest = await _toVsixManifest(manifest, files, { web: true })
const result = await parseXmlManifest(vsixManifest);
const properties = result.PackageManifest.Metadata[0].Properties[0].Property;
const extensionKindProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.ExtensionKind');
assert.equal(extensionKindProps[0].$.Value, 'workspace');
});
});
describe('qna', () => {
it('should use marketplace qna by default', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null)
});
it('should not use marketplace in a github repo, without specifying it', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
repository: 'https://github.com/username/repository'
});
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
});
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
it('should not use marketplace in a github repo, without specifying it', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
repository: 'https://github.com/username/repository'
});
it('should use marketplace in a github repo, when specifying it', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
repository: 'https://github.com/username/repository',
qna: 'marketplace'
});
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
});
assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'true');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
it('should use marketplace in a github repo, when specifying it', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
repository: 'https://github.com/username/repository',
qna: 'marketplace'
});
it('should handle qna=marketplace', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
qna: 'marketplace'
});
assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'true');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
});
assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'true');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
it('should handle qna=marketplace', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
qna: 'marketplace'
});
it('should handle qna=false', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
qna: false
});
assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'true');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
});
assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'false');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
it('should handle qna=false', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
qna: false
});
it('should handle custom qna', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
qna: 'http://myqna'
});
assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'false');
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink');
});
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA');
assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink', 'http://myqna');
it('should handle custom qna', async () => {
const xmlManifest = await toXMLManifest({
name: 'test',
publisher: 'mocha',
version: '0.0.1',
engines: Object.create(null),
qna: 'http://myqna'
});
assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA');
assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink', 'http://myqna');
});
});
@ -1442,6 +1575,102 @@ describe('MarkdownProcessor', () => {
});
});
it('should replace relative links with GitHub URLs while respecting githubBranch', () => {
const manifest = {
name: 'test',
publisher: 'mocha',
version: '0.0.1',
description: 'test extension',
engines: Object.create(null),
repository: 'https://github.com/username/repository'
};
const root = fixture('readme');
const processor = new ReadmeProcessor(manifest, {
githubBranch: 'main'
});
const readme = {
path: 'extension/readme.md',
localPath: path.join(root, 'readme.md')
};
return processor.onFile(readme)
.then(file => read(file))
.then(actual => {
return readFile(path.join(root, 'readme.branch.main.expected.md'), 'utf8')
.then(expected => {
assert.equal(actual, expected);
});
});
});
it("should override image URLs with baseImagesUrl while also respecting githubBranch", () => {
const manifest = {
name: "test",
publisher: "mocha",
version: "0.0.1",
description: "test extension",
engines: Object.create(null),
repository: "https://github.com/username/repository",
};
const root = fixture("readme");
const processor = new ReadmeProcessor(manifest, {
githubBranch: "main",
// Override image relative links to point to different base URL
baseImagesUrl: "https://github.com/base",
});
const readme = {
path: "extension/readme.md",
localPath: path.join(root, "readme.md"),
};
return processor
.onFile(readme)
.then((file) => read(file))
.then((actual) => {
return readFile(
path.join(root, "readme.branch.override.images.expected.md"),
"utf8"
).then((expected) => {
assert.equal(actual, expected);
});
});
});
it("should override githubBranch setting with baseContentUrl", () => {
const manifest = {
name: "test",
publisher: "mocha",
version: "0.0.1",
description: "test extension",
engines: Object.create(null),
repository: "https://github.com/username/repository",
};
const root = fixture("readme");
const processor = new ReadmeProcessor(manifest, {
githubBranch: "main",
baseContentUrl: "https://github.com/base",
});
const readme = {
path: "extension/readme.md",
localPath: path.join(root, "readme.md"),
};
return processor
.onFile(readme)
.then((file) => read(file))
.then((actual) => {
return readFile(
path.join(root, "readme.branch.override.content.expected.md"),
"utf8"
).then((expected) => {
assert.equal(actual, expected);
});
});
});
it('should infer baseContentUrl if its a github repo (.git)', () => {
const manifest = {
name: 'test',
@ -1500,6 +1729,87 @@ describe('MarkdownProcessor', () => {
});
});
it('should replace issue links with urls if its a github repo.', () => {
const manifest = {
name: 'test',
publisher: 'mocha',
version: '0.0.1',
description: 'test extension',
engines: Object.create(null),
repository: 'https://github.com/username/repository.git'
};
const root = fixture('readme');
const processor = new ReadmeProcessor(manifest, {});
const readme = {
path: 'extension/readme.md',
localPath: path.join(root, 'readme.github.md')
};
return processor.onFile(readme)
.then(file => read(file))
.then(actual => {
return readFile(path.join(root, 'readme.github.expected.md'), 'utf8')
.then(expected => {
assert.equal(actual, expected);
});
});
});
it('should not replace issue links with urls if its a github repo but issue link expansion is disabled.', () => {
const manifest = {
name: 'test',
publisher: 'mocha',
version: '0.0.1',
description: 'test extension',
engines: Object.create(null),
repository: 'https://github.com/username/repository.git'
};
const root = fixture('readme');
const processor = new ReadmeProcessor(manifest, { expandGitHubIssueLinks: false });
const readme = {
path: 'extension/readme.md',
localPath: path.join(root, 'readme.github.md')
};
return processor.onFile(readme)
.then(file => read(file))
.then(actual => {
return readFile(path.join(root, 'readme.github.md'), 'utf8')
.then(expected => {
assert.equal(actual, expected);
});
});
});
it('should not replace issue links with urls if its not a github repo.', () => {
const manifest = {
name: 'test',
publisher: 'mocha',
version: '0.0.1',
description: 'test extension',
engines: Object.create(null),
repository: 'https://some-other-provider.com/username/repository.git'
};
const root = fixture('readme');
const processor = new ReadmeProcessor(manifest, {});
const readme = {
path: 'extension/readme.md',
localPath: path.join(root, 'readme.github.md')
};
return processor.onFile(readme)
.then(file => read(file))
.then(actual => {
return readFile(path.join(root, 'readme.github.md'), 'utf8')
.then(expected => {
assert.equal(actual, expected);
});
});
});
it('should prevent non-HTTPS images', async () => {
const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' };
const contents = `![title](http://foo.png)`;
@ -1537,6 +1847,25 @@ describe('MarkdownProcessor', () => {
assert(file);
});
it('should allow SVG from GitHub actions in image tag', async () => {
const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' };
const contents = `![title](https://github.com/fakeuser/fakerepo/workflows/fakeworkflowname/badge.svg)`;
const processor = new ReadmeProcessor(manifest, {});
const readme = { path: 'extension/readme.md', contents };
const file = await processor.onFile(readme);
assert(file);
});
it('should prevent SVG from a GitHub repo in image tag', async () => {
const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' };
const contents = `![title](https://github.com/eviluser/evilrepo/blob/master/malicious.svg)`;
const processor = new ReadmeProcessor(manifest, {});
const readme = { path: 'extension/readme.md', contents };
await throws(() => processor.onFile(readme));
});
it('should prevent SVGs from not trusted sources in img tags', async () => {
const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' };
const contents = `<img src="https://foo/hello.svg" />`;
@ -1573,4 +1902,184 @@ describe('MarkdownProcessor', () => {
await throws(() => processor.onFile(readme));
});
it('should catch an unchanged README.md', async () => {
const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' };
const contents = `This is the README for your extension `;
const processor = new ReadmeProcessor(manifest, {});
const readme = { path: 'extension/readme.md', contents };
await throws(() => processor.onFile(readme));
})
});
describe('isSupportedWebExtension', () => {
it('should return true if extension report has extension', () => {
const manifest = createManifest({ name: 'test', publisher: 'mocha' });
const extensionReport: IExtensionsReport = { malicious: [], web: { extensions: ['mocha.test'], publishers: [] } };
assert.ok(isSupportedWebExtension(manifest, extensionReport));
});
it('should return true if extension report has publisher', () => {
const manifest = createManifest({ name: 'test', publisher: 'mocha' });
const extensionReport: IExtensionsReport = { malicious: [], web: { extensions: [], publishers: ['mocha'] } };
assert.ok(isSupportedWebExtension(manifest, extensionReport));
});
it('should return false if extension report does not has extension', () => {
const manifest = createManifest({ name: 'test', publisher: 'mocha' });
const extensionReport: IExtensionsReport = { malicious: [], web: { extensions: [], publishers: [] } };
assert.ok(!isSupportedWebExtension(manifest, extensionReport));
});
});
describe('WebExtensionProcessor', () => {
it('should include file', async () => {
const manifest = createManifest({ extensionKind: ['web'] });
const processor = new WebExtensionProcessor(manifest, { web: true });
const file = { path: 'extension/browser.js', contents: '' };
await processor.onFile(file);
await processor.onEnd();
const expected: IAsset[] = [{ type: `Microsoft.VisualStudio.Code.WebResources/${file.path}`, path: file.path }];
assert.deepEqual(processor.assets, expected);
});
it('should include file when extension kind is not specified', async () => {
const manifest = createManifest({ browser: 'browser.js' });
const processor = new WebExtensionProcessor(manifest, { web: true });
const file = { path: 'extension/browser.js', contents: '' };
await processor.onFile(file);
await processor.onEnd();
const expected: IAsset[] = [{ type: `Microsoft.VisualStudio.Code.WebResources/${file.path}`, path: file.path }];
assert.deepEqual(processor.assets, expected);
});
it('should not include file when not asked for', async () => {
const manifest = createManifest({ extensionKind: ['web'] });
const processor = new WebExtensionProcessor(manifest, { web: false });
const file = { path: 'extension/browser.js', contents: '' };
await processor.onFile(file);
await processor.onEnd();
assert.deepEqual(processor.assets, []);
});
it('should not include file for non web extension', async () => {
const manifest = createManifest({ extensionKind: ['ui'] });
const processor = new WebExtensionProcessor(manifest, { web: true });
const file = { path: 'extension/browser.js', contents: '' };
await processor.onFile(file);
await processor.onEnd();
assert.deepEqual(processor.assets, []);
});
it('should include manifest', async () => {
const manifest = createManifest({ extensionKind: ['web'] });
const processor = new WebExtensionProcessor(manifest, { web: true });
const manifestFile = { path: 'extension/package.json', contents: JSON.stringify(manifest) };
await processor.onFile(manifestFile);
await processor.onEnd();
const expected: IAsset[] = [{ type: `Microsoft.VisualStudio.Code.WebResources/${manifestFile.path}`, path: manifestFile.path }];
assert.deepEqual(processor.assets, expected);
});
it('should fail for svg file', async () => {
const manifest = createManifest({ extensionKind: ['web'] });
const processor = new WebExtensionProcessor(manifest, { web: true });
try {
await processor.onFile({ path: 'extension/sample.svg', contents: '' });
} catch (error) {
return; // expected
}
assert.fail('Should fail');
});
it('should include max 25 files', async () => {
const manifest = createManifest({ extensionKind: ['web'] });
const processor = new WebExtensionProcessor(manifest, { web: true });
const expected: IAsset[] = [];
for (let i = 1; i <= 25; i++) {
const file = { path: `extension/${i}.json`, contents: `${i}` };
await processor.onFile(file);
expected.push({ type: `Microsoft.VisualStudio.Code.WebResources/${file.path}`, path: file.path });
}
await processor.onEnd();
assert.deepEqual(processor.assets.length, 25);
assert.deepEqual(processor.assets, expected);
});
it('should throw an error if there are more than 25 files', async () => {
const manifest = createManifest({ extensionKind: ['web'] });
const processor = new WebExtensionProcessor(manifest, { web: true });
for (let i = 1; i <= 26; i++) {
await processor.onFile({ path: `extension/${i}.json`, contents: `${i}` });
}
try {
await processor.onEnd();
} catch (error) {
return; // expected error
}
assert.fail('Should fail');
});
it('should include web extension property & tag', async () => {
const manifest = createManifest({ extensionKind: ['web'] });
const processor = new WebExtensionProcessor(manifest, { web: true });
await processor.onEnd();
assert.equal(processor.vsix.webExtension, true);
assert.deepEqual(processor.tags, ['__web_extension']);
});
it('should include web extension property & tag when extension kind is not provided', async () => {
const manifest = createManifest({ browser: 'browser.js' });
const processor = new WebExtensionProcessor(manifest, { web: true });
await processor.onEnd();
assert.equal(processor.vsix.webExtension, true);
assert.deepEqual(processor.tags, ['__web_extension']);
});
it('should not include web extension property & tag when not asked for', async () => {
const manifest = createManifest({ extensionKind: ['web'] });
const processor = new WebExtensionProcessor(manifest, { web: false });
await processor.onEnd();
assert.equal(processor.vsix.webExtension, undefined);
assert.deepEqual(processor.tags, []);
});
it('should not include web extension property & tag for non web extension', async () => {
const manifest = createManifest({ extensionKind: ['ui'] });
const processor = new WebExtensionProcessor(manifest, { web: true });
await processor.onEnd();
assert.equal(processor.vsix.webExtension, undefined);
assert.deepEqual(processor.tags, []);
});
});

View file

@ -1,5 +1,5 @@
import * as assert from 'assert';
import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility } from '../validation';
import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility, validateVSCodeTypesCompatibility } from '../validation';
describe('validatePublisher', () => {
it('should throw with empty', () => {
@ -98,4 +98,32 @@ describe('validateEngineCompatibility', () => {
assert.throws(() => validateVersion('>=1'));
assert.throws(() => validateVersion('>=1.0'));
});
});
describe('validateVSCodeTypesCompatibility', () => {
it('should validate', () => {
validateVSCodeTypesCompatibility('*', '1.30.0');
validateVSCodeTypesCompatibility('*', '^1.30.0');
validateVSCodeTypesCompatibility('*', '~1.30.0');
validateVSCodeTypesCompatibility('1.30.0', '1.30.0');
validateVSCodeTypesCompatibility('1.30.0', '1.20.0');
validateVSCodeTypesCompatibility('1.46.0', '1.45.1');
assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '^1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '~1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('^1.30.0', '1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('~1.30.0', '1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.x.x', '1.30.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.x.0', '1.30.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.5.0', '1.30.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.5', '1.30.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.5', '1.30'));
});
});

View file

@ -1,5 +1,5 @@
import * as _read from 'read';
import { WebApi, getPersonalAccessTokenHandler } from 'azure-devops-node-api/WebApi';
import { WebApi, getBasicHandler } from 'azure-devops-node-api/WebApi';
import { IGalleryApi, GalleryApi } from 'azure-devops-node-api/GalleryApi';
import * as denodeify from 'denodeify';
import chalk from 'chalk';
@ -23,7 +23,7 @@ export function getPublishedUrl(extension: string): string {
export async function getGalleryAPI(pat: string): Promise<IGalleryApi> {
// from https://github.com/Microsoft/tfs-cli/blob/master/app/exec/extension/default.ts#L287-L292
const authHandler = getPersonalAccessTokenHandler(pat);
const authHandler = getBasicHandler('OAuth', pat);
return new GalleryApi(marketplaceUrl, [authHandler]);
// const vsoapi = new WebApi(marketplaceUrl, authHandler);
@ -31,7 +31,7 @@ export async function getGalleryAPI(pat: string): Promise<IGalleryApi> {
}
export async function getSecurityRolesAPI(pat: string): Promise<ISecurityRolesApi> {
const authHandler = getPersonalAccessTokenHandler(pat);
const authHandler = getBasicHandler('OAuth', pat);
const vsoapi = new WebApi(marketplaceUrl, authHandler);
return await vsoapi.getSecurityRolesApi();
}

View file

@ -1,4 +1,5 @@
import * as semver from 'semver';
import * as parseSemver from 'parse-semver';
const nameRegex = /^[a-z0-9][a-z0-9\-]*$/i;
@ -40,4 +41,63 @@ export function validateEngineCompatibility(version: string): void {
if (!/^\*$|^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/.test(version)) {
throw new Error(`Invalid vscode engine compatibility version '${version}'`);
}
}
/**
* User shouldn't use a newer version of @types/vscode than the one specified in engines.vscode
*/
export function validateVSCodeTypesCompatibility(engineVersion: string, typeVersion: string): void {
if (engineVersion === '*') {
return;
}
if (!typeVersion) {
throw new Error(`Missing @types/vscode version`);
}
let plainEngineVersion: string, plainTypeVersion: string;
try {
const engineSemver = parseSemver(`vscode@${engineVersion}`);
plainEngineVersion = engineSemver.version;
} catch (err) {
throw new Error('Failed to parse semver of engines.vscode');
}
try {
const typeSemver = parseSemver(`@types/vscode@${typeVersion}`);
plainTypeVersion = typeSemver.version;
} catch (err) {
throw new Error('Failed to parse semver of @types/vscode');
}
// For all `x`, use smallest version for comparison
plainEngineVersion = plainEngineVersion.replace(/x/g, '0');
const [typeMajor, typeMinor, typePatch] = plainTypeVersion.split('.').map(x => {
try {
return parseInt(x);
} catch (err) {
return 0;
}
});
const [engineMajor, engineMinor, enginePatch] = plainEngineVersion.split('.').map(x => {
try {
return parseInt(x);
} catch (err) {
return 0;
}
});
const error = new Error(`@types/vscode ${typeVersion} greater than engines.vscode ${engineVersion}. Consider upgrade engines.vscode or use an older @types/vscode version`);
if (typeMajor > engineMajor) {
throw error;
}
if (typeMajor === engineMajor && typeMinor > engineMinor) {
throw error;
}
if (typeMajor === engineMajor && typeMinor === engineMinor && typePatch > enginePatch) {
throw error;
}
}

View file

@ -1,5 +1,3 @@
const os = require('os');
export type ViewTableRow = string[];
export type ViewTable = ViewTableRow[];
@ -13,18 +11,11 @@ const columns = process.stdout.columns ? process.stdout.columns : 80;
// xxx: Windows cmd + powershell standard fonts currently don't support the full
// unicode charset. For now we use fallback icons when on windows.
const useFallbackIcons = os.platform() === 'win32';
const useFallbackIcons = process.platform === 'win32';
export const icons = useFallbackIcons?
{
download: '\u{2193}',
star: '\u{2665}',
emptyStar: '\u{2022}',
} : {
download: '\u{2913}',
star: '\u{2605}',
emptyStar: '\u{2606}',
};
export const icons = useFallbackIcons
? { download: '\u{2193}', star: '\u{2665}', emptyStar: '\u{2022}', }
: { download: '\u{2913}', star: '\u{2605}', emptyStar: '\u{2606}', };
export function formatDate(date) { return date.toLocaleString(fixedLocale, format.date); }
export function formatTime(date) { return date.toLocaleString(fixedLocale, format.time); }
@ -55,11 +46,11 @@ export function wordWrap(text: string, width: number = columns): string {
return text
.replace(/^\s+/, '')
.split('')
.reduce(([out, buffer, pos], ch, i) => {
.reduce(([out, buffer, pos], ch) => {
const nl = pos === maxWidth ? `\n${indent}` : '';
const newPos: number = nl ? 0 : +pos + 1;
return / |-|,|\./.test(ch) ?
[`${out}${buffer}${ch}${nl}`, '', newPos] : [`${out}${nl}`, buffer+ch, newPos];
[`${out}${buffer}${ch}${nl}`, '', newPos] : [`${out}${nl}`, buffer + ch, newPos];
}, [indent, '', 0])
.slice(0, 2)
.join('');

View file

@ -1 +0,0 @@
--require source-map-support/register out/test

View file

@ -3,7 +3,9 @@
"target": "es2015",
"module": "commonjs",
"declaration": true,
"outDir": "out"
"outDir": "out",
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": [
"src"

1974
yarn.lock

File diff suppressed because it is too large Load diff