├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .husky └── pre-commit ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── certificates ├── favicon.png ├── index.css ├── index.html └── index.js ├── certs.md ├── ionic-start ├── .gitignore ├── README.md ├── angular.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ └── utilities │ │ │ ├── dom.ts │ │ │ ├── messages.ts │ │ │ ├── template.ts │ │ │ └── vscode.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── android.svg │ │ ├── apple.svg │ │ ├── blank.svg │ │ ├── capacitor.svg │ │ ├── chevron-down.svg │ │ ├── ionic.svg │ │ ├── list.svg │ │ ├── logo-angular.svg │ │ ├── logo-capacitor.svg │ │ ├── logo-react.svg │ │ ├── logo-vue.svg │ │ ├── my-first-app.svg │ │ ├── sidemenu.svg │ │ ├── star-outline.svg │ │ ├── star.svg │ │ ├── tabs.svg │ │ └── web.svg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── tests.ts ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json ├── log-client └── index.html ├── media ├── ionic.png └── ionic.svg ├── package-lock.json ├── package.json ├── plugin-explorer ├── .gitignore ├── README.md ├── angular.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── plugin-info.ts │ │ ├── plugin.component.css │ │ ├── plugin.component.ts │ │ ├── plugin.service.ts │ │ ├── star.component.ts │ │ └── utilities │ │ │ ├── dom.ts │ │ │ ├── messages.ts │ │ │ └── vscode.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── android.svg │ │ ├── apple.svg │ │ ├── capacitor.svg │ │ ├── chevron-down.svg │ │ ├── cordova.svg │ │ ├── star-outline.svg │ │ └── star.svg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── tests.ts ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json ├── resources ├── dark │ ├── add.svg │ ├── android.svg │ ├── angular.svg │ ├── apple.svg │ ├── beaker.svg │ ├── box.svg │ ├── build-anim.svg │ ├── build.svg │ ├── capacitor.svg │ ├── checkbox.svg │ ├── checkmark.svg │ ├── circle-filled.svg │ ├── comment.svg │ ├── cordova.svg │ ├── debug-alt-small-anim.svg │ ├── debug-alt-small.svg │ ├── debug.svg │ ├── dependency.svg │ ├── edit.svg │ ├── error.svg │ ├── file-media.svg │ ├── files.svg │ ├── globe-select.svg │ ├── globe.svg │ ├── info.svg │ ├── ionic.svg │ ├── lightbulb.svg │ ├── live-select.svg │ ├── live.svg │ ├── more.svg │ ├── nexus-select.svg │ ├── nexus.svg │ ├── react.svg │ ├── refresh.svg │ ├── run-anim.svg │ ├── run.svg │ ├── settings-gear.svg │ ├── sync-anim.svg │ ├── sync.svg │ ├── vscode-select.svg │ ├── vscode.svg │ ├── vue.svg │ └── warning.svg ├── light │ ├── add.svg │ ├── android.svg │ ├── angular.svg │ ├── apple.svg │ ├── beaker.svg │ ├── box.svg │ ├── build-anim.svg │ ├── build.svg │ ├── capacitor.svg │ ├── checkbox.svg │ ├── checkmark.svg │ ├── circle-filled.svg │ ├── comment.svg │ ├── cordova.svg │ ├── debug-alt-small-anim.svg │ ├── debug-alt-small.svg │ ├── debug.svg │ ├── dependency.svg │ ├── edit.svg │ ├── error.svg │ ├── file-media.svg │ ├── files.svg │ ├── globe-select.svg │ ├── globe.svg │ ├── info.svg │ ├── ionic.svg │ ├── lightbulb.svg │ ├── more.svg │ ├── nexus-select.svg │ ├── nexus.svg │ ├── react.svg │ ├── refresh.svg │ ├── run-anim.svg │ ├── run.svg │ ├── settings-gear.svg │ ├── sync-anim.svg │ ├── sync.svg │ ├── vscode-select.svg │ ├── vscode.svg │ ├── vue.svg │ └── warning.svg ├── log-language.xml └── qrious.min.js ├── roadmap.md ├── src ├── advanced-actions.ts ├── analyze-size.ts ├── analyzer.ts ├── android-debug-bridge.ts ├── android-debug-list.ts ├── android-debug-models.ts ├── android-debug-provider.ts ├── android-debug.ts ├── angular-generate.ts ├── audit.ts ├── build-configuration.ts ├── cap-project.ts ├── capacitor-add.ts ├── capacitor-build.ts ├── capacitor-config-file.ts ├── capacitor-configure.ts ├── capacitor-device.ts ├── capacitor-migrate.ts ├── capacitor-open.ts ├── capacitor-platform.ts ├── capacitor-pwa.ts ├── capacitor-run.ts ├── capacitor-sync.ts ├── command-name.ts ├── command-title.ts ├── context-variables.ts ├── discovery.ts ├── editor-preview.ts ├── error-handler.ts ├── extension.ts ├── features.ts ├── gradle-to-json.ts ├── ignore.ts ├── imports-angular.ts ├── imports-auto-fix.ts ├── imports-icons.ts ├── ionic-auth.ts ├── ionic-build.ts ├── ionic-devserver-provider.ts ├── ionic-export.ts ├── ionic-init.ts ├── ionic-projects-provider.ts ├── ionic-serve.ts ├── ionic-start-templates.ts ├── ionic-start.ts ├── ionic-tree-provider.ts ├── live-reload.ts ├── log-server-scripts.ts ├── log-server.ts ├── log-settings.ts ├── logging.ts ├── messages.ts ├── monorepo.ts ├── monorepos-lerna.ts ├── monorepos-npm.ts ├── monorepos-nx.ts ├── monorepos-pnpm.ts ├── nexus-browser.ts ├── node-commands.ts ├── npm-info-data.ts ├── npm-info.ts ├── npm-model.ts ├── package-info.ts ├── package-lock.ts ├── peer-dependencies.ts ├── peer-dependency-cleanup.ts ├── plugin-explorer.ts ├── plugin-summary.ts ├── prettier.ts ├── privacy-manifest.ts ├── process-list.ts ├── process-packages.ts ├── project.ts ├── quick-fix.ts ├── recommend.ts ├── recommendation.ts ├── rules-angular-json.ts ├── rules-angular-migrate.ts ├── rules-angular-toolkit.ts ├── rules-browserslist.ts ├── rules-capacitor-migration.ts ├── rules-capacitor-plugins.ts ├── rules-capacitor.ts ├── rules-cordova.ts ├── rules-deprecated-plugins.ts ├── rules-ionic-native.ts ├── rules-package-upgrade.ts ├── rules-packages.ts ├── rules-web-project.ts ├── scripts.ts ├── source-map-server.ts ├── splash-icon.ts ├── strip-json-comments.ts ├── tasks.ts ├── telemetry.ts ├── tip.ts ├── update-minor.ts ├── utilities.ts ├── web-configuration.ts ├── web-debug.ts ├── workspace-state.ts └── xcode.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | vscode.proposed.d.ts -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | plugins: [ 7 | '@typescript-eslint', 8 | ], 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | rules: { 14 | 'semi': [2, "always"], 15 | '@typescript-eslint/no-unused-vars': 0, 16 | '@typescript-eslint/no-explicit-any': 0, 17 | '@typescript-eslint/explicit-module-boundary-types': 0, 18 | '@typescript-eslint/no-non-null-assertion': 0, 19 | '@typescript-eslint/no-namespace': 0, 20 | 'no-inner-declarations': 0, 21 | } 22 | }; -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Build Ionic VS Code Extension 5 | 6 | on: 7 | push: 8 | branches: ['main'] 9 | pull_request: 10 | branches: ['main'] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 22.x 23 | 24 | - name: Install dependencies 25 | run: | 26 | npm install 27 | cd plugin-explorer 28 | npm install 29 | cd ../ionic-start 30 | npm install 31 | npm install -g @vscode/vsce 32 | 33 | - name: Lint 34 | run: | 35 | npm run lint 36 | 37 | - name: Build 38 | run: | 39 | npm run build 40 | 41 | - name: Upload release bundle 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: ionic.vsix 45 | path: ./ionic-*.vsix 46 | retention-days: 60 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | yarn.lock 4 | .DS_Store 5 | npm-debug.log 6 | Thumbs.db 7 | .vs/ 8 | *.vsix -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 10 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 11 | "preLaunchTask": "npm: watch" 12 | }, 13 | { 14 | "name": "Run Extension Tests", 15 | "type": "extensionHost", 16 | "request": "launch", 17 | "runtimeExecutable": "${execPath}", 18 | "args": ["--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test"], 19 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"], 20 | "preLaunchTask": "npm: watch" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "plugin-explorer": false, 6 | "ionic-start": false 7 | }, 8 | "search.exclude": { 9 | "out": true // set this to false to include "out" folder in search results 10 | }, 11 | "typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version 12 | "typescript.tsc.autoDetect": "off", 13 | "editor.insertSpaces": false 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | roadmap.md 2 | site/videos/** 3 | site/** 4 | **/*.vsix 5 | plugin-explorer/.angular 6 | plugin-explorer/node_modules 7 | plugin-explorer/src 8 | plugin-explorer/*.json 9 | plugin-explorer/*.js 10 | plugin-explorer/*.md 11 | ionic-start/** 12 | !ionic-start/build/** 13 | plugin-explorer/*.json 14 | plugin-explorer/*.js 15 | plugin-explorer/*.md 16 | node_modules/typescript 17 | node_modules/chevotrain 18 | node_modules/handlebars 19 | node_modules/esbuild 20 | node_modules/esbuild-darwin-64 21 | node_modules/eslint 22 | node_modules/rollup 23 | node_modules/@typescript-eslint 24 | node_modules/@types 25 | node_modules/**/*.md 26 | node_modules/**/*.txt 27 | node_modules/**/*.html 28 | node_modules/conventional-changelog 29 | node_modules/.bin 30 | node_modules/native-run 31 | node_modules/**/LICENSE 32 | log-client/node_modules 33 | log-client/*.ts 34 | log-client/*.map 35 | .husky 36 | **/*.ts 37 | **/*.jar 38 | !node_modules/@trapezedev/**/*.jar 39 | **/*.map -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Optionally install Volta: https://docs.volta.sh/guide/getting-started 4 | 5 | Ensure Visual Studio Extension Manager: 6 | 7 | - `npm install -g @vscode/vsce` 8 | 9 | For this project install dependencies: 10 | 11 | - `npm install` 12 | 13 | # Debugging 14 | 15 | - Press F5 in VSCode to start debugging the extension. It will open a VS Code window with the extension installed. 16 | 17 | # Testing the build 18 | 19 | To create a test build of the extension (ie create a `.vsix` file): 20 | 21 | - `npm run build` 22 | - Install the built `.vsix` file in VS Code 23 | 24 | # Building the plugin manager 25 | 26 | The Plugin Manager is an Angular application that works with the VS Code extension to search for and display plugins. You can build it 27 | by opening this project with the VS Code extension and clicking `Scripts` > `build`. Alternatively, you can `cd plugin-explorer`, `npm install`, and `npm run build`. This will compile the app to the right folder, and when you run the VS Code extension it will display 28 | it when visiting packages. 29 | 30 | # Publishing 31 | 32 | Make sure Visual Studio Extension Manager is installed (`npm install -g vsce`). 33 | 34 | Run 35 | 36 | - `vsce package` 37 | - A packaged with name `Ionic-0.0.1.vsix` will be created which can be installed or published to the marketplace. 38 | 39 | You can upload to the marketplace with `vsce publish` or you can manually upload the .vsix file [here](https://marketplace.visualstudio.com/manage/publishers/ionic). 40 | 41 | # Publish Prerelease 42 | 43 | You need a personal access token (see [here](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#get-a-personal-access-token)). 44 | 45 | Run 46 | 47 | - `vsce login Ionic` (make sure the I is capitalized) 48 | - `vsce publish --pre-release` 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022-present Drifty Co. 2 | https://ionic.io 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /certificates/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/vscode-ionic/058c9a0ab2d9ecaf3be051d1a6a94b7b19b76ae3/certificates/favicon.png -------------------------------------------------------------------------------- /certificates/index.css: -------------------------------------------------------------------------------- 1 | ion-card { 2 | margin-left: auto; 3 | margin-right: auto; 4 | max-width: 600px; 5 | margin-bottom: 30px; 6 | margin-top: 30px; 7 | } 8 | 9 | i { 10 | font-family: 'Courier New', Courier, monospace; 11 | font-weight: bold; 12 | font-style: normal; 13 | } 14 | 15 | ion-content { 16 | --background: #eee; 17 | } 18 | -------------------------------------------------------------------------------- /certificates/index.js: -------------------------------------------------------------------------------- 1 | setTimeout(() => { 2 | document.getElementById('url').innerText = document.location.href; 3 | document.getElementById('mac').hidden = !isMac() || isAndroid(); 4 | document.getElementById('other').hidden = !(isMac() || isWindows()) || isAndroid(); 5 | document.getElementById('ios').hidden = !isIOS(); 6 | document.getElementById('android').hidden = !isAndroid(); 7 | }, 30); 8 | 9 | function isMac() { 10 | return navigator.platform.toUpperCase().indexOf('MAC') >= 0 && !isTouch(); 11 | } 12 | 13 | function isAndroid() { 14 | return navigator.userAgent.toLowerCase().indexOf('android') > -1; 15 | } 16 | 17 | function isWindows() { 18 | return navigator.platform.indexOf('Win') > -1; 19 | } 20 | function isIOS() { 21 | return ( 22 | ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) || 23 | // iPad on iOS 13 detection 24 | isTouch() 25 | ); 26 | } 27 | 28 | function isTouch() { 29 | return navigator.userAgent.includes('Mac') && 'ontouchend' in document; 30 | } 31 | -------------------------------------------------------------------------------- /certs.md: -------------------------------------------------------------------------------- 1 | # Live Reload 2 | 3 | To aid with rapid development the Live Reload feature will allow your browser or mobile device to use your development web server instead of static files on your device. Whenever you change a file in your editor the device will reload your app and display your changes. 4 | 5 | In the VS Code extension you can choose `Settings` > `Live Reload` and then run on Web or mobile device to use the Live Reload feature. 6 | 7 | Live Reload will use http (not https) and this may affect your application particularly if it uses APIs that require a secure context (such as geolocation, crypto). 8 | 9 | ## Live Reload with SSL 10 | 11 | If your app uses APIs that require a secure context (like web crypto) then it must be served using https. The device will also need to trust the connection which means that if you use a self signed certificate it will need to be installed and trusted on the device. 12 | 13 | The VS Code extension can help create a self signed certificate and trust it on devices. Choose `Settings` > `Use HTTPS`. When you check the box `Use HTTPS` it will generate a self signed certificate on your computer and will display a page with instructions on how to download, install and trust the certificate. 14 | 15 | After downloading, installing and trusting the certificate on the device you want to test your application you can then run the app and it will be served by your local development server. 16 | 17 | ## Troubleshooting 18 | 19 | ### http is always used 20 | 21 | Your local development server needs to use https and the created certificate and private key. Angular projects are understand and handled by the VS Code extension. React and Vue projects are not and need manual configuration. 22 | 23 | ### White Page on Startup 24 | 25 | The certificate is not installed and trusted. To do this go to `Settings` > `Use HTTPS` (you may need to uncheck and recheck the option). A web page will display with instructions on installing and trusting the certificate. 26 | 27 | You can visit this page on the mobile device to download the certificate and install it. 28 | 29 | ## Known Issues 30 | 31 | ### Android SSL Support 32 | 33 | > Android devices will not trust a certificate when run in the app (causing a white screen) even when the certificate is installed correctly and displays the web app in Chrome. 34 | 35 | You can get around this issue by installing the SSL skip plugin: `npm install @jcesarmobile/ssl-skip`. 36 | 37 | ### Windows SSL Support 38 | 39 | > Windows is not supported or tested (yet). 40 | -------------------------------------------------------------------------------- /ionic-start/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /build 6 | /tmp 7 | /out-tsc 8 | /bazel-out 9 | 10 | # Node 11 | /node_modules 12 | npm-debug.log 13 | yarn-error.log 14 | 15 | # IDEs and editors 16 | .idea/ 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | .angular/ 23 | *.sublime-workspace 24 | 25 | # Visual Studio Code 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # Miscellaneous 34 | /.angular/cache 35 | .sass-cache/ 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | testem.log 40 | /typings 41 | 42 | # System files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /ionic-start/README.md: -------------------------------------------------------------------------------- 1 | # Ionic Start 2 | 3 | This directory contains an Angular app that lets you create a new project. 4 | It uses the [VSCode toolkit](https://github.com/microsoft/vscode-webview-ui-toolkit). Samples are [here](https://github.com/microsoft/vscode-webview-ui-toolkit-samples) 5 | -------------------------------------------------------------------------------- /ionic-start/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma'), 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true, // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/my-app'), 29 | subdir: '.', 30 | reporters: [{ type: 'html' }, { type: 'text-summary' }], 31 | }, 32 | reporters: ['progress', 'kjhtml'], 33 | port: 9876, 34 | colors: true, 35 | logLevel: config.LOG_INFO, 36 | autoWatch: true, 37 | browsers: ['Chrome'], 38 | singleRun: false, 39 | restartOnFileChange: true, 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /ionic-start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-start", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build --output-hashing=none --configuration production", 9 | "watch": "ng build --watch --configuration development", 10 | "test": "ng test" 11 | }, 12 | "dependencies": { 13 | "@angular/animations": "^19.0.1", 14 | "@angular/common": "^19.0.1", 15 | "@angular/compiler": "^19.0.1", 16 | "@angular/core": "^19.0.1", 17 | "@angular/forms": "^19.0.1", 18 | "@angular/platform-browser": "^19.0.1", 19 | "@angular/platform-browser-dynamic": "^19.0.1", 20 | "@angular/router": "^19.0.1", 21 | "@vscode/webview-ui-toolkit": "1.4.0", 22 | "rxjs": "~7.5.0", 23 | "tslib": "2.6.0", 24 | "zone.js": "~0.15.0" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "^19.0.2", 28 | "@angular/cli": "^19.0.2", 29 | "@angular/compiler-cli": "^19.0.1", 30 | "@types/jasmine": "4.3.5", 31 | "@types/node": "18.15.11", 32 | "@types/vscode-webview": "1.57.5", 33 | "jasmine-core": "5.3.0", 34 | "karma": "6.4.4", 35 | "karma-chrome-launcher": "3.2.0", 36 | "karma-coverage": "2.2.1", 37 | "karma-jasmine": "5.1.0", 38 | "karma-jasmine-html-reporter": "2.1.0", 39 | "typescript": "5.5.4" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ionic-start/src/app/utilities/dom.ts: -------------------------------------------------------------------------------- 1 | export function getValue(id: string): string { 2 | return document.getElementById(id)?.getAttribute('current-value') as string; 3 | } 4 | 5 | export function checked(id: string): boolean { 6 | const value = document.getElementById(id)?.getAttribute('current-checked'); 7 | return value == 'true'; 8 | } 9 | 10 | export function setChecked(id: string, checked: boolean): void { 11 | document.getElementById(id)?.setAttribute('current-checked', checked ? 'true' : 'false'); 12 | } 13 | -------------------------------------------------------------------------------- /ionic-start/src/app/utilities/messages.ts: -------------------------------------------------------------------------------- 1 | import { vscode } from './vscode'; 2 | 3 | export enum MessageType { 4 | getTemplates = 'getTemplates', 5 | createProject = 'createProject', 6 | getProjectsFolder = 'getProjectsFolder', 7 | chooseFolder = 'chooseFolder', 8 | creatingProject = 'creatingProject', 9 | } 10 | 11 | export function sendMessage(command: MessageType, value: string) { 12 | vscode.postMessage({ command, text: value }); 13 | } 14 | -------------------------------------------------------------------------------- /ionic-start/src/app/utilities/template.ts: -------------------------------------------------------------------------------- 1 | export interface Template { 2 | type: string; 3 | typeName: string; 4 | title: string; 5 | name: string; 6 | description: string; 7 | appearance?: string; 8 | icon?: string; 9 | } 10 | -------------------------------------------------------------------------------- /ionic-start/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/vscode-ionic/058c9a0ab2d9ecaf3be051d1a6a94b7b19b76ae3/ionic-start/src/assets/.gitkeep -------------------------------------------------------------------------------- /ionic-start/src/assets/android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ionic-start/src/assets/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ionic-start/src/assets/blank.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ionic-start/src/assets/capacitor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ionic-start/src/assets/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ionic-start/src/assets/ionic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ionic-start/src/assets/list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ionic-start/src/assets/logo-angular.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ionic-start/src/assets/logo-capacitor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ionic-start/src/assets/logo-vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ionic-start/src/assets/my-first-app.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ionic-start/src/assets/sidemenu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ionic-start/src/assets/star-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ionic-start/src/assets/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ionic-start/src/assets/tabs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ionic-start/src/assets/web.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ionic-start/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /ionic-start/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /ionic-start/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/vscode-ionic/058c9a0ab2d9ecaf3be051d1a6a94b7b19b76ae3/ionic-start/src/favicon.ico -------------------------------------------------------------------------------- /ionic-start/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Plugin Explorer 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ionic-start/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { environment } from './environments/environment'; 3 | import { bootstrapApplication } from '@angular/platform-browser'; 4 | import { AppComponent } from './app/app.component'; 5 | import { 6 | provideVSCodeDesignSystem, 7 | vsCodeButton, 8 | vsCodeCheckbox, 9 | vsCodeDivider, 10 | vsCodeLink, 11 | vsCodePanelView, 12 | vsCodeProgressRing, 13 | vsCodeRadio, 14 | vsCodeRadioGroup, 15 | vsCodeTag, 16 | vsCodeTextField, 17 | } from '@vscode/webview-ui-toolkit'; 18 | 19 | if (environment.production) { 20 | enableProdMode(); 21 | } 22 | 23 | // In order to use the Webview UI Toolkit web components they 24 | // must be registered with the browser (i.e. webview) using the 25 | // syntax below. 26 | provideVSCodeDesignSystem().register( 27 | vsCodeButton(), 28 | vsCodeTextField(), 29 | vsCodePanelView(), 30 | vsCodeLink(), 31 | vsCodeTag(), 32 | vsCodeCheckbox(), 33 | vsCodeDivider(), 34 | vsCodeRadio(), 35 | vsCodeRadioGroup(), 36 | vsCodeProgressRing(), 37 | ); 38 | 39 | bootstrapApplication(AppComponent).catch((err) => console.error(err)); 40 | -------------------------------------------------------------------------------- /ionic-start/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | /*************************************************************************************************** 51 | * APPLICATION IMPORTS 52 | */ 53 | -------------------------------------------------------------------------------- /ionic-start/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | main { 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | height: 100%; 7 | } 8 | 9 | h2, 10 | h3 { 11 | margin: 0; 12 | } 13 | 14 | main > * { 15 | margin: 0.5rem 0; 16 | } 17 | 18 | .columns { 19 | display: flex; 20 | width: 100%; 21 | } 22 | 23 | vscode-text-field { 24 | width: 100%; 25 | } 26 | 27 | .tooltip { 28 | position: relative; 29 | display: inline-block; 30 | border-bottom: 1px dotted var(--vscode-editor-background); 31 | } 32 | 33 | .tooltip .tooltiptext { 34 | visibility: hidden; 35 | width: 120px; 36 | background-color: var(--vscode-editor-background); 37 | color: var(--vscode-editor-foreground); 38 | text-align: center; 39 | border-radius: 6px; 40 | padding: 5px 0; 41 | 42 | /* Position the tooltip */ 43 | position: absolute; 44 | z-index: 1; 45 | } 46 | 47 | .small-tooltip { 48 | font-size: smaller !important; 49 | width: 190px; 50 | } 51 | 52 | .ionicon { 53 | width: 16px; 54 | height: 16px; 55 | } 56 | 57 | .tooltip:hover .tooltiptext { 58 | visibility: visible; 59 | } 60 | 61 | p { 62 | line-height: 1.7; 63 | margin: 0; 64 | } 65 | 66 | /* For live editing */ 67 | /* body { 68 | background-color: #2c2c2c; 69 | color: white; 70 | font-family:Arial, Helvetica, sans-serif; 71 | } */ 72 | -------------------------------------------------------------------------------- /ionic-start/src/tests.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; 6 | 7 | declare const require: { 8 | context( 9 | path: string, 10 | deep?: boolean, 11 | filter?: RegExp, 12 | ): { 13 | (id: string): T; 14 | keys(): string[]; 15 | }; 16 | }; 17 | 18 | // First, initialize the Angular testing environment. 19 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 20 | 21 | // Then we find all the tests. 22 | const context = require.context('./', true, /\.spec\.ts$/); 23 | // And load the modules. 24 | context.keys().map(context); 25 | -------------------------------------------------------------------------------- /ionic-start/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": ["src/main.ts", "src/polyfills.ts"], 9 | "include": ["src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /ionic-start/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "skipLibCheck": true, 20 | "target": "es2015", 21 | "module": "es2020", 22 | "lib": ["es2018", "dom"], 23 | }, 24 | "angularCompilerOptions": { 25 | "enableI18nLegacyMessageIdFormat": false, 26 | "strictInjectionParameters": true, 27 | "strictInputAccessModifiers": true, 28 | "strictStandalone": true, 29 | "strictTemplates": true, 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /ionic-start/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": ["jasmine"] 7 | }, 8 | "files": ["src/test.ts", "src/polyfills.ts"], 9 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /log-client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /media/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/vscode-ionic/058c9a0ab2d9ecaf3be051d1a6a94b7b19b76ae3/media/ionic.png -------------------------------------------------------------------------------- /media/ionic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /plugin-explorer/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /build 6 | /tmp 7 | /out-tsc 8 | /bazel-out 9 | 10 | # Node 11 | /node_modules 12 | npm-debug.log 13 | yarn-error.log 14 | 15 | # IDEs and editors 16 | .idea/ 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | .angular/ 23 | *.sublime-workspace 24 | 25 | # Visual Studio Code 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # Miscellaneous 34 | /.angular/cache 35 | .sass-cache/ 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | testem.log 40 | /typings 41 | 42 | # System files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /plugin-explorer/README.md: -------------------------------------------------------------------------------- 1 | # Plugin Explorer 2 | 3 | This directory contains an Angular app that lets you explore plugins and install them. 4 | It uses the [VSCode toolkit](https://github.com/microsoft/vscode-webview-ui-toolkit). Samples are [here](https://github.com/microsoft/vscode-webview-ui-toolkit-samples) 5 | -------------------------------------------------------------------------------- /plugin-explorer/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma'), 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true, // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/my-app'), 29 | subdir: '.', 30 | reporters: [{ type: 'html' }, { type: 'text-summary' }], 31 | }, 32 | reporters: ['progress', 'kjhtml'], 33 | port: 9876, 34 | colors: true, 35 | logLevel: config.LOG_INFO, 36 | autoWatch: true, 37 | browsers: ['Chrome'], 38 | singleRun: false, 39 | restartOnFileChange: true, 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /plugin-explorer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plugin-explorer", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build --output-hashing=none --configuration production", 9 | "watch": "ng build --watch --configuration development", 10 | "test": "ng test" 11 | }, 12 | "dependencies": { 13 | "@angular/animations": "^19.0.1", 14 | "@angular/common": "^19.0.1", 15 | "@angular/compiler": "^19.0.1", 16 | "@angular/core": "^19.0.1", 17 | "@angular/forms": "^19.0.1", 18 | "@angular/platform-browser": "^19.0.1", 19 | "@angular/platform-browser-dynamic": "^19.0.1", 20 | "@angular/router": "^19.0.1", 21 | "@vscode/webview-ui-toolkit": "1.4.0", 22 | "rxjs": "~7.5.0", 23 | "tslib": "2.5.2", 24 | "zone.js": "~0.15.0" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "^19.0.2", 28 | "@angular/cli": "^19.0.2", 29 | "@angular/compiler-cli": "^19.0.1", 30 | "@types/jasmine": "4.3.1", 31 | "@types/node": "18.15.11", 32 | "@types/vscode-webview": "1.57.5", 33 | "jasmine-core": "5.3.0", 34 | "karma": "6.4.4", 35 | "karma-chrome-launcher": "3.2.0", 36 | "karma-coverage": "2.2.0", 37 | "karma-jasmine": "5.1.0", 38 | "karma-jasmine-html-reporter": "~1.7.0", 39 | "typescript": "5.5.4" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugin-explorer/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .panel { 2 | background-color: rgba(255, 255, 255, 0.02); 3 | margin-bottom: 1rem; 4 | margin-left: 0; 5 | margin-right: 0; 6 | padding: 1rem; 7 | } 8 | 9 | .progress { 10 | width: 100%; 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | } 15 | 16 | .two-col { 17 | display: flex; 18 | } 19 | 20 | .side { 21 | padding-left: 20px; 22 | min-width: 180px; 23 | } 24 | -------------------------------------------------------------------------------- /plugin-explorer/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 11 | 12 | Search 13 |
14 |
15 | @if (busy) { 16 |
17 | 18 |
19 | } 20 |
21 |

{{ listTitle }}

22 | @for (plugin of plugins; track plugin) { 23 |
24 | 25 |
26 | } 27 |

28 | The most popular open source plugins are tested and indexed. Got a plugin that isnt here? 29 | Submit a suggestion 30 |

31 |
32 |
33 |
34 | 35 | 36 | iOS 37 | Android 38 | Both 39 | Any 40 | 41 |
42 |

Filters

43 | Installed
46 | Official Support
47 | Capacitor Only
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /plugin-explorer/src/app/plugin-info.ts: -------------------------------------------------------------------------------- 1 | export interface Plugin { 2 | name: string; 3 | version: string; 4 | success: string[]; 5 | repo?: string; 6 | keywords?: string[]; 7 | fails: string[]; 8 | description?: string; 9 | quality?: number; 10 | versions: string[]; 11 | platforms: string[]; 12 | author: any; 13 | bugs?: string; 14 | published: string; 15 | downloads?: number; 16 | stars?: number; 17 | image?: string; 18 | updated?: string; 19 | license: string; 20 | title: string; // Calculated 21 | rating: number; // Calculated 22 | ratingInfo: string; // Calculated 23 | tagInfo: string; // Calcuilated 24 | dailyDownloads: string; // Calculated 25 | changed: string; // Calculated 26 | installed: string; // Calculated: whether the plugin is installed in the current project 27 | framework: string | undefined; // Calculated: either capacitor or cordova 28 | moreInfoUrl: string; // Calculated 29 | singlePlatform: string | undefined; // Calculated: either android or apple 30 | } 31 | 32 | export interface PluginInfo { 33 | name: string; 34 | version: string; 35 | latest: string; 36 | } 37 | -------------------------------------------------------------------------------- /plugin-explorer/src/app/plugin.component.css: -------------------------------------------------------------------------------- 1 | vscode-badge { 2 | margin: 0.2rem; 3 | padding: 0.2rem; 4 | border-radius: 0.1rem; 5 | opacity: 0.3; 6 | background: rgba(255, 255, 255, 0.1); 7 | line-height: 2; 8 | } 9 | 10 | vscode-button { 11 | margin-right: 0.5rem; 12 | } 13 | 14 | .subtitle { 15 | font-family: monospace, 'Courier New', Courier; 16 | opacity: 0.5; 17 | margin-top: 0.2rem; 18 | margin-bottom: 0; 19 | font-size: smaller; 20 | } 21 | 22 | .px-group { 23 | display: flex; 24 | flex-direction: row; 25 | justify-content: stretch; 26 | width: 100%; 27 | } 28 | 29 | .side { 30 | min-width: 160px; 31 | justify-content: flex-end; 32 | } 33 | 34 | .author { 35 | vertical-align: middle; 36 | width: 50px; 37 | height: 50px; 38 | object-fit: contain; 39 | border-radius: 50%; 40 | } 41 | 42 | .prof { 43 | min-width: 55px; 44 | height: 55px; 45 | margin-right: 1rem; 46 | display: flex; 47 | justify-content: center; 48 | } 49 | 50 | .panel2 { 51 | width: 100%; 52 | padding-right: 1rem; 53 | } 54 | 55 | .center-title { 56 | display: flex; 57 | align-items: center; 58 | gap: 8px; 59 | } 60 | 61 | .center-image { 62 | height: 100%; 63 | display: flex; 64 | align-items: center; 65 | } 66 | 67 | .wide-tip { 68 | width: 300px !important; 69 | top: 25px; 70 | line-height: 2; 71 | padding: 1rem; 72 | } 73 | -------------------------------------------------------------------------------- /plugin-explorer/src/app/star.component.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA, Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'star', 5 | imports: [], 6 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 7 | template: ` 8 | @for (x of _stars; track x) { 9 | 10 | 13 | 14 | } 15 | @for (x of _misses; track x) { 16 | 17 | 24 | 25 | } 26 | 27 | `, 28 | }) 29 | export class StarComponent { 30 | _stars = [0, 1, 2]; 31 | _misses = [3, 4, 5]; 32 | @Input() set rating(value: number) { 33 | if (!value) { 34 | this._stars = []; 35 | this._misses = [].constructor(5); 36 | } else { 37 | this._stars = [].constructor(value); 38 | this._misses = [].constructor(5 - value); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugin-explorer/src/app/utilities/dom.ts: -------------------------------------------------------------------------------- 1 | export function d(id: string): string { 2 | return document.getElementById(id)?.getAttribute('current-value') as string; 3 | } 4 | 5 | export function checked(id: string): boolean { 6 | const value = document.getElementById(id)?.getAttribute('current-checked'); 7 | return value == 'true'; 8 | } 9 | 10 | export function setChecked(id: string, checked: boolean): void { 11 | document.getElementById(id)?.setAttribute('current-checked', checked ? 'true' : 'false'); 12 | } 13 | -------------------------------------------------------------------------------- /plugin-explorer/src/app/utilities/messages.ts: -------------------------------------------------------------------------------- 1 | import { vscode } from './vscode'; 2 | 3 | export enum MessageType { 4 | getPlugins = 'getPlugins', 5 | getPlugin = 'getPlugin', 6 | getInstalledDeps = 'getInstalledDeps', 7 | chooseVersion = 'choose-version', 8 | } 9 | 10 | export function sendMessage(command: MessageType, value: string) { 11 | vscode.postMessage({ command, text: value }); 12 | } 13 | -------------------------------------------------------------------------------- /plugin-explorer/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/vscode-ionic/058c9a0ab2d9ecaf3be051d1a6a94b7b19b76ae3/plugin-explorer/src/assets/.gitkeep -------------------------------------------------------------------------------- /plugin-explorer/src/assets/android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /plugin-explorer/src/assets/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /plugin-explorer/src/assets/capacitor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /plugin-explorer/src/assets/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /plugin-explorer/src/assets/cordova.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /plugin-explorer/src/assets/star-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /plugin-explorer/src/assets/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugin-explorer/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /plugin-explorer/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /plugin-explorer/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/vscode-ionic/058c9a0ab2d9ecaf3be051d1a6a94b7b19b76ae3/plugin-explorer/src/favicon.ico -------------------------------------------------------------------------------- /plugin-explorer/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Plugin Explorer 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /plugin-explorer/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { 3 | provideVSCodeDesignSystem, 4 | vsCodeButton, 5 | vsCodeCheckbox, 6 | vsCodeDivider, 7 | vsCodeLink, 8 | vsCodePanelView, 9 | vsCodeProgressRing, 10 | vsCodeRadio, 11 | vsCodeRadioGroup, 12 | vsCodeTag, 13 | vsCodeTextField, 14 | } from '@vscode/webview-ui-toolkit'; 15 | import { environment } from './environments/environment'; 16 | import { AppComponent } from './app/app.component'; 17 | import { bootstrapApplication } from '@angular/platform-browser'; 18 | 19 | if (environment.production) { 20 | enableProdMode(); 21 | } 22 | 23 | // In order to use the Webview UI Toolkit web components they 24 | // must be registered with the browser (i.e. webview) using the 25 | // syntax below. 26 | provideVSCodeDesignSystem().register( 27 | vsCodeButton(), 28 | vsCodeTextField(), 29 | vsCodePanelView(), 30 | vsCodeLink(), 31 | vsCodeTag(), 32 | vsCodeCheckbox(), 33 | vsCodeDivider(), 34 | vsCodeRadio(), 35 | vsCodeRadioGroup(), 36 | vsCodeProgressRing(), 37 | ); 38 | 39 | bootstrapApplication(AppComponent).catch((err) => console.error(err)); 40 | -------------------------------------------------------------------------------- /plugin-explorer/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | /*************************************************************************************************** 51 | * APPLICATION IMPORTS 52 | */ 53 | -------------------------------------------------------------------------------- /plugin-explorer/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | main { 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | height: 100%; 7 | } 8 | 9 | h2, 10 | h3 { 11 | margin: 0; 12 | } 13 | 14 | main > * { 15 | margin: 0.5rem 0; 16 | } 17 | 18 | .columns { 19 | display: flex; 20 | width: 100%; 21 | } 22 | 23 | vscode-text-field { 24 | width: 100%; 25 | } 26 | 27 | .tooltip { 28 | position: relative; 29 | display: inline-block; 30 | border-bottom: 1px dotted var(--vscode-editor-background); 31 | } 32 | 33 | .tooltip .tooltiptext { 34 | visibility: hidden; 35 | width: 120px; 36 | background-color: var(--vscode-editor-background); 37 | color: var(--vscode-editor-foreground); 38 | text-align: center; 39 | border-radius: 6px; 40 | padding: 5px 0; 41 | 42 | /* Position the tooltip */ 43 | position: absolute; 44 | z-index: 1; 45 | } 46 | 47 | .small-tooltip { 48 | font-size: smaller !important; 49 | width: 190px; 50 | } 51 | 52 | .ionicon { 53 | width: 16px; 54 | height: 16px; 55 | } 56 | 57 | .tooltip:hover .tooltiptext { 58 | visibility: visible; 59 | } 60 | 61 | p { 62 | line-height: 1.7; 63 | margin: 0; 64 | } 65 | 66 | /* For live editing */ 67 | /* body { 68 | background-color: #2c2c2c; 69 | color: white; 70 | font-family:Arial, Helvetica, sans-serif; 71 | } */ 72 | -------------------------------------------------------------------------------- /plugin-explorer/src/tests.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; 6 | 7 | declare const require: { 8 | context( 9 | path: string, 10 | deep?: boolean, 11 | filter?: RegExp, 12 | ): { 13 | (id: string): T; 14 | keys(): string[]; 15 | }; 16 | }; 17 | 18 | // First, initialize the Angular testing environment. 19 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 20 | 21 | // Then we find all the tests. 22 | const context = require.context('./', true, /\.spec\.ts$/); 23 | // And load the modules. 24 | context.keys().map(context); 25 | -------------------------------------------------------------------------------- /plugin-explorer/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": ["src/main.ts", "src/polyfills.ts"], 9 | "include": ["src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /plugin-explorer/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "skipLibCheck": true, 20 | "target": "es2015", 21 | "module": "es2020", 22 | "lib": ["es2018", "dom"], 23 | }, 24 | "angularCompilerOptions": { 25 | "enableI18nLegacyMessageIdFormat": false, 26 | "strictInjectionParameters": true, 27 | "strictInputAccessModifiers": true, 28 | "strictStandalone": true, 29 | "strictTemplates": true, 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /plugin-explorer/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": ["jasmine"] 7 | }, 8 | "files": ["src/test.ts", "src/polyfills.ts"], 9 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /resources/dark/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/angular.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/dark/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/beaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/dark/box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/build-anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /resources/dark/build.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/dark/capacitor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/checkbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/dark/circle-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/cordova.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/debug-alt-small-anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/dark/debug-alt-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/dark/debug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /resources/dark/dependency.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/dark/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/dark/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/file-media.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/files.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/globe-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 12 | 14 | -------------------------------------------------------------------------------- /resources/dark/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 12 | 14 | -------------------------------------------------------------------------------- /resources/dark/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/ionic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/dark/lightbulb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/live-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /resources/dark/live.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /resources/dark/more.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /resources/dark/nexus-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /resources/dark/nexus.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /resources/dark/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/dark/run-anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 18 | 19 | 20 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /resources/dark/run.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/settings-gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/sync-anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/dark/sync.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/vscode-select.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | -------------------------------------------------------------------------------- /resources/dark/vscode.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | -------------------------------------------------------------------------------- /resources/dark/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/dark/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/angular.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/light/apple.svg: -------------------------------------------------------------------------------- 1 | Logo Apple -------------------------------------------------------------------------------- /resources/light/beaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/light/box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/build-anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /resources/light/build.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/light/capacitor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/checkbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/light/circle-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/cordova.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/debug-alt-small-anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/light/debug-alt-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/light/debug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /resources/light/dependency.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/file-media.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/files.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/globe-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 12 | 14 | -------------------------------------------------------------------------------- /resources/light/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 12 | 14 | -------------------------------------------------------------------------------- /resources/light/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/ionic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/light/lightbulb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/more.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /resources/light/nexus-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /resources/light/nexus.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /resources/light/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/run-anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 18 | 19 | 20 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /resources/light/run.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/settings-gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/light/sync-anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/light/sync.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/light/vscode-select.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | -------------------------------------------------------------------------------- /resources/light/vscode.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | -------------------------------------------------------------------------------- /resources/light/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/light/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/android-debug-list.ts: -------------------------------------------------------------------------------- 1 | import { Command, TreeItemCollapsibleState } from 'vscode'; 2 | import { debugAndroid } from './android-debug'; 3 | import { findDevices, findWebViews } from './android-debug-bridge'; 4 | import { Device, WebView } from './android-debug-models'; 5 | import { CommandName } from './command-name'; 6 | import { ionicState } from './ionic-tree-provider'; 7 | import { Recommendation } from './recommendation'; 8 | import { QueueFunction, Tip, TipType } from './tip'; 9 | 10 | export async function getAndroidWebViewList( 11 | hasCapacitorAndroid: boolean, 12 | wwwFolder: string, 13 | ): Promise { 14 | if (ionicState.refreshDebugDevices) { 15 | ionicState.refreshDebugDevices = false; 16 | } 17 | if (!hasCapacitorAndroid) { 18 | return []; 19 | } 20 | 21 | const result: Array = []; 22 | const devices = await findDevices(); 23 | for (const device of devices) { 24 | const webviews = await findWebViews(device!); 25 | for (const webview of webviews) { 26 | const r = new Recommendation( 27 | `Debug ${webview.packageName} ${webview.versionName} on running Android device ${device.product}`, 28 | `(${device.product})`, 29 | `${webview.packageName}`, 30 | TreeItemCollapsibleState.None, 31 | getCommand(), 32 | undefined, 33 | ); 34 | r.setIcon('debug'); 35 | r.tip = new Tip(undefined, undefined, TipType.Run).setQueuedAction(debug, device, webview, wwwFolder).doNotWait(); 36 | r.command.arguments = [r]; 37 | result.push(r); 38 | } 39 | if (webviews.length == 0) { 40 | const r = new Recommendation( 41 | 'test', 42 | 'No Web View', 43 | device.product, 44 | TreeItemCollapsibleState.None, 45 | getCommand(), 46 | undefined, 47 | ); 48 | r.setIcon('android'); 49 | result.push(r); 50 | } 51 | } 52 | return result; 53 | } 54 | 55 | async function debug(queueFunction: QueueFunction, device: Device, webview: WebView, wwwfolder: string): Promise { 56 | queueFunction(); 57 | debugAndroid(webview.packageName, wwwfolder); 58 | return; 59 | } 60 | 61 | function getCommand(): Command { 62 | return { 63 | command: CommandName.Function, 64 | title: 'Open', 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /src/android-debug-models.ts: -------------------------------------------------------------------------------- 1 | export interface WebView { 2 | device: Device; 3 | socket: string; 4 | type: WebViewType; 5 | packageName?: string; 6 | versionName?: string; 7 | } 8 | 9 | export enum WebViewType { 10 | chrome = 'chrome', 11 | webview = 'webview', 12 | crosswalk = 'crosswalk', 13 | unknown = 'unknown', 14 | } 15 | 16 | export type DeviceState = 17 | | 'device' 18 | | 'connecting' 19 | | 'offline' 20 | | 'unknown' 21 | | 'bootloader' 22 | | 'recovery' 23 | | 'download' 24 | | 'unauthorized' 25 | | 'host' 26 | | 'no permissions'; 27 | 28 | export interface Device { 29 | serial: string; 30 | state: DeviceState; 31 | usb?: string; 32 | product?: string; 33 | model?: string; 34 | device?: string; 35 | features?: string; 36 | transportId?: string; 37 | } 38 | 39 | export interface ForwardedSocket { 40 | local: string; 41 | remote: string; 42 | } 43 | 44 | export interface AdbOptions { 45 | executable: string; 46 | arguments: string[]; 47 | } 48 | 49 | export interface ShellOptions extends AdbOptions { 50 | serial: string; 51 | command: string; 52 | } 53 | 54 | export interface ForwardOptions extends AdbOptions { 55 | serial: string; 56 | local: string; 57 | remote: string; 58 | } 59 | 60 | export interface UnforwardOptions extends AdbOptions { 61 | local: string; 62 | } 63 | 64 | export interface Process { 65 | pid: number; 66 | name: string; 67 | } 68 | 69 | export interface Package { 70 | packageName: string; 71 | versionName: string; 72 | } 73 | 74 | export interface WebViewPage { 75 | url: string; 76 | title: string; 77 | webSocketDebuggerUrl: string; 78 | } 79 | -------------------------------------------------------------------------------- /src/android-debug.ts: -------------------------------------------------------------------------------- 1 | import { debug, workspace } from 'vscode'; 2 | import { startSourceMapServer } from './source-map-server'; 3 | import { debugSkipFiles } from './utilities'; 4 | import { ionicState } from './ionic-tree-provider'; 5 | 6 | // The debug provider type for VS Code 7 | export const AndroidDebugType = 'android-web'; 8 | 9 | export function debugAndroid(packageName: string, wwwFolder: string) { 10 | // Source maps are required for debugging. These are loaded from where the app is 11 | // loaded (eg http://localhost) so we're running a source map server to deliver them 12 | // An alternative includes inlining the source maps. 13 | 14 | // Inlining source maps: 15 | // https://github.com/ionic-team/ionic-framework/issues/16455#issuecomment-505397373 16 | 17 | // Solution: https://ionic.zendesk.com/hc/en-us/articles/5177027959319 18 | 19 | // See this location for options for debugging that are supported 20 | // https://github.com/microsoft/vscode-js-debug/blob/main/OPTIONS.md#pwa-chrome-attach 21 | 22 | // Note: options here include sourceMapPathOverrides and resolveSourceMapLocations both dont fix the 23 | // problem with source maps not being accessible to the debugger 24 | ionicState.debugged = true; 25 | debug.startDebugging(workspace.workspaceFolders[0], { 26 | type: AndroidDebugType, 27 | name: 'Debug Android', 28 | request: 'attach', 29 | packageName: packageName, 30 | webRoot: '${workspaceFolder}', 31 | skipFiles: debugSkipFiles(), 32 | }); 33 | 34 | startSourceMapServer(wwwFolder); 35 | } 36 | -------------------------------------------------------------------------------- /src/angular-generate.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './project'; 2 | 3 | import { getRunOutput, getStringFrom, openUri, replaceAll } from './utilities'; 4 | import { write, writeError, writeIonic } from './logging'; 5 | import { join } from 'path'; 6 | import { existsSync } from 'fs'; 7 | import { isGreaterOrEqual } from './analyzer'; 8 | import { window } from 'vscode'; 9 | import { QueueFunction } from './tip'; 10 | import { ionicState } from './ionic-tree-provider'; 11 | import { npx } from './node-commands'; 12 | import { checkAngularJson } from './rules-angular-json'; 13 | 14 | export async function angularGenerate( 15 | queueFunction: QueueFunction, 16 | project: Project, 17 | angularType: string, 18 | ): Promise { 19 | let name = await window.showInputBox({ 20 | title: `New Angular ${angularType}`, 21 | placeHolder: `Enter name for new ${angularType}`, 22 | }); 23 | 24 | if (!name || name.length < 1) return; 25 | queueFunction(); 26 | 27 | // CREATE src/app/test2/test2.component.ts 28 | try { 29 | let args = ''; 30 | if (isGreaterOrEqual('@angular/core', '15.0.0')) { 31 | if (isGreaterOrEqual('@ionic/angular-toolkit', '8.1.0')) { 32 | if (angularType == 'page') { 33 | args += ' --standalone'; 34 | } 35 | } 36 | if (isGreaterOrEqual('@ionic/angular-toolkit', '11.0.1')) { 37 | if (angularType == 'component') { 38 | args += ' --standalone'; 39 | } 40 | } 41 | } 42 | name = replaceAll(name, ' ', '-').trim(); 43 | writeIonic(`Creating Angular ${angularType} named ${name}..`); 44 | checkAngularJson(project); 45 | const angularProjectName = ionicState.project ?? 'app'; 46 | // eg ng generate page page-a --standalone --project=app 47 | let cmd = `${npx(project)} ng generate ${angularType} ${name}${args} --project=${angularProjectName}`; 48 | if (angularType == 'directive') { 49 | cmd += ` --skip-import`; 50 | } 51 | write(`> ${cmd}`); 52 | const out = await getRunOutput(cmd, project.projectFolder()); 53 | write(out); 54 | const src = getStringFrom(out, 'CREATE ', '.ts'); 55 | const path = join(project.projectFolder(), src + '.ts'); 56 | if (!src || !existsSync(path)) { 57 | writeError(`Failed to create Angular ${angularType} named ${name}`); 58 | } else { 59 | writeIonic(`Created Angular ${angularType} named ${name}`); 60 | await openUri(path); 61 | } 62 | } catch (err) { 63 | writeError(`Unable to generate Angular ${angularType} named ${name}: ${err}`); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/build-configuration.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './project'; 2 | import { ionicState } from './ionic-tree-provider'; 3 | import { exists } from './analyzer'; 4 | import { ExtensionContext, window } from 'vscode'; 5 | import { existsSync, readFileSync } from 'fs'; 6 | import { join } from 'path'; 7 | 8 | export function getConfigurationName(): string { 9 | if (!ionicState.configuration || ionicState.configuration == 'default') { 10 | return ''; 11 | } else { 12 | return `(${ionicState.configuration})`; 13 | } 14 | } 15 | 16 | export function getConfigurationArgs(isDebugging?: boolean): string { 17 | let config = ionicState.configuration; 18 | if (isDebugging) { 19 | // If we are debugging and its an Angular project without a selected build config 20 | // then choose "development" so that source maps work 21 | if (config == 'production') { 22 | config = 'development'; // Assume we have this configuration 23 | } 24 | } 25 | if (!config || config == 'default') { 26 | return ''; 27 | } else { 28 | if (exists('vue') || exists('react')) { 29 | return ` --mode=${config}`; 30 | } else { 31 | return ` --configuration=${config}`; 32 | } 33 | } 34 | } 35 | 36 | export async function buildConfiguration(folder: string, context: ExtensionContext, project: Project): Promise { 37 | let configs = []; 38 | const filename = join(project.projectFolder(), 'angular.json'); 39 | if (existsSync(filename)) { 40 | configs = getAngularBuildConfigs(filename); 41 | } 42 | if (exists('vue') || exists('react')) { 43 | configs = ['development', 'production']; 44 | } 45 | if (configs.length == 0) { 46 | window.showInformationMessage('No build configurations found in this project'); 47 | return; 48 | } 49 | configs.unshift('default'); 50 | const selection = window.showQuickPick(configs, { placeHolder: 'Select a build configuration to use' }); 51 | return selection; 52 | } 53 | 54 | function getAngularBuildConfigs(filename: string): Array { 55 | try { 56 | const result = []; 57 | const angular = JSON.parse(readFileSync(filename, 'utf8')); 58 | for (const config of Object.keys(angular.projects.app.architect.build.configurations)) { 59 | result.push(config); 60 | } 61 | return result; 62 | } catch { 63 | return []; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/cap-project.ts: -------------------------------------------------------------------------------- 1 | export interface CapacitorProjectState { 2 | iosBundleId?: string; 3 | androidBundleId?: string; 4 | iosVersion?: string; 5 | androidVersion?: string; 6 | iosBuild?: number; 7 | androidBuild?: number; 8 | iosDisplayName?: string; 9 | androidDisplayName?: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/capacitor-add.ts: -------------------------------------------------------------------------------- 1 | import { exists } from './analyzer'; 2 | import { CapacitorPlatform } from './capacitor-platform'; 3 | import { useIonicCLI } from './capacitor-run'; 4 | import { InternalCommand } from './command-name'; 5 | import { MonoRepoType } from './monorepo'; 6 | import { npx } from './node-commands'; 7 | import { Project } from './project'; 8 | 9 | /** 10 | * Add a Capacitor Platform 11 | * @param {Project} project 12 | * @param {CapacitorPlatform} platform 13 | * @returns string 14 | */ 15 | export function capacitorAdd(project: Project, platform: CapacitorPlatform): string { 16 | const ionic = useIonicCLI() ? 'ionic ' : ''; 17 | switch (project.repoType) { 18 | case MonoRepoType.none: 19 | return `${npx(project)} ${ionic}cap add ${platform}`; 20 | case MonoRepoType.npm: 21 | case MonoRepoType.yarn: 22 | case MonoRepoType.lerna: 23 | case MonoRepoType.folder: 24 | case MonoRepoType.pnpm: 25 | return `${InternalCommand.cwd}${npx(project)} ${ionic}cap add ${platform}`; 26 | case MonoRepoType.nx: 27 | return nxAdd(project, platform); 28 | default: 29 | throw new Error('Unsupported Monorepo type'); 30 | } 31 | } 32 | 33 | function nxAdd(project: Project, platform: CapacitorPlatform): string { 34 | return `${npx(project)} nx run ${project.monoRepo.name}:add:${platform}`; 35 | } 36 | -------------------------------------------------------------------------------- /src/capacitor-platform.ts: -------------------------------------------------------------------------------- 1 | export enum CapacitorPlatform { 2 | ios = 'ios', 3 | android = 'android', 4 | } 5 | -------------------------------------------------------------------------------- /src/capacitor-pwa.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { Project } from './project'; 3 | import { ActionResult } from './command-name'; 4 | import { ignore } from './ignore'; 5 | import { ionicState } from './ionic-tree-provider'; 6 | import { QueueFunction, Tip } from './tip'; 7 | import { runCommands } from './advanced-actions'; 8 | import { npx } from './node-commands'; 9 | 10 | export async function integratePWA(queueFunction: QueueFunction, project: Project, tip: Tip): Promise { 11 | const result = await window.showInformationMessage( 12 | `Progressive Web Application (PWA) Integration - This will add @angular/pwa to your project and make changes in your project to make it a PWA (manifest file, splash screen and icon resources).`, 13 | 'Apply Changes', 14 | 'Ignore', 15 | ); 16 | if (result == 'Ignore') { 17 | ignore(tip, ionicState.context); 18 | return; 19 | } 20 | if (!result) { 21 | return; 22 | } 23 | queueFunction(); 24 | await runCommands( 25 | [`${npx(project)} ng add @angular/pwa --defaults --skip-confirmation true`], 26 | 'Adding @angular/pwa', 27 | project, 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/capacitor-sync.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './project'; 2 | import { MonoRepoType } from './monorepo'; 3 | import { exists, isGreaterOrEqual } from './analyzer'; 4 | import { InternalCommand } from './command-name'; 5 | import { npx, PackageManager, preflightNPMCheck } from './node-commands'; 6 | import { getConfigurationArgs } from './build-configuration'; 7 | import { useIonicCLI } from './capacitor-run'; 8 | 9 | /** 10 | * Creates the capacitor sync command 11 | * @param {Project} project 12 | * @returns string 13 | */ 14 | export async function capacitorSync(project: Project): Promise { 15 | const preop = preflightNPMCheck(project); 16 | 17 | const ionicCLI = useIonicCLI(); 18 | switch (project.repoType) { 19 | case MonoRepoType.none: 20 | return preop + (ionicCLI ? ionicCLISync(project) : capCLISync(project)); 21 | case MonoRepoType.folder: 22 | case MonoRepoType.pnpm: 23 | case MonoRepoType.lerna: 24 | case MonoRepoType.yarn: 25 | case MonoRepoType.npm: 26 | return InternalCommand.cwd + preop + (ionicCLI ? ionicCLISync(project) : capCLISync(project)); 27 | case MonoRepoType.nx: 28 | return preop + nxSync(project); 29 | default: 30 | throw new Error('Unsupported Monorepo type'); 31 | } 32 | } 33 | 34 | function capCLISync(project: Project): string { 35 | if (isGreaterOrEqual('@capacitor/cli', '4.1.0')) { 36 | return `${npx(project)} cap sync --inline`; 37 | } 38 | return `${npx(project)} cap sync${getConfigurationArgs()}`; 39 | } 40 | 41 | function ionicCLISync(project: Project): string { 42 | return `${npx(project)} ionic cap sync --inline${getConfigurationArgs()}`; 43 | } 44 | 45 | function nxSync(project: Project): string { 46 | if (project.monoRepo.isNXStandalone) { 47 | return capCLISync(project); 48 | } 49 | return `${npx(project)} nx sync ${project.monoRepo.name}${getConfigurationArgs()}`; 50 | } 51 | -------------------------------------------------------------------------------- /src/command-title.ts: -------------------------------------------------------------------------------- 1 | export enum CommandTitle { 2 | OpenInXCode = 'Open in Xcode', 3 | OpenInAndroidStudio = 'Open in Android Studio', 4 | RunForIOS = 'iOS', 5 | RunForAndroid = 'Android', 6 | Sync = 'Sync', 7 | RunForWeb = 'Web', 8 | } 9 | -------------------------------------------------------------------------------- /src/context-variables.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './project'; 2 | 3 | export enum Context { 4 | // Whether the project has been inspected (true) or not (false) 5 | inspectedProject = 'inspectedProject', 6 | 7 | // Whether the user has clicked Login (true) 8 | isLoggingIn = 'isLoggingIn', 9 | 10 | // Whether the current user is not known (true) 11 | isAnonymous = 'isAnonymous', 12 | 13 | // VS Code hasnt opened a folder 14 | noProjectFound = 'noProjectFound', 15 | 16 | // Used for splash screen assets that can be viewed 17 | asset = 'asset', 18 | 19 | // The panel for monorepo projects 20 | isMonoRepo = 'isMonoRepo', 21 | 22 | // The panel for the running dev server 23 | isDevServing = 'isDevServing', 24 | 25 | // A scope that can be upgraded 26 | upgrade = 'upgrade', 27 | 28 | // Upgrade options 29 | lightbulb = 'lightbulb', 30 | 31 | // Live Reload 32 | liveReload = 'liveReload', 33 | 34 | // Stop option 35 | stop = 'stop', 36 | 37 | // Build configuration 38 | buildConfig = 'buildConfig', 39 | 40 | // Web configuration 41 | webConfig = 'webConfig', 42 | 43 | // Web Debug configuration 44 | webDebugConfig = 'webDebugConfig', 45 | 46 | // Select Action 47 | selectAction = 'selectAction', 48 | 49 | // Device selection 50 | selectDevice = 'selectDevice', 51 | 52 | // Shell (eg /bin/zsh) 53 | shell = 'shell', 54 | 55 | // Rebuild used for splash screen 56 | rebuild = 'rebuild', 57 | 58 | // Refresh used for debug instances 59 | refreshDebug = 'refreshDebug', 60 | } 61 | 62 | // Commands from vs code 63 | export enum VSCommand { 64 | setContext = 'setContext', 65 | } 66 | 67 | export function PackageCacheOutdated(project: Project) { 68 | if (project?.monoRepo?.localPackageJson) { 69 | return 'npmOutdatedData_' + project.monoRepo.name; 70 | } 71 | return 'npmOutdatedData'; 72 | } 73 | 74 | export function PackageCacheList(project: Project) { 75 | if (project?.monoRepo?.localPackageJson) { 76 | return 'npmListData_' + project.monoRepo.name; 77 | } 78 | return 'npmListData'; 79 | } 80 | 81 | export function CapProjectCache(project: Project) { 82 | if (project?.monoRepo?.localPackageJson) { 83 | return 'CapacitorProject_' + project.monoRepo.name; 84 | } 85 | return 'CapacitorProject'; 86 | } 87 | 88 | export function PackageCacheModified(project: Project) { 89 | if (project?.monoRepo?.localPackageJson) { 90 | return 'packagesModified_' + project.monoRepo.name; 91 | } 92 | return 'packagesModified'; 93 | } 94 | 95 | export const LastManifestCheck = 'LastManifestCheck'; 96 | -------------------------------------------------------------------------------- /src/features.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { GlobalSetting, getGlobalSetting, setGlobalSetting } from './workspace-state'; 3 | import { alt } from './utilities'; 4 | 5 | // Feature Flags for experimental options 6 | export const Features = { 7 | debugAndroid: true, // Whether debugging for Android is turned on 8 | pluginExplorer: true, // Whether the plugin explorer is shown 9 | requireLogin: false, // Whether we require the user to be logged in via "ionic login" 10 | }; 11 | 12 | export function showTips() { 13 | const tips = getGlobalSetting(GlobalSetting.lastTipsShown); 14 | const shownAt = tips ? Date.parse(tips) : 0; 15 | const days = (new Date().getTime() - shownAt) / (1000 * 3600 * 24); 16 | if (days > 30) { 17 | window.showInformationMessage(`Ionic Tip: Press ${alt('D')} to debug your app and ${alt('R')} to run it!`, 'OK'); 18 | setGlobalSetting(GlobalSetting.lastTipsShown, new Date().toISOString()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/gradle-to-json.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, readFileSync } from 'fs'; 2 | import { replaceAll } from './utilities'; 3 | 4 | export function gradleToJson(filename: string): any | undefined { 5 | if (!existsSync(filename)) { 6 | return undefined; 7 | } 8 | try { 9 | const lines = readFileSync(filename, 'utf8').split('\n'); 10 | const result = {}; 11 | let at = result; 12 | const stack = [at]; 13 | for (const line of lines) { 14 | if (line.trim().endsWith('{')) { 15 | const key = replaceAll(line, '{', '').trim(); 16 | at[key] = {}; 17 | stack.push(at); 18 | at = at[key]; 19 | } else if (line.trim().endsWith('}')) { 20 | at = stack.pop(); 21 | } else if (line.trim() !== '') { 22 | const kv = line.trim().split(' '); 23 | if (kv.length == 2) { 24 | at[kv[0]] = kv[1]; 25 | } else { 26 | at[kv[0]] = []; 27 | for (let i = 1; i < kv.length; i++) { 28 | at[kv[0]].push(kv[i]); 29 | } 30 | } 31 | } 32 | } 33 | return result; 34 | } catch { 35 | return undefined; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ignore.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | import { Tip } from './tip'; 3 | 4 | /** 5 | * Allows recommendations to be ignored. We need to store the recommendation text from the tip 6 | * @param {Tip} tip 7 | * @param {vscode.ExtensionContext} context 8 | */ 9 | export function ignore(tip: Tip, context: ExtensionContext) { 10 | const key = 'ignoredRecommendations'; 11 | const txt = `${tip.message}+${tip.title}`; 12 | const listJSON: string = context.workspaceState.get(key); 13 | let list = []; 14 | 15 | if (listJSON) { 16 | list = JSON.parse(listJSON); 17 | } 18 | if (!list.includes(txt)) { 19 | list.push(txt); 20 | } 21 | 22 | context.workspaceState.update(key, JSON.stringify(list)); 23 | } 24 | 25 | export function getIgnored(context: ExtensionContext): Array { 26 | const key = 'ignoredRecommendations'; 27 | const listJSON: string = context.workspaceState.get(key); 28 | let list = []; 29 | try { 30 | list = JSON.parse(listJSON); 31 | return list; 32 | } catch { 33 | return []; 34 | } 35 | } 36 | 37 | export function clearIgnored(context: ExtensionContext) { 38 | const key = 'ignoredRecommendations'; 39 | context.workspaceState.update(key, undefined); 40 | } 41 | 42 | export function excludeIgnoredTips(tips: Array, context: ExtensionContext): Array { 43 | const key = 'ignoredRecommendations'; 44 | const listJSON: string = context.workspaceState.get(key); 45 | let list = []; 46 | 47 | if (listJSON) { 48 | try { 49 | list = JSON.parse(listJSON); 50 | return tips.filter((tip) => { 51 | return tip && !list.includes(`${tip.message}+${tip.title}`); 52 | }); 53 | } catch { 54 | context.workspaceState.update(key, '[]'); 55 | return tips; 56 | } 57 | } else { 58 | return tips; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ionic-auth.ts: -------------------------------------------------------------------------------- 1 | import { Context, VSCommand } from './context-variables'; 2 | import { ionicState } from './ionic-tree-provider'; 3 | import { sendTelemetryEvent, TelemetryEventType } from './telemetry'; 4 | import { writeAppend } from './logging'; 5 | import { ExtensionContext, ExtensionKind, UIKind, commands, env, window } from 'vscode'; 6 | import { join } from 'path'; 7 | import { ExecException, exec } from 'child_process'; 8 | 9 | /** 10 | * ionic login and signup commands 11 | * @param {string} folder 12 | * @param {vscode.ExtensionContext} context 13 | */ 14 | export async function ionicLogin(folder: string, context: ExtensionContext) { 15 | const ifolder = join(folder, 'node_modules', '@ionic', 'cli', 'bin'); 16 | try { 17 | if (env.uiKind == UIKind.Web) { 18 | window.showErrorMessage( 19 | 'The Codespaces browser editor has limited functionality. Click "Next" to continue.', 20 | 'Next', 21 | ); 22 | ionicState.skipAuth = true; 23 | await commands.executeCommand(VSCommand.setContext, Context.isAnonymous, false); 24 | return; 25 | } 26 | await run(`npx ionic login --confirm`, ifolder); 27 | sendTelemetryEvent(folder, TelemetryEventType.Login, context); 28 | } catch (err) { 29 | window.showErrorMessage(err); 30 | ionicState.skipAuth = true; 31 | await commands.executeCommand(VSCommand.setContext, Context.isAnonymous, false); 32 | } 33 | } 34 | 35 | export async function ionicSignup(folder: string, context: ExtensionContext) { 36 | const ifolder = join(folder, 'node_modules', '@ionic', 'cli', 'bin'); 37 | await run('npx ionic signup', ifolder); 38 | sendTelemetryEvent(folder, TelemetryEventType.SignUp, context); 39 | } 40 | 41 | async function run(command: string, folder: string): Promise { 42 | return new Promise((resolve, reject) => { 43 | let out = ''; 44 | const cmd = exec(command, { cwd: folder }, (error: ExecException, stdout: string, stderror: string) => { 45 | if (stdout) { 46 | out += stdout; 47 | writeAppend(out); 48 | } 49 | if (!error) { 50 | writeAppend(out); 51 | resolve(out); 52 | } else { 53 | if (stderror) { 54 | reject(stderror); 55 | } else { 56 | resolve(out); 57 | } 58 | } 59 | }); 60 | cmd.stdin.pipe(process.stdin); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/ionic-devserver-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CancellationToken, 3 | ExtensionContext, 4 | WebviewView, 5 | WebviewViewProvider, 6 | WebviewViewResolveContext, 7 | } from 'vscode'; 8 | 9 | import { commands } from 'vscode'; 10 | import { CommandName } from './command-name'; 11 | import { qrWebView } from './nexus-browser'; 12 | 13 | export class IonicDevServerProvider implements WebviewViewProvider { 14 | registered = false; 15 | constructor( 16 | private workspaceRoot: string | undefined, 17 | private context: ExtensionContext, 18 | ) {} 19 | 20 | resolveWebviewView(webviewView: WebviewView, context: WebviewViewResolveContext, token: CancellationToken) { 21 | if (this.registered) return; 22 | this.registered = true; 23 | commands.registerCommand(CommandName.ViewDevServer, (url: string) => { 24 | const shortUrl = qrWebView(webviewView.webview, url); 25 | //webviewView.description = shortUrl; 26 | webviewView.show(true); 27 | }); 28 | 29 | commands.registerCommand(CommandName.hideDevServer, () => { 30 | // THERE IS NO API TO HIDE/COLLAPSE A VIEW 31 | const shortUrl = qrWebView(webviewView.webview, undefined); 32 | //webviewView.show(true); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ionic-projects-provider.ts: -------------------------------------------------------------------------------- 1 | import { Event, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, TreeItemCollapsibleState } from 'vscode'; 2 | import { CommandName } from './command-name'; 3 | import { ionicState } from './ionic-tree-provider'; 4 | import { Recommendation } from './recommendation'; 5 | 6 | export class IonicProjectsreeProvider implements TreeDataProvider { 7 | private _onDidChangeTreeData: EventEmitter = new EventEmitter< 8 | Recommendation | undefined | void 9 | >(); 10 | readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; 11 | constructor( 12 | private workspaceRoot: string | undefined, 13 | private context: ExtensionContext, 14 | ) {} 15 | 16 | selectedProject: string; 17 | 18 | refresh(project: string): void { 19 | ionicState.workspace = project; 20 | this.selectedProject = project; 21 | this._onDidChangeTreeData.fire(); 22 | } 23 | 24 | getTreeItem(element: Recommendation): TreeItem { 25 | return element; 26 | } 27 | 28 | getChildren(element?: Recommendation): Thenable { 29 | return Promise.resolve(this.projectList()); 30 | } 31 | 32 | projectList(): Array { 33 | const list = []; 34 | for (const project of ionicState.projects) { 35 | const cmd = { 36 | command: CommandName.ProjectSelect, 37 | title: 'Open', 38 | arguments: [project.name], 39 | }; 40 | const r = new Recommendation(project.folder, undefined, project.name, TreeItemCollapsibleState.None, cmd); 41 | const icon = project.name == this.selectedProject ? 'circle-filled' : 'none'; 42 | r.setIcon(icon); 43 | list.push(r); 44 | } 45 | return list; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ionic-start-templates.ts: -------------------------------------------------------------------------------- 1 | // You can paste these by running npx ionic start -l 2 | export const ionicTemplates = ` 3 | Starters for @ionic/vue (--type=vue) 4 | 5 | name | description 6 | ---------------------------------------------------------------------------------- 7 | tabs | A starting project with a simple tabbed interface 8 | sidemenu | A starting project with a side menu with navigation in the content area 9 | blank | A blank starter project 10 | list | A starting project with a list 11 | 12 | 13 | Starters for @ionic/angular (--type=angular) 14 | 15 | name | description 16 | -------------------------------------------------------------------------------------- 17 | tabs | A starting project with a simple tabbed interface 18 | sidemenu | A starting project with a side menu with navigation in the content area 19 | blank | A blank starter project 20 | list | A starting project with a list 21 | my-first-app | A template for the "Build Your First App" tutorial 22 | 23 | 24 | Starters for angular-standalone (--type=angular-standalone) 25 | 26 | name | description 27 | -------------------------------------------------------------------------------------- 28 | tabs | A starting project with a simple tabbed interface 29 | sidemenu | A starting project with a side menu with navigation in the content area 30 | blank | A blank starter project 31 | list | A starting project with a list 32 | my-first-app | A template for the "Build Your First App" tutorial 33 | 34 | 35 | Starters for @ionic/react (--type=react) 36 | 37 | name | description 38 | -------------------------------------------------------------------------------------- 39 | blank | A blank starter project 40 | list | A starting project with a list 41 | my-first-app | A template for the "Build Your First App" tutorial 42 | sidemenu | A starting project with a side menu with navigation in the content area 43 | tabs | A starting project with a simple tabbed interface 44 | `; 45 | -------------------------------------------------------------------------------- /src/logging.ts: -------------------------------------------------------------------------------- 1 | import { OutputChannel, window } from 'vscode'; 2 | 3 | let channel: OutputChannel = undefined; 4 | 5 | function getOutputChannel(): OutputChannel { 6 | if (!channel) { 7 | channel = window.createOutputChannel('Ionic'); 8 | channel.show(); 9 | } 10 | return channel; 11 | } 12 | 13 | export function clearOutput(): OutputChannel { 14 | const channel = getOutputChannel(); 15 | channel.clear(); 16 | channel.show(); 17 | return channel; 18 | } 19 | 20 | export function showOutput() { 21 | const channel = getOutputChannel(); 22 | channel.show(); 23 | } 24 | 25 | export function write(message: string) { 26 | getOutputChannel().appendLine(message); 27 | } 28 | 29 | export function writeAppend(message: string) { 30 | getOutputChannel().append(message); 31 | } 32 | 33 | export function writeIonic(message: string) { 34 | const channel = getOutputChannel(); 35 | channel.appendLine(`[Ionic] ${message}`); 36 | } 37 | 38 | export function writeError(message: string) { 39 | const channel = getOutputChannel(); 40 | channel.appendLine(`[error] ${message}`); 41 | } 42 | 43 | export function writeWarning(message: string) { 44 | const channel = getOutputChannel(); 45 | channel.appendLine(`[warning] ${message}`); 46 | } 47 | -------------------------------------------------------------------------------- /src/messages.ts: -------------------------------------------------------------------------------- 1 | import { npmInstall } from './node-commands'; 2 | import { Command, Tip, TipType } from './tip'; 3 | 4 | export const error = (title: string, str: string): Tip => { 5 | return new Tip(title, str, TipType.Error, str, Command.NoOp, 'OK').canIgnore(); 6 | }; 7 | 8 | export const libString = (lib: string, ver: string) => { 9 | const vstr = ver ? ` (${ver})` : ''; 10 | return `${lib}${vstr}`; 11 | }; 12 | 13 | export const writeMinVersionError = (library: string, version: string, minVersion: string, reason: string): Tip => { 14 | return new Tip( 15 | library, 16 | `${library} must be upgraded from ${version} to at least version ${minVersion}${reason ? ' ' + reason : ''}`, 17 | TipType.Error, 18 | undefined, 19 | npmInstall(library + '@latest'), 20 | `Upgrade`, 21 | `${library} successfully updated.`, 22 | ).canIgnore(); 23 | }; 24 | export const writeMinVersionWarning = ( 25 | library: string, 26 | version: string, 27 | minVersion: string, 28 | reason: string, 29 | url?: string, 30 | ): Tip => { 31 | let r = reason ? ' ' + reason : ''; 32 | if (url) r = `[${reason}](${url})`; 33 | return new Tip( 34 | library, 35 | `Update to at least ${minVersion}${reason ? ' ' + reason : ''}`, 36 | TipType.Idea, 37 | `${library} ${version} should be updated to at least ${minVersion}${reason ? ' ' + reason : ''}`, 38 | npmInstall(`${library}@latest`), 39 | `Upgrade`, 40 | `${library} successfully updated.`, 41 | ).canIgnore(); 42 | }; 43 | 44 | export const writeConsistentVersionWarning = (lib1: string, ver1: string, lib2: string, ver2: string) => { 45 | return new Tip( 46 | lib2, 47 | `Version of ${libString(lib2, ver2)} should match ${libString(lib1, ver1)}`, 48 | TipType.Error, 49 | undefined, 50 | npmInstall(`${lib2}@${ver1}`), 51 | `Upgrade`, 52 | `${lib2} successfully updated.`, 53 | ).canIgnore(); 54 | }; 55 | 56 | export const writeConsistentVersionError = (lib1: string, ver1: string, lib2: string, ver2: string): Tip => { 57 | return new Tip( 58 | lib2, 59 | `Version of ${libString(lib2, ver2)} must match ${libString(lib1, ver1)}`, 60 | TipType.Error, 61 | undefined, 62 | npmInstall(`${lib2}@${ver1}`), 63 | `Upgrade`, 64 | `${lib2} successfully updated.`, 65 | ).canIgnore(); 66 | }; 67 | -------------------------------------------------------------------------------- /src/monorepos-lerna.ts: -------------------------------------------------------------------------------- 1 | import * as globule from 'globule'; 2 | 3 | import { MonoRepoProject } from './monorepo'; 4 | import { Project } from './project'; 5 | import { existsSync, readFileSync } from 'fs'; 6 | import { basename, join } from 'path'; 7 | 8 | export function getLernaWorkspaces(project: Project): Array { 9 | const lernaFile = join(project.folder, 'lerna.json'); 10 | if (!existsSync(lernaFile)) { 11 | return []; 12 | } 13 | 14 | try { 15 | const json = readFileSync(lernaFile, { encoding: 'utf8' }); 16 | const lerna = JSON.parse(json); 17 | const list = []; 18 | for (const folder of lerna.packages) { 19 | list.push(folder); 20 | } 21 | const folders = globule.find({ src: list, srcBase: project.folder }); 22 | const repos: Array = []; 23 | for (const folder of folders) { 24 | repos.push({ folder: join(project.folder, folder), name: basename(folder) }); 25 | } 26 | return repos; 27 | } catch (err) { 28 | console.error(err); 29 | return []; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/monorepos-npm.ts: -------------------------------------------------------------------------------- 1 | import * as globule from 'globule'; 2 | 3 | import { MonoRepoProject } from './monorepo'; 4 | import { Project } from './project'; 5 | import { basename, join } from 'path'; 6 | 7 | /** 8 | * Get mono repo project list when using npm workspaces 9 | * @param {Project} project 10 | * @returns Array of Mono Repo Projects 11 | */ 12 | export function getNpmWorkspaceProjects(project: Project): Array { 13 | const result: Array = []; 14 | const folders = globule.find({ src: project.workspaces, srcBase: project.folder }); 15 | for (const folder of folders) { 16 | result.push({ name: basename(folder), folder: join(project.folder, folder) }); 17 | } 18 | return result; 19 | } 20 | -------------------------------------------------------------------------------- /src/monorepos-pnpm.ts: -------------------------------------------------------------------------------- 1 | import * as globule from 'globule'; 2 | 3 | import { MonoRepoProject } from './monorepo'; 4 | import { replaceAll } from './utilities'; 5 | import { Project } from './project'; 6 | import { existsSync, readFileSync } from 'fs'; 7 | import { basename, join } from 'path'; 8 | 9 | export function getPnpmWorkspaces(project: Project): Array { 10 | const pw = join(project.folder, 'pnpm-workspace.yaml'); 11 | if (!existsSync(pw)) { 12 | return []; 13 | } 14 | const yaml = readFileSync(pw, { encoding: 'utf8' }); 15 | try { 16 | const list = []; 17 | for (const line of yaml.split('\n')) { 18 | if (line.trim().startsWith('-')) { 19 | let folder = line.replace('-', '').trim(); 20 | folder = replaceAll(folder, '"', ''); 21 | folder = replaceAll(folder, `'`, ''); 22 | list.push(folder); 23 | // packages/* 24 | // '.' 25 | // */** 26 | // '!devtool/**' 27 | // '!docs/**' 28 | // '!examples/**' 29 | } 30 | } 31 | const folders = globule.find({ src: list, srcBase: project.folder }); 32 | const repos: Array = []; 33 | for (const folder of folders) { 34 | repos.push({ folder: join(project.folder, folder), name: basename(folder) }); 35 | } 36 | return repos; 37 | } catch (err) { 38 | console.error(err); 39 | return []; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/npm-info-data.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the API response from https://registry.npmjs.org/${name}/latest or https://registry.npmjs.org/${name} 3 | */ 4 | export interface NpmInfo { 5 | _id: string; 6 | _rev: string; 7 | name: string; 8 | 'dist-tags': DistTags; 9 | versions: string[] | undefined; 10 | time: any; 11 | created: string; 12 | maintainers: string[]; 13 | description: string; 14 | homepage: string; 15 | keywords: string[]; 16 | repository: Repository; 17 | author: { name: string; email?: string; url?: string } | string; 18 | bugs: Bugs; 19 | license: { type: string; url?: string } | string; 20 | readmeFilename: string; 21 | _cached: boolean; 22 | _contentLength: number; 23 | version: string; 24 | main: string; 25 | module: string; 26 | types: string; 27 | unpkg: string; 28 | scripts: any; 29 | devDependencies: any; 30 | peerDependencies: any; 31 | dependencies: any; 32 | prettier: string; 33 | swiftlint: string; 34 | gitHead: string; 35 | engines: any; 36 | _nodeVersion: string; 37 | _npmVersion: string; 38 | dist: Dist; 39 | cordova: CordovaInfo; 40 | capacitor: CapacitorInfo; 41 | _npmUser: string; 42 | directories: Directories; 43 | _npmOperationalInternal: NpmOperationalInternal; 44 | _hasShrinkwrap: boolean; 45 | } 46 | 47 | interface CordovaInfo { 48 | platforms: string[] | string; 49 | } 50 | 51 | interface Directories { 52 | unknown: any; 53 | } 54 | 55 | interface CapacitorInfo { 56 | ios: any; 57 | android: any; 58 | } 59 | 60 | interface DistTags { 61 | latest: string; 62 | next: string; 63 | } 64 | 65 | interface Repository { 66 | type: string; 67 | url: string; 68 | } 69 | 70 | interface Bugs { 71 | url: string; 72 | } 73 | 74 | interface Dist { 75 | integrity: string; 76 | shasum: string; 77 | tarball: string; 78 | fileCount: number; 79 | unpackedSize: number; 80 | signatures: Signature[]; 81 | 'npm-signature': string; 82 | } 83 | 84 | interface Signature { 85 | keyid: string; 86 | sig: string; 87 | } 88 | 89 | interface NpmOperationalInternal { 90 | host: string; 91 | tmp: string; 92 | } 93 | 94 | /** 95 | * This is the response from the npm api https://api.npmjs.org/downloads/[period]/[package] 96 | */ 97 | export interface NpmDownloads { 98 | downloads: number; 99 | start: string; 100 | end: string; 101 | package: string; 102 | } 103 | -------------------------------------------------------------------------------- /src/npm-info.ts: -------------------------------------------------------------------------------- 1 | import { NpmInfo } from './npm-info-data'; 2 | 3 | export async function getNpmInfo(name: string, latest: boolean): Promise { 4 | let url = ''; 5 | try { 6 | url = latest ? `https://registry.npmjs.org/${name}/latest` : `https://registry.npmjs.org/${name}`; 7 | const np: NpmInfo = await httpGet(url, npmHeaders()); 8 | if (!np.name) throw new Error(`No name found in ${url}`); // This error is happening for some reason 9 | //np.versions = undefined; 10 | np.version = np['dist-tags'] ? np['dist-tags'].latest : np.version; 11 | return np; 12 | } catch (error) { 13 | const msg = `${error}`; 14 | if (msg.includes(`'Not found'`)) { 15 | console.error(`[error] ${name} was not found on npm.`); 16 | } else { 17 | console.error(`getNpmInfo Failed ${url}`, error); 18 | } 19 | return {} as NpmInfo; 20 | } 21 | } 22 | 23 | function npmHeaders(): any { 24 | return { 25 | headers: { 26 | Authorization: `bearer ${getNpmToken()}`, 27 | 'User-Agent': 'Ionic VSCode Extension', 28 | Accept: '*/*', 29 | }, 30 | }; 31 | } 32 | 33 | export async function httpGet(url: string, opts: any): Promise { 34 | const response = await fetch(url, opts); 35 | try { 36 | const data = await response.json(); 37 | if (rateLimited(data)) { 38 | console.log(`The api call ${url} was rate limited.`); 39 | } 40 | return data; 41 | } catch (error) { 42 | throw new Error(`Error: get ${url}: ${response.status} ${response.statusText}`); 43 | } 44 | } 45 | 46 | function rateLimited(a: any): boolean { 47 | return ( 48 | (a as any).message?.startsWith('API rate limit exceeded') || 49 | (a as any).message?.startsWith('You have exceeded a secondary rate limit') 50 | ); 51 | } 52 | 53 | export function getNpmToken() { 54 | return process.env.DATA_SCRIPTS_NPM_TOKEN; 55 | } 56 | -------------------------------------------------------------------------------- /src/npm-model.ts: -------------------------------------------------------------------------------- 1 | // Used for the data than comes from npm list --json 2 | export interface NpmPackage { 3 | // Version number in the package.json 4 | version: string; 5 | 6 | // Name in package.json 7 | name: string; 8 | 9 | // This is an object with properties for each package. 10 | // eg { "@capacitor/project": {"version": "1.0.31", "resolved": "https://registry.npmjs.org/@capacitor/project/-/project-1.0.31.tgz"}} 11 | dependencies: object; 12 | } 13 | 14 | export interface NpmDependency { 15 | version: string; // Version number 16 | resolved: string; // URL to the package 17 | } 18 | 19 | // Used from npm outdated --json 20 | export interface NpmOutdatedDependency { 21 | current: string; // Current version 22 | wanted: string; 23 | latest: string; 24 | dependent: string; // Package that depends on this 25 | location: string; // path to the node modules folder 26 | } 27 | 28 | export enum PackageType { 29 | Dependency = 'Dependency', 30 | CapacitorPlugin = 'Capacitor Plugin', 31 | CordovaPlugin = 'Plugin', 32 | } 33 | 34 | export enum PackageVersion { 35 | Unknown = 'Unknown', 36 | 37 | // Like a version that is pulled from git or local folder 38 | Custom = '[custom]', 39 | } 40 | -------------------------------------------------------------------------------- /src/package-info.ts: -------------------------------------------------------------------------------- 1 | export interface PackageInfo { 2 | version: string; 3 | current: string; 4 | wanted: string; 5 | latest: string; 6 | change: string; 7 | depType: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/package-lock.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { Project } from './project'; 3 | import { PackageManager } from './node-commands'; 4 | import { existsSync, readFileSync } from 'fs'; 5 | import { tEnd, tStart } from './utilities'; 6 | import { NpmPackage } from './npm-model'; 7 | 8 | export function getVersionsFromPackageLock(project: Project): NpmPackage { 9 | if (project.packageManager != PackageManager.npm) return undefined; 10 | const lockFile = join(project.projectFolder(), 'package-lock.json'); 11 | if (!existsSync(lockFile)) return undefined; 12 | const command = `getVersionsFromPackageLock`; 13 | tStart(command); 14 | const txt = readFileSync(lockFile, { encoding: 'utf8' }); 15 | const data = JSON.parse(txt); 16 | const result = {}; 17 | try { 18 | const packages = data.packages['']; 19 | for (const dep of [...Object.keys(packages.dependencies), ...Object.keys(packages.devDependencies)]) { 20 | const name = `node_modules/${dep}`; 21 | result[dep] = { version: data.packages[name].version }; 22 | } 23 | tEnd(command); 24 | return { name: project.name, version: '0.0.0', dependencies: result }; 25 | } catch { 26 | return undefined; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/peer-dependency-cleanup.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { getAllPackageNames, getPackageVersion, load } from './analyzer'; 3 | import { ionicState } from './ionic-tree-provider'; 4 | import { write } from './logging'; 5 | import { DependencyVersion, PeerReport, checkPeerDependencies } from './peer-dependencies'; 6 | import { Project, inspectProject } from './project'; 7 | import { showProgress } from './utilities'; 8 | 9 | export async function peerDependencyCleanup(project: Project): Promise { 10 | let report: PeerReport; 11 | await showProgress(`Checking dependencies in your project...`, async () => { 12 | // Need to reload dependency list 13 | await inspectProject(ionicState.rootFolder, ionicState.context, undefined); 14 | 15 | const dependencies = getAllPackageNames(); 16 | const list: DependencyVersion[] = []; 17 | for (const dependency of dependencies) { 18 | const versionInfo = getPackageVersion(dependency); 19 | list.push({ name: dependency, version: versionInfo.version }); 20 | } 21 | 22 | report = await checkPeerDependencies(project.projectFolder(), list, []); 23 | }); 24 | //write(JSON.stringify(report, undefined, 2)); 25 | if (report.commands.length == 0) { 26 | write(`There are no dependency conflicts.`); 27 | return; 28 | } 29 | write(''); 30 | let question = 'Would you like to fix these?'; 31 | if (report.commands.length == 1) { 32 | question = `Would you like to update ${report.dependencies[0].name}?`; 33 | } 34 | if ( 35 | (await window.showWarningMessage( 36 | `There ${isAre(report.commands.length)} ${report.commands.length} dependency conflict${plural( 37 | report.commands.length, 38 | )} that can be resolved. ${question}`, 39 | 'Yes', 40 | 'No', 41 | )) != 'Yes' 42 | ) { 43 | return; 44 | } 45 | for (const cmd of report.commands) { 46 | write(`> ${cmd}`); 47 | await project.run2(cmd, true); 48 | } 49 | write(`${report.commands.length} dependency conflict${plural(report.commands.length)} resolved.`); 50 | } 51 | 52 | function isAre(count: number): string { 53 | return count == 1 ? 'is' : 'are'; 54 | } 55 | 56 | function plural(count: number): string { 57 | return count > 1 ? 's' : ''; 58 | } 59 | -------------------------------------------------------------------------------- /src/plugin-summary.ts: -------------------------------------------------------------------------------- 1 | export interface PluginSummary { 2 | plugins: Plugin[]; 3 | } 4 | 5 | export interface Plugin { 6 | name: string; 7 | version: string; 8 | success: string[]; 9 | repo?: string; 10 | keywords?: string[]; 11 | fails: string[]; 12 | description?: string; 13 | quality?: number; 14 | versions: string[]; 15 | author: any; 16 | bugs?: string; 17 | published: string; 18 | downloads?: number; 19 | stars?: number; 20 | image?: string; 21 | updated?: string; 22 | fork?: boolean; 23 | license?: string; 24 | } 25 | -------------------------------------------------------------------------------- /src/quick-fix.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CancellationToken, 3 | CodeAction, 4 | CodeActionContext, 5 | CodeActionKind, 6 | CodeActionProvider, 7 | Command, 8 | Diagnostic, 9 | ProviderResult, 10 | Range, 11 | TextDocument, 12 | } from 'vscode'; 13 | import { autoFixImports } from './imports-auto-fix'; 14 | 15 | export class ImportQuickFixProvider implements CodeActionProvider { 16 | public static readonly providedCodeActionKinds = [CodeActionKind.QuickFix]; 17 | 18 | public provideCodeActions( 19 | document: TextDocument, 20 | range: Range | Selection, 21 | context: CodeActionContext, 22 | token: CancellationToken, 23 | ): ProviderResult<(CodeAction | Command)[]> { 24 | // Filter out diagnostics that are not related to missing imports 25 | const missingImportDiagnostics = context.diagnostics.filter((diagnostic) => 26 | diagnostic.message.includes('is not a known element'), 27 | ); 28 | 29 | // Return an array of code actions for each diagnostic 30 | return missingImportDiagnostics.map((diagnostic) => this.createImportQuickFix(document, diagnostic)); 31 | } 32 | 33 | private createImportQuickFix(document: TextDocument, diagnostic: Diagnostic): CodeAction { 34 | // Get the name of the missing identifier from the diagnostic message 35 | const missingComponent = diagnostic.message.split(' ')[0].replace(/["']/g, ''); 36 | console.log(diagnostic.message); 37 | autoFixImports(document, missingComponent); 38 | return; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/recommendation.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { Tip } from './tip'; 3 | import { Command, TreeItem, TreeItemCollapsibleState } from 'vscode'; 4 | 5 | export class Recommendation extends TreeItem { 6 | public children: Recommendation[]; 7 | private iconName: string; 8 | private data: any; 9 | public whenExpanded: () => Promise>; 10 | 11 | constructor( 12 | public readonly tooltip: string, 13 | public readonly title: string, 14 | public readonly label: string, 15 | public readonly collapsibleState: TreeItemCollapsibleState, 16 | public readonly command?: Command, 17 | public tip?: Tip, 18 | public readonly url?: string, 19 | ) { 20 | super(label, collapsibleState); 21 | 22 | this.tooltip = `${this.tooltip}`; 23 | this.description = this.title; 24 | } 25 | 26 | public setIcon(name: string) { 27 | this.iconName = name; 28 | this.iconPath = { 29 | light: join(__filename, '..', '..', 'resources', 'light', name + '.svg'), 30 | dark: join(__filename, '..', '..', 'resources', 'dark', name + '.svg'), 31 | }; 32 | } 33 | 34 | public setData(data: any): Recommendation { 35 | this.data = data; 36 | return this; 37 | } 38 | 39 | public getData(): any { 40 | return this.data; 41 | } 42 | 43 | // Animated icons need to have an equivalent filename with -anim that contains animated svg 44 | public animate() { 45 | this.setIcon(this.iconName + '-anim'); 46 | } 47 | 48 | public setContext(value: string) { 49 | this.contextValue = value; 50 | } 51 | 52 | iconPath = undefined; 53 | contextValue = 'recommendation'; 54 | } 55 | -------------------------------------------------------------------------------- /src/rules-angular-toolkit.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './project'; 2 | import { QueueFunction, Tip, TipType } from './tip'; 3 | import { window } from 'vscode'; 4 | import { existsSync, readFileSync, writeFileSync } from 'fs'; 5 | import { join } from 'path'; 6 | 7 | /** 8 | * For Capacitor project if @ionic/angular-toolkit >= v6 then 9 | * "ionic-cordova-build" / "ionic-cordova-serve" sections in angular.json are not needed 10 | * Note: In Cordova projects require @ionic/cordova-builders 11 | * @param {Project} project 12 | */ 13 | export function checkMigrationAngularToolkit(project: Project) { 14 | // v6 removed the "ionic-cordova-build" / "ionic-cordova-serve" sections in Angular.json 15 | const filename = join(project.folder, 'angular.json'); 16 | if (existsSync(filename)) { 17 | const txt = readFileSync(filename, 'utf8'); 18 | if (txt && txt.includes('ionic-cordova-build')) { 19 | project.add( 20 | new Tip('Migrate angular.json', 'Remove Cordova configurations', TipType.Error).setQueuedAction( 21 | fixAngularJson, 22 | filename, 23 | ), 24 | ); 25 | } 26 | } 27 | } 28 | 29 | async function fixAngularJson(queueFunction: QueueFunction, filename: string) { 30 | if ( 31 | !(await window.showErrorMessage( 32 | 'When using @ionic/angular-toolkit v6+ the ionic-cordova-build and ionic-cordova-serve sections in angular.json can be removed.', 33 | 'Fix angular.json', 34 | )) 35 | ) 36 | return; 37 | queueFunction(); 38 | const txt = readFileSync(filename, 'utf8'); 39 | const angular = JSON.parse(txt); 40 | try { 41 | for (const project of Object.keys(angular.projects)) { 42 | delete angular.projects[project].architect['ionic-cordova-build']; 43 | delete angular.projects[project].architect['ionic-cordova-serve']; 44 | } 45 | writeFileSync(filename, JSON.stringify(angular, undefined, 2)); 46 | window.showInformationMessage('angular.json has been migrated'); 47 | } catch (err) { 48 | window.showErrorMessage('Failed to fix angular.json: ' + err); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/rules-capacitor-migration.ts: -------------------------------------------------------------------------------- 1 | import { exists, reviewPlugin } from './analyzer'; 2 | import { InternalCommand } from './command-name'; 3 | import { reviewPluginsWithHooks } from './process-packages'; 4 | import { Project } from './project'; 5 | import { capacitorRecommendations } from './rules-capacitor'; 6 | import { Tip, TipType } from './tip'; 7 | import { isWindows } from './utilities'; 8 | 9 | export async function capacitorMigrationChecks(packages, project: Project): Promise { 10 | const tips: Tip[] = []; 11 | project.setGroup( 12 | 'Capacitor Migration', 13 | 'Your Cordova application ' + 14 | project.name + 15 | ' can be migrated to Capacitor (see [guide](https://capacitorjs.com/docs/cordova/migrating-from-cordova-to-capacitor)). The following recommendations will help with the migration:', 16 | TipType.Capacitor, 17 | true, 18 | ); 19 | 20 | const list = await capacitorRecommendations(project, true); 21 | tips.push(...list); 22 | 23 | // Plugins with Hooks 24 | tips.push(...reviewPluginsWithHooks(packages)); 25 | 26 | // Requires evaluation to determine compatibility 27 | tips.push(reviewPlugin('cordova-wheel-selector-plugin')); 28 | tips.push(reviewPlugin('cordova-plugin-secure-storage')); 29 | tips.push(reviewPlugin('newrelic-cordova-plugin')); 30 | 31 | if (exists('cordova-ios') || exists('cordova-android') || project.fileExists('config.xml')) { 32 | const movecmd = isWindows() ? 'rename config.xml config.xml.bak' : 'mv config.xml config.xml.bak'; 33 | tips.push( 34 | new Tip( 35 | 'Remove Cordova Project', 36 | '', 37 | TipType.Capacitor, 38 | 'Remove the Cordova integration', 39 | ['npm uninstall cordova-ios', 'npm uninstall cordova-android', movecmd, InternalCommand.removeCordova], 40 | 'Remove Cordova', 41 | 'Removing Cordova', 42 | 'Successfully removed Cordova', 43 | ), 44 | ); 45 | } 46 | project.tips(tips); 47 | } 48 | -------------------------------------------------------------------------------- /src/rules-deprecated-plugins.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './project'; 2 | 3 | /** 4 | * Rules around deprecated plugins 5 | * @param {Project} project 6 | */ 7 | export function checkDeprecatedPlugins(project: Project) { 8 | // Adobe Mobiles services deprecation 9 | project.deprecatedPlugin( 10 | 'adobe-mobile-services', 11 | 'Mobile Services reaches end-of-life on December 31, 2022', 12 | 'https://experienceleague.adobe.com/docs/mobile-services/using/eol.html?lang=en', 13 | ); 14 | 15 | // Cordova Plugin Crop deprecation 16 | project.deprecatedPlugin( 17 | 'cordova-plugin-crop', 18 | 'cordova-plugin-crop is deprecated and does not support Android 11+', 19 | 'https://github.com/jeduan/cordova-plugin-crop#readme', 20 | ); 21 | 22 | // App Center deprecated Cordova SDK 23 | project.deprecatedPlugin( 24 | 'cordova-plugin-appcenter-analytics', 25 | 'App Center is deprecating support for Cordova SDK in April 2022', 26 | 'https://devblogs.microsoft.com/appcenter/announcing-apache-cordova-retirement', 27 | ); 28 | project.deprecatedPlugin( 29 | 'cordova-plugin-appcenter-crashes', 30 | 'App Center is deprecating support for Cordova SDK in April 2022', 31 | 'https://devblogs.microsoft.com/appcenter/announcing-apache-cordova-retirement', 32 | ); 33 | project.deprecatedPlugin( 34 | 'cordova-plugin-appcenter-shared', 35 | 'App Center is deprecating support for Cordova SDK in April 2022', 36 | 'https://devblogs.microsoft.com/appcenter/announcing-apache-cordova-retirement', 37 | ); 38 | 39 | project.deprecatedPlugin('cordova-plugin-contacts', 'Consider migration to @capacitor-community/contacts'); 40 | 41 | project.deprecatedPlugin( 42 | '@ionic-enterprise/offline-storage', 43 | 'Replace this plugin with @ionic-enterprise/secure-storage', 44 | ); 45 | 46 | project.recommendRemove( 47 | 'jetifier', 48 | 'jetifier', 49 | 'This tool was used to transition non-AndroidX libraries. By now though all plugins support Android 10 and this tool should be removed.', 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/rules-package-upgrade.ts: -------------------------------------------------------------------------------- 1 | import { fixIssue } from './extension'; 2 | import { npmInstall } from './node-commands'; 3 | import { Tip } from './tip'; 4 | import { getRunOutput, showProgress } from './utilities'; 5 | import { QuickPickItem, window } from 'vscode'; 6 | import { QuickPickItemKind } from 'vscode'; 7 | 8 | interface PackageInfo { 9 | name: string; 10 | version: string; 11 | } 12 | /** 13 | * Upgrade a package by allowing a user to select from the available versions 14 | * @param {PackageInfo} info 15 | * @param {string} folder 16 | */ 17 | export async function packageUpgrade(info: PackageInfo, folder: string): Promise { 18 | let txt = ''; 19 | await showProgress(`Finding versions of ${info.name}`, async () => { 20 | txt = await getRunOutput(`npm view ${info.name} versions --json`, folder); 21 | }); 22 | const versions: Array = JSON.parse(txt).reverse(); 23 | const idx = versions.findIndex((version) => info.version == version); 24 | versions.splice(idx, 1); 25 | const picks: QuickPickItem[] = []; 26 | const betas: string[] = []; 27 | picks.push({ label: 'Releases', kind: QuickPickItemKind.Separator }); 28 | for (const version of versions) { 29 | if (version.includes('-')) { 30 | betas.push(version); 31 | } else { 32 | picks.push({ label: version }); 33 | } 34 | } 35 | if (betas.length > 0) { 36 | picks.push({ label: 'Betas', kind: QuickPickItemKind.Separator }); 37 | for (const version of betas) { 38 | picks.push({ label: version }); 39 | } 40 | } 41 | const selection: QuickPickItem = await window.showQuickPick(picks, { 42 | placeHolder: `Update to version of ${info.name}`, 43 | }); 44 | if (!selection) return; 45 | 46 | const message = `Update ${info.name} to ${selection.label}`; 47 | await fixIssue( 48 | npmInstall(`${info.name}@${selection.label}`), 49 | folder, 50 | undefined, 51 | new Tip(message, undefined).showProgressDialog(), 52 | undefined, 53 | message, 54 | ); 55 | return true; 56 | } 57 | -------------------------------------------------------------------------------- /src/rules-web-project.ts: -------------------------------------------------------------------------------- 1 | import { exists } from './analyzer'; 2 | import { InternalCommand } from './command-name'; 3 | import { MonoRepoType } from './monorepo'; 4 | 5 | import { npmInstall, npx } from './node-commands'; 6 | import { Project } from './project'; 7 | import { Tip, TipType } from './tip'; 8 | import { asAppId } from './utilities'; 9 | import { checkCapacitorPluginMigration } from './rules-capacitor-plugins'; 10 | import { existsSync } from 'fs'; 11 | import { join } from 'path'; 12 | 13 | /** 14 | * Web projects are not using Capacitor or Cordova 15 | * @param {Project} project 16 | */ 17 | export function webProject(project: Project) { 18 | let outFolder = 'www'; 19 | 20 | // If there is a build folder and not a www folder then... 21 | if (!existsSync(join(project.projectFolder(), 'www'))) { 22 | if (existsSync(join(project.projectFolder(), 'build')) || exists('react')) { 23 | outFolder = 'build'; // use build folder (usually react) 24 | } else if (existsSync(join(project.projectFolder(), 'dist')) || exists('vue')) { 25 | outFolder = 'dist'; /// use dist folder (usually vue) 26 | } 27 | } 28 | 29 | const pre = project.repoType != MonoRepoType.none ? InternalCommand.cwd : ''; 30 | 31 | if (project.isCapacitorPlugin) { 32 | checkCapacitorPluginMigration(project); 33 | } 34 | 35 | if (!project.isCapacitorPlugin) { 36 | project.tip( 37 | new Tip( 38 | 'Add Capacitor Integration', 39 | '', 40 | TipType.Capacitor, 41 | 'Integrate Capacitor with this project to make it native mobile?', 42 | [ 43 | npmInstall(`@capacitor/core`), 44 | npmInstall(`@capacitor/cli`), 45 | npmInstall(`@capacitor/app @capacitor/haptics @capacitor/keyboard @capacitor/status-bar`), 46 | `${pre}${npx(project)} capacitor init "${project.name}" "${asAppId(project.name)}" --web-dir ${outFolder}`, 47 | InternalCommand.ionicInit, 48 | ], 49 | 'Add Capacitor', 50 | 'Capacitor added to this project', 51 | 'https://capacitorjs.com', 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/scripts.ts: -------------------------------------------------------------------------------- 1 | import { exists } from './analyzer'; 2 | import { MonoRepoType } from './monorepo'; 3 | import { npmRun } from './node-commands'; 4 | import { Project } from './project'; 5 | import { Tip, TipType } from './tip'; 6 | import { getPackageJSON, PackageFile } from './utilities'; 7 | 8 | // Look in package.json for scripts and add options to execute 9 | export function addScripts(project: Project) { 10 | const expand = !(exists('@capacitor/core') || exists('cordova-ios') || exists('cordova-android')); 11 | project.setGroup(`Scripts`, `The scripts from package.json`, TipType.Files, expand); 12 | 13 | addScriptsFrom(getPackageJSON(project.projectFolder()), project); 14 | 15 | if (project.repoType == MonoRepoType.nx) { 16 | addScriptsFrom(getPackageJSON(project.folder), project); 17 | addNXScripts(['build', 'test', 'lint', 'e2e'], project); 18 | } 19 | } 20 | 21 | function addScriptsFrom(packages: PackageFile, project: Project) { 22 | if (packages.scripts) { 23 | for (const script of Object.keys(packages.scripts)) { 24 | project.add( 25 | new Tip(script, '', TipType.Run, '', npmRun(script), `Running ${script}`, `Ran ${script}`) 26 | .canStop() 27 | .canAnimate() 28 | .setTooltip(`Runs 'npm run ${script}' found in package.json`), 29 | ); 30 | } 31 | } 32 | 33 | // We may be able to migrate a Capacitor Plugin 34 | project.isCapacitorPlugin = !!(packages.capacitor?.ios || packages.capacitor?.android); 35 | } 36 | 37 | function addNXScripts(names: Array, project: Project) { 38 | for (const name of names) { 39 | project.add( 40 | new Tip( 41 | `${project.monoRepo.name} ${name}`, 42 | '', 43 | TipType.Run, 44 | '', 45 | `npx nx run ${project.monoRepo.name}:${name}`, 46 | `Running ${name}`, 47 | `Ran ${name}`, 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/source-map-server.ts: -------------------------------------------------------------------------------- 1 | import { writeIonic } from './logging'; 2 | import { readFile } from 'fs'; 3 | import { createServer } from 'http'; 4 | import { extname, join } from 'path'; 5 | 6 | export function startSourceMapServer(folder: string) { 7 | writeIonic('Starting source map server on port 80....'); 8 | createServer((request, response) => { 9 | const filePath = join(folder, request.url); 10 | writeIonic(`Serving ${filePath}`); 11 | 12 | const ex = extname(filePath); 13 | const contentType = getMimeType(ex); 14 | 15 | readFile(filePath, (error, content) => { 16 | if (error) { 17 | if (error.code == 'ENOENT') { 18 | readFile('./404.html', function (error, content) { 19 | response.writeHead(200, { 'Content-Type': contentType }); 20 | response.end(content, 'utf-8'); 21 | }); 22 | } else { 23 | response.writeHead(500); 24 | response.end('Oh bummer error: ' + error.code + ' ..\n'); 25 | response.end(); 26 | } 27 | } else { 28 | response.writeHead(200, { 'Content-Type': contentType }); 29 | response.end(content, 'utf-8'); 30 | } 31 | }); 32 | }).listen(80); 33 | } 34 | 35 | function getMimeType(extname: string): string { 36 | switch (extname) { 37 | case '.js': 38 | return 'text/javascript'; 39 | case '.css': 40 | return 'text/css'; 41 | case '.json': 42 | return 'application/json'; 43 | case '.png': 44 | return 'image/png'; 45 | case '.jpg': 46 | return 'image/jpg'; 47 | case '.wav': 48 | return 'audio/wav'; 49 | } 50 | return 'text/html'; 51 | } 52 | -------------------------------------------------------------------------------- /src/web-configuration.ts: -------------------------------------------------------------------------------- 1 | import { commands, window } from 'vscode'; 2 | import { getSetting, setSetting, WorkspaceSetting } from './workspace-state'; 3 | import { Context, VSCommand } from './context-variables'; 4 | 5 | export enum WebConfigSetting { 6 | nexus = 'WebConfigNexusBrowser', 7 | browser = 'WebConfigWebBrowser', 8 | editor = 'WebConfigEditor', 9 | none = 'WebConfigNone', 10 | } 11 | 12 | export function getWebConfiguration(): WebConfigSetting { 13 | const setting = getSetting(WorkspaceSetting.webAction); 14 | if (setting) { 15 | return setting; 16 | } else { 17 | return WebConfigSetting.browser; 18 | } 19 | } 20 | 21 | export async function setWebConfig(setting: WebConfigSetting) { 22 | setSetting(WorkspaceSetting.webAction, setting); 23 | commands.executeCommand(VSCommand.setContext, Context.webConfig, setting); 24 | } 25 | -------------------------------------------------------------------------------- /src/web-debug.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { getSetting, setSetting, WorkspaceSetting } from './workspace-state'; 3 | 4 | export enum WebDebugSetting { 5 | edge = 'pwa-msedge', 6 | chrome = 'chrome', 7 | } 8 | 9 | export function getWebDebugSetting(): WebDebugSetting { 10 | const setting = getSetting(WorkspaceSetting.debugBrowser); 11 | if (setting) { 12 | return setting; 13 | } else { 14 | return WebDebugSetting.edge; 15 | } 16 | } 17 | 18 | export async function webDebugSetting(): Promise { 19 | const setting = getSetting(WorkspaceSetting.debugBrowser); 20 | const configs = [ 21 | check(WebDebugSetting.edge, setting, 'Microsoft Edge'), 22 | check(WebDebugSetting.chrome, setting, 'Google Chrome'), 23 | ]; 24 | 25 | const selection = await window.showQuickPick(configs, { 26 | placeHolder: 'Select the debuggable Browser', 27 | }); 28 | if (selection) { 29 | const value = selection.includes('Edge') ? WebDebugSetting.edge : WebDebugSetting.chrome; 30 | setSetting(WorkspaceSetting.debugBrowser, value); 31 | } 32 | } 33 | 34 | function check(msg: string, setting: string, title: string): string { 35 | if (msg === setting) { 36 | return title + ` $(check)`; 37 | } 38 | return title; 39 | } 40 | -------------------------------------------------------------------------------- /src/workspace-state.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { ionicState } from './ionic-tree-provider'; 3 | 4 | export enum WorkspaceSetting { 5 | liveReload = 'liveReload', 6 | httpsForWeb = 'httpsForWeb', 7 | pluginDrift = 'pluginDrift', // Whether the user has been shown the plugin drift compared to NexusBrowser app 8 | webAction = 'webAction', 9 | logFilter = 'logFilter', 10 | tips = 'tipsShown', 11 | lastIPAddress = 'lastIPAddress', 12 | debugBrowser = 'debugBrowser', 13 | cocoaPods = 'cocoaPods2', 14 | } 15 | 16 | export enum ExtensionSetting { 17 | internalAddress = 'internalAddress', 18 | javaHome = 'javaHome', 19 | manualNewProjects = 'manualNewProjects', 20 | } 21 | 22 | export enum GlobalSetting { 23 | lastTipsShown = 'lastTipsShown', 24 | projectsFolder = 'projectsFolder', 25 | suggestNPMInstall = 'suggestNPMInstall', 26 | } 27 | 28 | export function getSetting(key: WorkspaceSetting): any { 29 | return ionicState.context.workspaceState.get(key); 30 | } 31 | 32 | export async function setSetting(key: WorkspaceSetting, value: any): Promise { 33 | await ionicState.context.workspaceState.update(key, value); 34 | } 35 | 36 | export function getExtSetting(key: ExtensionSetting): any { 37 | return workspace.getConfiguration('ionic').get(key); 38 | } 39 | 40 | export function getGlobalSetting(key: GlobalSetting): any { 41 | return ionicState.context.globalState.get(key); 42 | } 43 | 44 | export async function setGlobalSetting(key: GlobalSetting, value: any): Promise { 45 | return await ionicState.context.globalState.update(key, value); 46 | } 47 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019", "DOM"], 6 | "outDir": "out", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "rootDir": "src" 11 | }, 12 | "exclude": ["node_modules", "log-client", ".vscode-test", "plugin-explorer", "ionic-start"] 13 | } 14 | --------------------------------------------------------------------------------