зеркало из https://github.com/Azure/iotc-explorer.git
feat: initial commit
Co-authored-by: Rahul Chaudhary <7551493+rahulch95@users.noreply.github.com>
This commit is contained in:
Родитель
a08d057cf9
Коммит
df3c004c4d
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
---
|
||||
|
||||
<!-- ⚠️ Please search existing issues to avoid creating duplicates. ⚠️ -->
|
||||
<!-- Describe the bug here. -->
|
||||
|
||||
## Details
|
||||
- Operating System type and version:
|
||||
- Output of `iotc-explorer --version`:
|
||||
- Output of `node -v`:
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
1.
|
||||
2.
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature or enhancement
|
||||
---
|
||||
|
||||
<!-- ⚠️ Please search existing issues to avoid creating duplicates. ⚠️ -->
|
||||
<!-- Describe the enhancement here. -->
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: Question
|
||||
about: Ask a question
|
||||
---
|
||||
|
||||
<!-- ⚠️ Please search existing issues to avoid creating duplicates. ⚠️ -->
|
||||
<!-- Describe your question here. -->
|
|
@ -0,0 +1,13 @@
|
|||
<!--
|
||||
Thank you for your pull request. Please provide a description above and review
|
||||
the requirements below.
|
||||
|
||||
Contributors guide: https://github.com/Azure/iotc-explorer/blob/master/README.md#contributing
|
||||
-->
|
||||
|
||||
### Checklist
|
||||
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->
|
||||
|
||||
- [ ] `npm test` passes
|
||||
- [ ] documentation is changed or added
|
||||
- [ ] commit message follows [commit guidelines](https://github.com/Azure/iotc-explorer/blob/master/README.md#committing)
|
|
@ -1,15 +1,6 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
@ -20,42 +11,14 @@ coverage
|
|||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
# Build output
|
||||
dist
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
node_modules/
|
||||
.nyc_output/
|
||||
.vscode/
|
||||
.github/
|
||||
coverage/
|
||||
|
||||
src/
|
||||
dist/**/*.spec.*
|
||||
|
||||
*.tgz
|
||||
npm-debug.log*
|
|
@ -0,0 +1,26 @@
|
|||
language: node_js
|
||||
cache:
|
||||
directories:
|
||||
# https://twitter.com/maybekatz/status/905213355748720640
|
||||
- ~/.npm
|
||||
node_js:
|
||||
- '8'
|
||||
- '10'
|
||||
|
||||
# Trigger a push build on master branch + PRs build on every branch. Avoid
|
||||
# double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147)
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
stages:
|
||||
- commitlint
|
||||
- test
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: commitlint
|
||||
node_js: '8'
|
||||
script: commitlint-travis
|
||||
- stage: test
|
||||
script: npm run build-verify
|
200
README.md
200
README.md
|
@ -1,14 +1,194 @@
|
|||
# iotc-explorer
|
||||
|
||||
# Contributing
|
||||
Command-line interface for interacting with Azure IoT Central devices and
|
||||
applications.
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
## Installing
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
To use `iotc-explorer`, you will need to have the following installed:
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
* Node.js version 8.x or higher - https://nodejs.org
|
||||
|
||||
Once you have the prerequisites installed, run the following from your command
|
||||
line to install:
|
||||
|
||||
```
|
||||
npm install -g iotc-explorer
|
||||
```
|
||||
|
||||
*NOTE: You will typically need to run the install command with `sudo` in
|
||||
Unix-like environments.*
|
||||
|
||||
Once installed, you can run `iotc-explorer --help` to verify everything is
|
||||
working and get an overview of the available commands:
|
||||
|
||||
```
|
||||
$ iotc-explorer --help
|
||||
|
||||
iotc-explorer <command>
|
||||
|
||||
Commands:
|
||||
iotc-explorer config Manage configuration values for the CLI
|
||||
iotc-explorer get-twin <deviceId> Get the IoT Hub device twin for a specific device
|
||||
iotc-explorer login [token] Log in to an Azure IoT Central application
|
||||
iotc-explorer monitor-messages [deviceId] Monitor messages being sent to a specific device (if
|
||||
device id is provided), or all devices
|
||||
|
||||
Options:
|
||||
--version Show version number [boolean]
|
||||
--help Show help [boolean]
|
||||
```
|
||||
|
||||
## Running `iotc-explorer`
|
||||
|
||||
Below are some commands and common options that you can run when using
|
||||
`iotc-explorer`. To view the full set of commands and options, you can pass
|
||||
`--help` to `iotc-explorer` or any of its subcommands.
|
||||
|
||||
### Login
|
||||
|
||||
Before you get going, you will want to have one of your IoT Central application
|
||||
administrators get a SAS token for you to use. You can then use that token to
|
||||
log in to the CLI by running:
|
||||
|
||||
```sh
|
||||
iotc-explorer login "SharedAccessSignature sr=<your-resource>&sig=<your-signature>&skn=<your-key-name>&se=<your-expiry>"
|
||||
```
|
||||
|
||||
If you would rather not have the token persisted in your shell history, you can
|
||||
leave the token out and instead provide it when prompted:
|
||||
|
||||
```
|
||||
iotc-explorer login
|
||||
```
|
||||
|
||||
### Monitor Device Messages
|
||||
|
||||
You can watch the messages coming from either a specific device or all devices
|
||||
in your application using the `monitor-messages` command. This will start a
|
||||
watcher that will continuously output new messages as they come in.
|
||||
|
||||
To watch all devices in your application, simply run:
|
||||
|
||||
```
|
||||
iotc-explorer monitor-messages
|
||||
```
|
||||
|
||||
To watch a specific device, just add the device's id to the end of the command:
|
||||
|
||||
```
|
||||
iotc-explorer monitor-messages <your-device-id>
|
||||
```
|
||||
|
||||
You can also have the command output a more machine-friendly format by adding
|
||||
the `--raw` option to the command:
|
||||
|
||||
```
|
||||
iotc-explorer monitor-messages --raw
|
||||
```
|
||||
|
||||
### Get Device Twin
|
||||
|
||||
You can use the `get-twin` command to get the contents of the twin for an IoT
|
||||
Central device. To do so, simply run the following:
|
||||
|
||||
```
|
||||
iotc-explorer get-twin <your-device-id>
|
||||
```
|
||||
|
||||
As with `monitor-messages`, you can get a more machine-friendly output by
|
||||
passing the `--raw` option:
|
||||
|
||||
```
|
||||
iotc-explorer get-twin <your-device-id> --raw
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
### Developer Setup
|
||||
|
||||
For your first time setup, make sure you've done the following:
|
||||
|
||||
1. Make sure you have the [prerequisites](#installing) installed.
|
||||
2. Clone this repository to wherever you want to develop.
|
||||
3. Run `cd iotc-explorer` to enter the repository folder.
|
||||
4. Run `npm install`, then `npm run build` to get things configured.
|
||||
|
||||
### Writing Code
|
||||
|
||||
Once you're ready to start changing code, it is recommended that you link your
|
||||
project to the `iotc-explorer` executable by running the following (may require
|
||||
`sudo`):
|
||||
|
||||
```
|
||||
npm link
|
||||
```
|
||||
|
||||
Now, when you run `iotc-explorer`, it will point to the code in your development
|
||||
folder. To make the executable reflect your changes as they're made, set up a
|
||||
watch task in a terminal window to the side:
|
||||
|
||||
```
|
||||
npm run watch
|
||||
```
|
||||
|
||||
Now, whenever you make edits to the code you will be able to use them by running
|
||||
the `iotc-explorer` command on your machine.
|
||||
|
||||
When you're ready to stop local development, you can remove your connection to
|
||||
the `iotc-explorer` executable by running the following (may require `sudo`):
|
||||
|
||||
```
|
||||
npm unlink
|
||||
```
|
||||
|
||||
### Committing
|
||||
|
||||
This project uses the [Angular commit style][angular commit style] for
|
||||
generating changelogs and determining release versions. Any pull request with
|
||||
commits that don't follow this style will fail continuous integration. If you're
|
||||
not familiar with the style, you can run the following instead of the standard
|
||||
`git commit` to get a guided walkthrough to generating your commit message:
|
||||
|
||||
```
|
||||
npm run commit
|
||||
```
|
||||
|
||||
### Releasing
|
||||
|
||||
When it's time to cut a new release, run the following from the repository
|
||||
folder. This will (1) fetch the latest updates, (2) automatically update the
|
||||
package version and the changelog, (3) publish the package and (4) push the
|
||||
changes back into the repository:
|
||||
|
||||
```
|
||||
git checkout master
|
||||
git pull
|
||||
npm run build-verify
|
||||
npm run release
|
||||
npm publish
|
||||
git push
|
||||
```
|
||||
|
||||
### Contributor License Agreement
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require
|
||||
you to agree to a Contributor License Agreement (CLA) declaring that you have
|
||||
the right to, and actually do, grant us the rights to use your contribution. For
|
||||
details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether
|
||||
you need to provide a CLA and decorate the PR appropriately (e.g., label,
|
||||
comment). Simply follow the instructions provided by the bot. You will only need
|
||||
to do this once across all repos using our CLA.
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct][]. For more
|
||||
information see the [Code of Conduct FAQ][] or contact
|
||||
[opencode@microsoft.com][] with any additional questions or comments.
|
||||
|
||||
[Microsoft Open Source Code of Conduct]: https://opensource.microsoft.com/codeofconduct/
|
||||
[Code of Conduct FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
|
||||
[opencode@microsoft.com]: mailto:opencode@microsoft.com
|
||||
[angular commit style]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"name": "iotc-explorer",
|
||||
"description": "CLI for interacting with Azure IoT Central devices and applications",
|
||||
"version": "1.0.0",
|
||||
"author": "Microsoft",
|
||||
"license": "MIT",
|
||||
"main": "./dist/iotc-explorer.js",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"iotc-explorer": "dist/iotc-explorer.js"
|
||||
},
|
||||
"keywords": [
|
||||
"iotcentral",
|
||||
"iot",
|
||||
"azure",
|
||||
"device",
|
||||
"monitor"
|
||||
],
|
||||
"scripts": {
|
||||
"commit": "commit",
|
||||
"commitmsg": "commitlint -e $GIT_PARAMS",
|
||||
"clean": "rimraf dist",
|
||||
"lint": "tslint --project tsconfig.json -e \"**/*.json\" --fix",
|
||||
"prebuild": "npm run clean && npm run lint",
|
||||
"build": "tsc",
|
||||
"build-verify": "npm run clean && tslint --project tsconfig.json -e \"**/*.json\" && tsc",
|
||||
"test": "npm run build-verify",
|
||||
"prepare": "npm t",
|
||||
"watch": "tsc -w",
|
||||
"start": "./dist/iotc-explorer.js",
|
||||
"release": "standard-version"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"azure-event-hubs": "^0.2.6",
|
||||
"azure-iothub": "^1.7.3",
|
||||
"conf": "^2.0.0",
|
||||
"get-caller-file": "^2.0.0",
|
||||
"inquirer": "^6.2.0",
|
||||
"prettyjson": "^1.2.1",
|
||||
"source-map-support": "^0.5.6",
|
||||
"string-width": "^2.1.1",
|
||||
"yargs": "^12.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^7.1.2",
|
||||
"@commitlint/config-conventional": "^7.1.2",
|
||||
"@commitlint/prompt-cli": "^7.1.2",
|
||||
"@commitlint/travis-cli": "^7.1.2",
|
||||
"@types/async-lock": "^1.1.0",
|
||||
"@types/conf": "^1.4.0",
|
||||
"@types/inquirer": "0.0.43",
|
||||
"@types/node": "^8.10.29",
|
||||
"@types/prettyjson": "0.0.28",
|
||||
"@types/string-width": "^2.0.0",
|
||||
"@types/yargs": "^11.1.1",
|
||||
"husky": "^0.14.3",
|
||||
"rimraf": "^2.6.2",
|
||||
"standard-version": "^4.4.0",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-consistent-codestyle": "^1.13.3",
|
||||
"typescript": "^3.0.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { group } from '../core/command';
|
||||
import * as resources from '../resources.json';
|
||||
|
||||
export = group(
|
||||
'config',
|
||||
resources.commands.config.description
|
||||
);
|
|
@ -0,0 +1,18 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import command from '../../core/command';
|
||||
import * as config from '../../core/config';
|
||||
import * as resources from '../../resources.json';
|
||||
|
||||
export = command<{ key: string; }>({
|
||||
command: 'delete <key>',
|
||||
describe: resources.commands.config.commands.delete.description,
|
||||
|
||||
handler(args) {
|
||||
config.del(args.key as config.ConfigKey);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import command from '../../core/command';
|
||||
import * as config from '../../core/config';
|
||||
import * as resources from '../../resources.json';
|
||||
|
||||
export = command<{ key: string }>({
|
||||
command: 'get <key>',
|
||||
describe: resources.commands.config.commands.get.description,
|
||||
|
||||
handler(args, log) {
|
||||
const value = config.getOverride(args.key as config.ConfigKey);
|
||||
log.info(typeof value === 'string' ? value : JSON.stringify(value));
|
||||
}
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import command from '../../core/command';
|
||||
import * as config from '../../core/config';
|
||||
import * as resources from '../../resources.json';
|
||||
|
||||
export = command({
|
||||
command: 'list',
|
||||
describe: resources.commands.config.commands.list.description,
|
||||
|
||||
handler(args, log) {
|
||||
log.json(config.list());
|
||||
}
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import command from '../../core/command';
|
||||
import * as config from '../../core/config';
|
||||
import * as resources from '../../resources.json';
|
||||
|
||||
export = command<{ key: string; value: string; }>({
|
||||
command: 'set <key> <value>',
|
||||
describe: resources.commands.config.commands.set.description,
|
||||
|
||||
handler(args) {
|
||||
let value: any;
|
||||
try {
|
||||
value = JSON.parse(args.value);
|
||||
} catch {
|
||||
value = args.value;
|
||||
}
|
||||
|
||||
config.set(args.key as config.ConfigKey, value);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { Registry } from 'azure-iothub';
|
||||
import * as util from 'util';
|
||||
|
||||
import * as api from '../core/api';
|
||||
import command from '../core/command';
|
||||
import CliError from '../core/error';
|
||||
import * as opts from '../core/options';
|
||||
import * as resources from '../resources.json';
|
||||
|
||||
export = command<{ deviceId: string }, { 'hide-metadata': boolean } & opts.raw>({
|
||||
command: 'get-twin <deviceId>',
|
||||
describe: resources.commands.getTwin.description,
|
||||
|
||||
options: {
|
||||
'hide-metadata': {
|
||||
describe: resources.commands.getTwin.options.hideMetadata,
|
||||
alias: 'h',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
...opts.raw
|
||||
},
|
||||
|
||||
async handler(args, log) {
|
||||
const sasTokens = await api.generateSasTokens();
|
||||
const iothubRegistry = Registry.fromSharedAccessSignature(sasTokens.iothubTenantSasToken.sasToken);
|
||||
|
||||
let twin: any;
|
||||
try {
|
||||
twin = await util.promisify(iothubRegistry.getTwin.bind(iothubRegistry))(args.deviceId);
|
||||
} catch (e) {
|
||||
switch (e && e.name) {
|
||||
// The device was not found.
|
||||
case 'DeviceNotFoundError':
|
||||
throw new CliError(
|
||||
'DEVICE_NOT_FOUND',
|
||||
util.format(resources.errors.iotHub.deviceNotFound, args.deviceId)
|
||||
);
|
||||
// We just group other errors together for now since we don't
|
||||
// expect to see any of them.
|
||||
default:
|
||||
throw new CliError(
|
||||
'IOTHUB_REGISTRY_ERROR',
|
||||
util.format(resources.errors.iotHub.registryError, e && (e.name || e.message))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// We don't want internal information to be displayed
|
||||
delete twin._registry;
|
||||
|
||||
// If the user does not want metadata, delete $metadata and $version
|
||||
if (args['hide-metadata'] && twin.properties) {
|
||||
if (twin.properties.desired) {
|
||||
delete twin.properties.desired.$metadata;
|
||||
delete twin.properties.desired.$version;
|
||||
}
|
||||
|
||||
if (twin.properties.reported) {
|
||||
delete twin.properties.reported.$metadata;
|
||||
delete twin.properties.reported.$version;
|
||||
}
|
||||
}
|
||||
|
||||
log.json(twin);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import * as inquirer from 'inquirer';
|
||||
import * as querystring from 'querystring';
|
||||
import * as util from 'util';
|
||||
|
||||
import command from '../core/command';
|
||||
import * as config from '../core/config';
|
||||
import { SAS_TOKEN_PREFIX } from '../core/constants';
|
||||
import CliError from '../core/error';
|
||||
import * as resources from '../resources.json';
|
||||
|
||||
export = command<{ token?: string }>({
|
||||
command: 'login [token]',
|
||||
describe: resources.commands.login.description,
|
||||
|
||||
async handler(args, log) {
|
||||
let inputToken: string;
|
||||
if (args.token) {
|
||||
// The token was provided as an option to the command. Just use it
|
||||
// directly
|
||||
inputToken = args.token;
|
||||
} else {
|
||||
// Prompt the user for their token and use that value.
|
||||
inputToken = (await inquirer.prompt<{ token: string }>({
|
||||
type: 'input',
|
||||
name: 'token',
|
||||
message: 'SAS Token',
|
||||
prefix: '',
|
||||
suffix: ':'
|
||||
})).token;
|
||||
}
|
||||
|
||||
const invalidSasTokenCode = 'INVALID_SAS_TOKEN';
|
||||
|
||||
// Tokens must start with "SharedAccessSignature "
|
||||
if (!inputToken.startsWith(SAS_TOKEN_PREFIX)) {
|
||||
throw new CliError(
|
||||
invalidSasTokenCode,
|
||||
resources.errors.sasToken.invalidPrefix
|
||||
);
|
||||
}
|
||||
|
||||
const token = inputToken.substring(SAS_TOKEN_PREFIX.length);
|
||||
|
||||
// If the token isn't a valid uri fragment, this can throw. Catch it and
|
||||
// indicate that the token is malformed
|
||||
let parsed: querystring.ParsedUrlQuery;
|
||||
try {
|
||||
parsed = querystring.parse(token);
|
||||
} catch {
|
||||
throw new CliError(
|
||||
invalidSasTokenCode,
|
||||
resources.errors.sasToken.malformed
|
||||
);
|
||||
}
|
||||
|
||||
// The token must have the sr, se, and skn properties to be valid
|
||||
if (!parsed || !parsed.sr || !parsed.se || !parsed.skn) {
|
||||
throw new CliError(
|
||||
invalidSasTokenCode,
|
||||
resources.errors.sasToken.invalid
|
||||
);
|
||||
}
|
||||
|
||||
// If there is a token expiry, it cannot be in the past
|
||||
if (parsed.se && Number(parsed.se) < Date.now()) {
|
||||
throw new CliError(
|
||||
invalidSasTokenCode,
|
||||
resources.errors.sasToken.expired
|
||||
);
|
||||
}
|
||||
|
||||
// Set the relevant information in the configuration store
|
||||
config.set('iotc.credentials.token', inputToken);
|
||||
config.set('iotc.credentials.application', parsed.sr);
|
||||
|
||||
// Delete any cached hub credentials we have as they may be pointing to
|
||||
// the wrong place
|
||||
config.del('iotc.credentials.hubs');
|
||||
|
||||
// Inform the user
|
||||
log.info(
|
||||
parsed.se
|
||||
? util.format(
|
||||
resources.commands.login.logs.successWithExpiry,
|
||||
new Date(Number(parsed.se)).toString()
|
||||
)
|
||||
: resources.commands.login.logs.successNoExpiry
|
||||
);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,99 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { EventHubClient, TokenType } from 'azure-event-hubs';
|
||||
import * as util from 'util';
|
||||
|
||||
import * as api from '../core/api';
|
||||
import command from '../core/command';
|
||||
import * as opts from '../core/options';
|
||||
import * as resources from '../resources.json';
|
||||
|
||||
export = command<{ deviceId?: string; }, { 'start-time': number } & opts.raw>({
|
||||
command: 'monitor-messages [deviceId]',
|
||||
describe: resources.commands.monitorMessages.description,
|
||||
|
||||
options: {
|
||||
'start-time': {
|
||||
describe: resources.commands.monitorMessages.options.startTime,
|
||||
type: 'number',
|
||||
default: Date.now()
|
||||
},
|
||||
...opts.raw
|
||||
},
|
||||
|
||||
async handler(args, log): Promise<void> {
|
||||
const sasTokens = await api.generateSasTokens();
|
||||
const eventhubClient = EventHubClient.createFromTokenProvider(
|
||||
sasTokens.eventhubSasToken.hostname.substring(5),
|
||||
sasTokens.eventhubSasToken.entityPath,
|
||||
{
|
||||
tokenRenewalMarginInSeconds: -1,
|
||||
tokenValidTimeInSeconds: 3600,
|
||||
getToken: () => Promise.resolve({
|
||||
tokenType: TokenType.CbsTokenTypeSas,
|
||||
token: sasTokens.eventhubSasToken.sasToken,
|
||||
expiry: sasTokens.expiry,
|
||||
}),
|
||||
},
|
||||
);
|
||||
const partitionIds = await eventhubClient.getPartitionIds();
|
||||
|
||||
const deviceId = args.deviceId;
|
||||
const startTime = args['start-time'];
|
||||
|
||||
// Once we get here we know we can set up the client and start receiving
|
||||
// messages.
|
||||
log.info(
|
||||
deviceId
|
||||
? util.format(resources.commands.monitorMessages.logs.monitoringDevice, deviceId)
|
||||
: resources.commands.monitorMessages.logs.monitoringAll
|
||||
);
|
||||
log.blank();
|
||||
|
||||
partitionIds.map(partitionId => {
|
||||
eventhubClient.receive(
|
||||
partitionId,
|
||||
message => {
|
||||
const messageDeviceId = message.annotations && message.annotations['iothub-connection-device-id'];
|
||||
|
||||
// If device id is provided, and the message is not sent to it, return
|
||||
const isDeviceRelevant = !deviceId || messageDeviceId === deviceId;
|
||||
|
||||
// If message time is before the specified start time, return
|
||||
const messageTime = message.annotations && message.annotations['x-opt-enqueued-time'] || Date.now();
|
||||
const isMessageBeforeStartTime = messageTime < startTime;
|
||||
|
||||
// We don't want messages that don't fit our conditions
|
||||
if (!isDeviceRelevant || isMessageBeforeStartTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dateString = new Date(messageTime).toUTCString();
|
||||
|
||||
// Log the metadata and the message body
|
||||
log.divider(messageDeviceId
|
||||
? util.format(
|
||||
resources.commands.monitorMessages.logs.messageHeaderKnownDevice,
|
||||
messageDeviceId,
|
||||
dateString
|
||||
)
|
||||
: util.format(
|
||||
resources.commands.monitorMessages.logs.messageHeaderUnknownDevice,
|
||||
dateString
|
||||
)
|
||||
);
|
||||
log.json(message.body);
|
||||
log.blank();
|
||||
},
|
||||
error => {
|
||||
// Errors here should be fatal. Log it and exit
|
||||
log.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,90 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import ax, { AxiosError } from 'axios';
|
||||
import * as https from 'https';
|
||||
import * as util from 'util';
|
||||
|
||||
import * as resources from '../resources.json';
|
||||
|
||||
import * as config from './config';
|
||||
import CliError from './error';
|
||||
|
||||
export type IotHubSasTokens = {
|
||||
iothubTenantSasToken: {
|
||||
sasToken: string,
|
||||
},
|
||||
eventhubSasToken: {
|
||||
sasToken: string,
|
||||
entityPath: string,
|
||||
hostname: string,
|
||||
},
|
||||
expiry: number,
|
||||
};
|
||||
|
||||
const axios = ax.create({
|
||||
baseURL: `https://${config.get('iotc.api.host')}/${config.get('iotc.api.version')}/`,
|
||||
httpsAgent: new https.Agent({
|
||||
keepAlive: true,
|
||||
rejectUnauthorized: !!config.get('core.rejectUnauthorized')
|
||||
}),
|
||||
responseType: 'json'
|
||||
});
|
||||
|
||||
export async function generateSasTokens(): Promise<IotHubSasTokens> {
|
||||
const { token, appId } = getContext();
|
||||
const cacheTtl = config.get('core.tokenCacheTtl') as number;
|
||||
const cachedTokens = config.get('iotc.credentials.hubs') as any;
|
||||
|
||||
// If we have cached tokens and a nonzero ttl less than the time to expiry,
|
||||
// use the cached token.
|
||||
if (cacheTtl && cachedTokens && (cachedTokens.expiry * 1000 - Date.now()) > cacheTtl) {
|
||||
return cachedTokens;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post<IotHubSasTokens>(
|
||||
`/applications/${appId}/diagnostics/sasTokens`,
|
||||
undefined,
|
||||
{
|
||||
headers: {
|
||||
Authorization: token
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
config.set('iotc.credentials.hubs', response.data);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
throw new CliError(
|
||||
'IOTC_API_ERROR',
|
||||
util.format(resources.errors.iotCentral.genericError, getErrorMessage(e))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getContext() {
|
||||
const token = config.get('iotc.credentials.token') as string | undefined;
|
||||
const appId = config.get('iotc.credentials.application') as string | undefined;
|
||||
// if iot central token does not exist, throw error
|
||||
if (!token || !appId) {
|
||||
throw new CliError(
|
||||
'INVALID_CREDENTIALS',
|
||||
resources.errors.iotCentral.invalidCredentials
|
||||
);
|
||||
}
|
||||
return { token, appId };
|
||||
}
|
||||
|
||||
function getErrorMessage(err: AxiosError) {
|
||||
const responseCode = err &&
|
||||
err.response &&
|
||||
err.response.data &&
|
||||
err.response.data.error &&
|
||||
err.response.data.error.message;
|
||||
const errorCode = err.message;
|
||||
|
||||
return responseCode || errorCode || 'unknown error';
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
import Log from './log';
|
||||
import { Arguments, ArgvNoOptions, OptionConfig } from './types';
|
||||
|
||||
export interface Command<P extends object, O extends object> {
|
||||
/**
|
||||
* Command name and positional arguments as specified to yargs.
|
||||
*/
|
||||
command: string;
|
||||
|
||||
/**
|
||||
* Optional aliases that can be used instead of the main command name.
|
||||
*/
|
||||
aliases?: string[];
|
||||
|
||||
/**
|
||||
* Description of the command functionality and behavior.
|
||||
*/
|
||||
describe: string;
|
||||
|
||||
/**
|
||||
* Map of command option names to their configurations. All configuration
|
||||
* that can be passed to a yargs option is allowed, except for the
|
||||
* `required` key.
|
||||
*/
|
||||
options?: {
|
||||
[K in keyof O]: OptionConfig<O[K]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Optional builder function for specifying additional command setup. Allows
|
||||
* all of the same capabilities as the yargs function of the same name
|
||||
* except for setting options as those are done using the `options` key.
|
||||
*/
|
||||
builder?: (yargs: ArgvNoOptions) => ArgvNoOptions;
|
||||
|
||||
/**
|
||||
* Command handler function invoked when executing this command. It may be
|
||||
* synchronous or asynchronous.
|
||||
*/
|
||||
handler: (args: Arguments<P & O>, log: Log) => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command with the given configuration parameters in the CLI.
|
||||
*
|
||||
* @param command Command configuration object.
|
||||
*/
|
||||
export default function command<P extends object = {}, O extends object = {}>(command: Command<P, O>) {
|
||||
return {
|
||||
command: command.command,
|
||||
aliases: Array.isArray(command.aliases) && command.aliases.length > 0 ? command.aliases : undefined,
|
||||
describe: command.describe,
|
||||
builder(yargs: yargs.Argv) {
|
||||
if (command.builder) {
|
||||
yargs = command.builder(yargs) as yargs.Argv;
|
||||
}
|
||||
|
||||
if (command.options) {
|
||||
yargs = yargs.options(command.options);
|
||||
}
|
||||
|
||||
return yargs;
|
||||
},
|
||||
async handler(args: Arguments<P & O>) {
|
||||
const log = new Log(args);
|
||||
|
||||
const rejectionHandler = (err: any) => {
|
||||
log.error(err);
|
||||
process.exit(1);
|
||||
};
|
||||
process.on('unhandledRejection', rejectionHandler);
|
||||
|
||||
try {
|
||||
await command.handler(args, log);
|
||||
} catch (e) {
|
||||
rejectionHandler(e);
|
||||
} finally {
|
||||
process.removeListener('unhandledRejection', rejectionHandler);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a command group with the given name(s) and description. All commands
|
||||
* created in a sibling directory with the same name as the group will be
|
||||
* available as part of that group.
|
||||
*
|
||||
* @param cmd The name of the command group as it appears in the command
|
||||
* line. Can also be specified as an array, where entries after
|
||||
* the first one are aliases for the command group. Please note
|
||||
* that the expected folder name (as well as the name that
|
||||
* appears in the help text) is always the first entry in the
|
||||
* array.
|
||||
* @param describe The description of the command group that appears in the help
|
||||
* text.
|
||||
*/
|
||||
export function group(cmd: string | string[], describe: string) {
|
||||
const callerDir = path.dirname(require('get-caller-file')());
|
||||
const relativeToCaller = path.relative(__dirname, callerDir);
|
||||
const mainCommand = Array.isArray(cmd) ? cmd[0] : cmd;
|
||||
return command({
|
||||
command: mainCommand,
|
||||
aliases: Array.isArray(cmd) ? cmd.slice(1) : [],
|
||||
describe,
|
||||
builder(yargs) {
|
||||
return yargs
|
||||
.demandCommand(1)
|
||||
.commandDir(path.join(relativeToCaller, mainCommand));
|
||||
},
|
||||
handler: () => { }
|
||||
});
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import Conf = require('conf');
|
||||
|
||||
import { CONFIG_FOLDER } from './constants';
|
||||
|
||||
export type ConfigKey =
|
||||
'core.rejectUnauthorized' |
|
||||
'core.tokenCacheTtl' |
|
||||
'iotc.api.host' |
|
||||
'iotc.api.version' |
|
||||
'iotc.credentials.token' |
|
||||
'iotc.credentials.application' |
|
||||
'iotc.credentials.hubs';
|
||||
|
||||
const defaults: { [K in ConfigKey]?: unknown } = {
|
||||
'core.rejectUnauthorized': false,
|
||||
'core.tokenCacheTtl': 10 * 60 * 1000, // 10 minutes
|
||||
'iotc.api.host': 'api.azureiotcentral.com',
|
||||
'iotc.api.version': 'v1-beta'
|
||||
};
|
||||
|
||||
// Default configuration values. We keep these out of the defaults provided to
|
||||
// conf because if we ever have to change the defaults, they will not be
|
||||
// respected.
|
||||
|
||||
const conf = new Conf<any>({ projectName: CONFIG_FOLDER });
|
||||
|
||||
export function get(key: ConfigKey): unknown {
|
||||
return conf.has(key) ? conf.get(key) : defaults[key];
|
||||
}
|
||||
|
||||
export function getOverride(key: ConfigKey): unknown {
|
||||
return conf.get(key);
|
||||
}
|
||||
|
||||
export function getDefault(key: ConfigKey): unknown {
|
||||
return defaults[key];
|
||||
}
|
||||
|
||||
export function set(key: ConfigKey, value: any) {
|
||||
return conf.set(key, value);
|
||||
}
|
||||
|
||||
export function list(): unknown {
|
||||
return conf.store;
|
||||
}
|
||||
|
||||
export function del(key: ConfigKey) {
|
||||
conf.delete(key);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
export const SAS_TOKEN_PREFIX = 'SharedAccessSignature ';
|
||||
export const CONFIG_FOLDER = 'iotc-explorer';
|
||||
|
||||
export const DEFAULT_CLI_WIDTH = 80;
|
||||
export const DIVIDER_CHAR = '=';
|
||||
export const MIN_DIVIDER_PAD = 5;
|
|
@ -0,0 +1,13 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
export default class CliError extends Error {
|
||||
code: string;
|
||||
|
||||
constructor(code: string, message: string) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import * as prettyjson from 'prettyjson';
|
||||
import stringWidth = require('string-width');
|
||||
import * as util from 'util';
|
||||
|
||||
import * as resources from '../resources.json';
|
||||
|
||||
import { DEFAULT_CLI_WIDTH, DIVIDER_CHAR, MIN_DIVIDER_PAD } from './constants';
|
||||
import CliError from './error';
|
||||
import * as opts from './options';
|
||||
|
||||
export default class Log {
|
||||
private _raw: boolean;
|
||||
|
||||
constructor(options?: Partial<opts.raw>) {
|
||||
this._raw = !!(options && options.raw);
|
||||
}
|
||||
|
||||
info(message: string) {
|
||||
if (!this._raw) {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
error(error: any) {
|
||||
if (error instanceof CliError) {
|
||||
console.error(util.format(resources.general.knownError, error.message, error.code));
|
||||
} else {
|
||||
console.error(util.format(resources.general.unknownError, error && error.message || error));
|
||||
}
|
||||
}
|
||||
|
||||
json(value: any) {
|
||||
if (this._raw) {
|
||||
console.log(JSON.stringify(value));
|
||||
} else {
|
||||
console.log(prettyjson.render(value));
|
||||
}
|
||||
}
|
||||
|
||||
divider(message?: string) {
|
||||
if (this._raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cliWidth = typeof (process.stdout as any).getWindowSize === 'function'
|
||||
? (process.stdout as any).getWindowSize()[0]
|
||||
: DEFAULT_CLI_WIDTH;
|
||||
|
||||
if (!message) {
|
||||
console.log(DIVIDER_CHAR.repeat(cliWidth));
|
||||
} else {
|
||||
const messageWidth = stringWidth(message);
|
||||
const averagePad = Math.max(MIN_DIVIDER_PAD, (cliWidth - messageWidth) / 2);
|
||||
|
||||
// If there is an odd difference between the widths, we give the extra pad to the left
|
||||
const leftPad = Math.ceil(averagePad);
|
||||
const rightPad = Math.floor(averagePad);
|
||||
|
||||
console.log(`${DIVIDER_CHAR.repeat(leftPad - 1)} ${message} ${DIVIDER_CHAR.repeat(rightPad - 1)}`);
|
||||
}
|
||||
}
|
||||
|
||||
blank() {
|
||||
if (!this._raw) {
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import * as resources from '../resources.json';
|
||||
|
||||
import { OptionConfig } from './types';
|
||||
|
||||
export type Options<T extends object> = {
|
||||
[K in keyof T]: OptionConfig<T[K]>
|
||||
};
|
||||
|
||||
export type raw = { raw: boolean; };
|
||||
export const raw: Options<raw> = {
|
||||
raw: {
|
||||
alias: 'r',
|
||||
default: false,
|
||||
describe: resources.options.raw,
|
||||
type: 'boolean'
|
||||
}
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
export type OptionType<T> = T extends any[] ? 'array'
|
||||
: T extends string ? 'string'
|
||||
: T extends number ? 'number' | 'count'
|
||||
: T extends boolean ? 'boolean'
|
||||
: NonNullable<yargs.Options['type']>;
|
||||
|
||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
export type ArgvNoOptions = Omit<yargs.Argv, 'option' | 'options'>;
|
||||
|
||||
export type Arguments<T extends object> = Pick<yargs.Arguments, '_' | '$0'> & T;
|
||||
|
||||
export type OptionConfig<T> = Omit<yargs.Options, 'required' | 'require'> & { type: OptionType<T> };
|
|
@ -0,0 +1,23 @@
|
|||
#! /usr/bin/env node
|
||||
|
||||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
require('source-map-support').install();
|
||||
|
||||
function main(): yargs.Argv {
|
||||
return yargs
|
||||
.locale('en')
|
||||
.commandDir('commands')
|
||||
.help()
|
||||
.demandCommand(1)
|
||||
.strict()
|
||||
.wrap(yargs.terminalWidth());
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
main().argv;
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"errors": {
|
||||
"sasToken": {
|
||||
"invalidPrefix": "Provided token is not a SharedAccessSignature token. Please provide a valid token",
|
||||
"malformed": "Provided SAS token is malformed. Please provide a valid token",
|
||||
"invalid": "Provided SAS token is invalid. Please provide a valid token",
|
||||
"expired": "Provided SAS token is expired. Please provide a valid token"
|
||||
},
|
||||
"iotHub": {
|
||||
"deviceNotFound": "Could not find twin for device with id %s",
|
||||
"registryError": "Failed to fetch twin from IoT Hub with error '%s'"
|
||||
},
|
||||
"iotCentral": {
|
||||
"invalidCredentials": "Invalid credentials. Please login and then retry this action",
|
||||
"genericError": "IoT Central API call failed with message %s"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"knownError": "Error: %s [%s]",
|
||||
"unknownError": "Unknown Error: %s"
|
||||
},
|
||||
"commands": {
|
||||
"login": {
|
||||
"description": "Log in to an Azure IoT Central application",
|
||||
"prompts": {
|
||||
"token": "SAS Token"
|
||||
},
|
||||
"logs": {
|
||||
"successWithExpiry": "Login successful. Session expires %s",
|
||||
"successNoExpiry": "Login successful."
|
||||
}
|
||||
},
|
||||
"getTwin": {
|
||||
"description": "Get the IoT Hub device twin for a specific device",
|
||||
"options": {
|
||||
"hideMetadata": "Hide all device twin metadata"
|
||||
}
|
||||
},
|
||||
"monitorMessages": {
|
||||
"description": "Monitor messages being sent to a specific device (if device id is provided), or all devices",
|
||||
"options": {
|
||||
"startTime": "Only messages sent after start-time will be displayed. Defaults to current unix ms epoch time"
|
||||
},
|
||||
"logs": {
|
||||
"monitoringDevice": "Monitoring messages from device %s...",
|
||||
"monitoringAll": "Monitoring messages from all devices...",
|
||||
"messageHeaderKnownDevice": "Device %s at %s",
|
||||
"messageHeaderUnknownDevice": "Unknown device at %s"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"description": "Manage configuration values for the CLI",
|
||||
"commands": {
|
||||
"get": {
|
||||
"description": "Retrieve the value for the requested configuration key"
|
||||
},
|
||||
"set": {
|
||||
"description": "Set the specified key to the given value"
|
||||
},
|
||||
"list": {
|
||||
"description": "List all configuration keys and values"
|
||||
},
|
||||
"delete": {
|
||||
"description": "Delete the specified configuration key, reverting to the default value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"raw": "Return the raw machine-readable output instead of pretty-printed output"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"newLine": "lf",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"preserveWatchOutput": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"rulesDirectory": ["tslint-consistent-codestyle"],
|
||||
"rules": {
|
||||
"align": [
|
||||
true,
|
||||
"parameters"
|
||||
],
|
||||
"array-type": [
|
||||
true,
|
||||
"array"
|
||||
],
|
||||
"class-name": true,
|
||||
"curly": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"file-header": [
|
||||
true,
|
||||
"^!\\s*\\* Copyright \\(c\\) Microsoft Corporation\\. All rights reserved\\.\n \\* Licensed under the MIT License\\.\\s*$",
|
||||
"Copyright (c) Microsoft Corporation. All rights reserved.\nLicensed under the MIT License."
|
||||
],
|
||||
"import-spacing": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"max-line-length": [true, 120],
|
||||
"new-parens": true,
|
||||
"no-construct": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": true,
|
||||
"no-internal-module": true,
|
||||
"no-invalid-this": true,
|
||||
"no-misused-new": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unused-expression": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"one-line": [
|
||||
true,
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-finally",
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"only-arrow-functions": [
|
||||
true,
|
||||
"allow-declarations"
|
||||
],
|
||||
"ordered-imports": [
|
||||
true,
|
||||
{
|
||||
"grouped-imports": true
|
||||
}
|
||||
],
|
||||
"prefer-const": true,
|
||||
"prefer-for-of": true,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"space-before-function-paren": [
|
||||
true,
|
||||
{
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords"
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-module",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type",
|
||||
"check-typecast",
|
||||
"check-preblock"
|
||||
]
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче