Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding dev-tool to the repo #7872

Merged
merged 33 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
12b99c3
Azure SDK dev-tool first pass
witemple-msft Feb 20, 2020
8a01be3
[dev-tool] dev-tool dev-samples and fixes to all commands
witemple-msft Mar 11, 2020
7970f3e
Nested command structure
witemple-msft Mar 14, 2020
ae97341
Better argument parsing, type-checking, and recursive command structu…
witemple-msft Mar 18, 2020
4379675
Added dev-tool README
witemple-msft Mar 18, 2020
bbd0d47
[ai-text-analytics] Update package.json to use new script.
witemple-msft Mar 18, 2020
8c790ce
Removed some development cruft
witemple-msft Mar 18, 2020
d746960
prettier + eslint
witemple-msft Mar 18, 2020
2e70796
Quick fix to ParsedOptions type
witemple-msft Mar 18, 2020
b024362
WIP
witemple-msft Mar 20, 2020
47ef1cb
Command framework improvements
witemple-msft Mar 24, 2020
2a6a129
Basic unit-tests for package resolution.
witemple-msft Mar 24, 2020
a4a4051
One more test, assorted changes
witemple-msft Mar 24, 2020
5e95de2
README update
witemple-msft Mar 25, 2020
96238ab
Migrated all packages with sample code to use dev-tool
witemple-msft Mar 27, 2020
771869d
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft Mar 27, 2020
2401ec8
Added dummy integration-test stub
witemple-msft Mar 30, 2020
4160090
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft Mar 30, 2020
127a7d9
Added dummy integration-test stub for eslint plugin
witemple-msft Mar 30, 2020
b7752c8
Added dummy integration-test:browser stubs
witemple-msft Mar 30, 2020
80decda
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft Apr 3, 2020
3b44d8f
Added dev-tool dependency to packages using it
witemple-msft Apr 3, 2020
1cbef16
Corrected build:samples step in package.json
witemple-msft Apr 3, 2020
ed3df4d
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft Apr 8, 2020
b7ae412
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft Apr 8, 2020
440c499
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft May 13, 2020
9d14e4e
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft May 15, 2020
f7416ef
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft Jun 10, 2020
2832fd2
WIP
witemple-msft Jun 18, 2020
09fd249
[dev-tool] ts-to-js command
witemple-msft Jun 19, 2020
155bb5b
Merge remote-tracking branch 'upstream/master' into dev-tool
witemple-msft Jul 10, 2020
609b38b
[dev-tool] leaf command test
witemple-msft Jul 10, 2020
f3cd0ff
Fixed more deeply nested samples due to shared code
witemple-msft Jul 10, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions common/tools/dev-tool/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off"
"@typescript-eslint/explicit-function-return-type": "off"
}
}
86 changes: 32 additions & 54 deletions common/tools/dev-tool/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ It provides a place to centralize scripts, resources, and processes for developm

## Installation

`dev-tool` runs using ts-node, so it does not need to be built. It is ready-to-go after a `rush update`. It additionally does not need to be installed to a user's machine in order to be used in `package.json` scripts, since it provdes the `dev-tool` binary to any dependent packages through the `bin` entry in its `package.json`. Simply add `@azure/dev-tool` to the `devDependencies` of a package, and the `dev-tool` binary will become available. If you wish to use `dev-tool` from the CLI manually, you can install it globally on your system by running `npm install -g` from this directory.
`dev-tool` runs using ts-node, so it does not need to be built. It is ready-to-go after a `rush update`. It additionally does not need to be installed to a user's machine in order to be used in `package.json` scripts, since it provides the `dev-tool` binary to any dependent packages through the `bin` entry in its `package.json`. Simply add `@azure/dev-tool` to the `devDependencies` of a package, and the `dev-tool` binary will become available. If you wish to use `dev-tool` from the CLI manually, you can install it globally on your system by running `npm install -g` from this directory.

## Usage

`dev-tool` uses a tree-shaped command hierarchy. For example, at the time of writing, the command tree looks like this:
`dev-tool` uses a command hierarchy. For example, at the time of writing, the command tree looks like this:

`dev-tool`
- `about` (display command help and information)
Expand All @@ -29,12 +29,12 @@ The source hierarchy matches the command hierarchy. Every sub-command has its ow

### Command Interface

Every command file's exports must implement the `CommandModule` interface defined in `src/util/commandModule.ts`. The interface requires that every command export a constant `commandInfo` that implements the `CommandInfo` interface defined in the same file. The `CommandInfo` interface specifies the name, description, and options (command-line arguments) of the command. The command module must also export an async handler function as its default export. Two helper functions, `leafCommand` and `subCommand` are provided to assist with development and to provide strong type-checking when
Every command file's exports must implement the `CommandModule` interface defined in `src/util/commandModule.ts`. The interface requires that every command export a constant `commandInfo` that implements the `CommandInfo` interface defined in the same file. A helper command `makeCommandInfo` is provided to assist with the creation of this interface while providing strong type-checking of command-line options. The `CommandInfo` interface specifies the name, description, and options (command-line arguments) of the command. The command module must also export an async handler function as its default export. Two helper functions, `leafCommand` and `subCommand` are provided to assist with development and to provide strong type-checking when
extending dev-tool.

### Creating a new leaf command

To create a new leaf command in one of the existing sub-command, create a new TypeScript file for that command. Make sure that your module exports the required `commandInfo` and default handler function. When creating a command, use the `leafCommand` helper to get a strongly-typed `options` parameter for your handler.
To create a new leaf command in one of the existing sub-command, create a new TypeScript file for that command. Make sure that your module exports the required `commandInfo` and default handler function. When creating the `commandInfo` object, use the `makeCommandInfo` helper function. When creating a command, use the `leafCommand` helper to get a strongly-typed `options` parameter for your handler.

As an example, we can create a new `hello-world` command under the `dev-tool package` sub-command. The command will print out a string using the many different logging functions. It will accept an argument `--echo <string here>` that specifies the string to be printed.

Expand All @@ -44,51 +44,38 @@ As an example, we can create a new `hello-world` command under the `dev-tool pac
// Licensed under the MIT license

import { createPrinter } from "../../util/printer";
import { leafCommand } from "../../util/commandBuilder";
import { leafCommand, makeCommandInfo } from "../../framework/command";

const log = createPrinter("hello-world");

export const commandInfo = {
name: "hello-world",
description:
"print a lovely message",
options: {
echo: {
kind: "string",
description: "override the message to be printed",
default: "Hello world!"
}
export const commandInfo = makeCommandInfo("hello-world", "print a lovely message", {
echo: {
kind: "string",
description: "override the message to be printed",
default: "Hello world!"
}
} as const;
});

export default leafCommand(commandInfo, async (options) => {
// Demonstrate the colorized command output.
log("Normal:", options.echo);
log.success("Success:", options.echo);
log.info("Info:", options.echo);
log.warn("Warn:", options.echo);
log.error("Error:", options.echo);
log.debug("Debug:", options.echo);

return true;
// Demonstrate the colorized command output.
log("Normal:", options.echo);
log.success("Success:", options.echo);
log.info("Info:", options.echo);
log.warn("Warn:", options.echo);
log.error("Error:", options.echo);
log.debug("Debug:", options.echo);

return true;
});
```

(__Note__: the `as const` after the definition of `commandInfo` is important for the type of `options` in the handler to be inferred as tightly as possible.)
(__Note__: using the `makeCommandInfo` function is required to have strong type-checking on the `options` parameter of the handler. The `options` field of `commandInfo` must have a very strong type, and `makeCommandInfo` takes care of ensuring that the type is as strongly specified as possible.)

As a last step, add a mapping for the `"hello-world"` command to the sub-command map in `src/commands/package/index.ts`. This will allow the command to resolve:

`src/commands/package/index.ts`
```typescript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license

import { subCommand } from "../../util/commandBuilder";

export const commandInfo = {
name: "package",
description: "manage SDK packages in the monorepo"
};
// ...

export default subCommand(commandInfo, {
"hello-world": () => import("./hello-world"),
Expand Down Expand Up @@ -117,19 +104,16 @@ Instead of creating a single file `hello-world.ts`, we will instead create a fol
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license

import { subCommand } from "../../util/commandBuilder";
import { subCommand, makeCommandInfo } from "../../framework/command";

export const commandInfo = {
name: "hello",
description: "commands for printing some lovely messages"
};
export const commandInfo = makeCommandInfo("hello", "commands for printing some lovely messages");

export default subCommand(commandInfo, {
world: () => import("./world")
});
```

(__Note__: Since we don't have any arguments or options to add to the sub-command, the `options` field of `commandInfo` is omitted (since the sub-command just delegates to its child commands, we wouldn't be able to use any options in this parent command anyway).)
(__Note__: Since we don't have any arguments or options to add to the sub-command, the `options` argument to `makeCommandInfo` is omitted (since the sub-command just delegates to its child commands, we wouldn't be able to use any options in this parent command anyway).)

This simple file establishes the mapping from the command name `"world"` to our new command module `src/commands/hello/world.ts`. The contents of `world.ts` are very similar to the previous `hello-world.ts` module, but we will change the `name` field of `commandInfo` and the argument to `createPrinter`:

Expand All @@ -139,22 +123,16 @@ This simple file establishes the mapping from the command name `"world"` to our
// Licensed under the MIT license

import { createPrinter } from "../../util/printer";
import { leafCommand } from "../../util/commandBuilder";
import { leafCommand, makeCommandInfo } from "../../framework/command";

const log = createPrinter("world");

export const commandInfo = {
name: "world",
description:
"print a lovely message",
options: {
echo: {
kind: "string",
description: "override the message to be printed",
default: "Hello world!"
}
}
} as const;
export const commandInfo = makeCommandInfo("world", "print a lovely message", {
echo: {
kind: "string",
description: "override the message to be printed",
default: "Hello world!"
}});

export default leafCommand(commandInfo, async (options) => {
// Demonstrate the colorized command output.
Expand Down
17 changes: 12 additions & 5 deletions common/tools/dev-tool/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
"scripts": {
"audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit",
"build": "tsc",
"build:test": "echo skipped",
"clean": "rimraf dist dist-* *.tgz *.log",
"extract-api": "echo skipped",
"format": "prettier --write \"src/**/*.ts\" \"*.{js,json}\"",
"lint": "eslint src --ext .ts -f html -o template-lintReport.html || exit 0",
"format": "prettier --write src/**/*.ts test/**/*.ts *.{js,json}",
"lint": "eslint src test --ext .ts -f html -o template-lintReport.html || exit 0",
"pack": "npm pack 2>&1",
"prebuild": "npm run clean",
"unit-test": "echo skipped"
"unit-test": "mocha --require ts-node/register test/**/*.spec.ts"
},
"repository": "github:Azure/azure-sdk-for-js",
"author": "Microsoft Corporation",
Expand All @@ -38,14 +39,20 @@
},
"devDependencies": {
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
"@types/chai": "^4.1.6",
"@types/chai-as-promised": "^7.1.0",
"@types/chalk": "~2.2.0",
"@types/fs-extra": "^8.0.0",
"@types/minimist": "~1.2.0",
"@types/mocha": "^5.2.5",
"@types/node": "^8.0.0",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"eslint": "^6.1.0",
"rimraf": "^3.0.0",
"prettier": "^1.16.4"
"mocha": "^6.2.2",
"prettier": "^1.16.4",
"rimraf": "^3.0.0"
}
}
9 changes: 3 additions & 6 deletions common/tools/dev-tool/src/commands/about.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import chalk from "chalk";
import { baseCommands, baseCommandInfo } from ".";
import { resolveProject } from "../util/resolveProject";
import { createPrinter } from "../util/printer";
import { leafCommand } from "../util/commandBuilder";
import { printCommandUsage } from "../util/printCommandUsage";
import { leafCommand, makeCommandInfo } from "../framework/command";
import { printCommandUsage } from "../framework/printCommandUsage";

const log = createPrinter("help");

Expand All @@ -21,10 +21,7 @@ const banner = `\
Developer quality-of-life command for the Azure SDK for JS
`;

export const commandInfo = {
name: "about",
description: "display command help and information"
} as const;
export const commandInfo = makeCommandInfo("about", "display command help and information");

export default leafCommand(commandInfo, async (options) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use TSDoc? I like the idea of having the editor show what things mean. I think TSDoc would help!

console.log(chalk.blueBright(banner));
Expand Down
7 changes: 2 additions & 5 deletions common/tools/dev-tool/src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license

import { subCommand } from "../util/commandBuilder";
import { subCommand, makeCommandInfo } from "../framework/command";

/**
* All of dev-tool's base commands and the modules that define them
Expand All @@ -15,10 +15,7 @@ export const baseCommands = {
/**
* Metadata about the base command, only used in `dev-tool help`
*/
export const baseCommandInfo = {
name: "dev-tool",
description: "Azure SDK for JS dev-tool"
} as const;
export const baseCommandInfo = makeCommandInfo("dev-tool", "Azure SDK for JS dev-tool");

/**
* Default dev-tool subcommand
Expand Down
7 changes: 2 additions & 5 deletions common/tools/dev-tool/src/commands/package/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license

import { subCommand } from "../../util/commandBuilder";
import { subCommand, makeCommandInfo } from "../../framework/command";

export const commandInfo = {
name: "package",
description: "manage SDK packages in the monorepo"
};
export const commandInfo = makeCommandInfo("package", "manage SDK packages in the monorepo");

export default subCommand(commandInfo, {
resolve: () => import("./resolve")
Expand Down
20 changes: 11 additions & 9 deletions common/tools/dev-tool/src/commands/package/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import path from "path";

import { resolveProject } from "../../util/resolveProject";
import { createPrinter } from "../../util/printer";
import { leafCommand } from "../../util/commandBuilder";
import { leafCommand } from "../../framework/command";
import { makeCommandInfo } from "../../framework/command";

const log = createPrinter("resolve-package");

export const commandInfo = {
name: "resolve",
description: "display information about the project that owns a directory",
options: {
export const commandInfo = makeCommandInfo(
"resolve",
"display information about the project that owns a directory",
{
directory: {
shortName: "d",
kind: "multistring",
description: "base directory for resolution (uses CWD if unset)"
kind: "string",
description: "base directory for resolution (uses CWD if unset)",
allowMultiple: true
},
quiet: {
shortName: "q",
Expand All @@ -25,10 +27,10 @@ export const commandInfo = {
description: "output only the directory name with no extra formatting"
}
}
} as const;
);

export default leafCommand(commandInfo, async (options) => {
const dirs = (options.directory || [process.cwd()]).map((p) => path.resolve(p));
const dirs = (options.directory ?? [process.cwd()]).map((p) => path.resolve(p));
for (const dir of dirs) {
try {
const currentPackage = await resolveProject(dir);
Expand Down
10 changes: 5 additions & 5 deletions common/tools/dev-tool/src/commands/samples/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import path from "path";

import { resolveProject } from "../../util/resolveProject";
import { createPrinter } from "../../util/printer";
import { leafCommand } from "../../util/commandBuilder";
import { leafCommand, makeCommandInfo } from "../../framework/command";

const log = createPrinter("dev-samples");

export const commandInfo = {
name: "dev",
description: "link samples to local sources for access to IntelliSense during development"
} as const;
export const commandInfo = makeCommandInfo(
"dev",
"link samples to local sources for access to IntelliSense during development"
);

export default leafCommand(commandInfo, async () => {
const pkg = await resolveProject(process.cwd());
Expand Down
7 changes: 2 additions & 5 deletions common/tools/dev-tool/src/commands/samples/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license

import { subCommand } from "../../util/commandBuilder";
import { subCommand, makeCommandInfo } from "../../framework/command";

export const commandInfo = {
name: "samples",
description: "manage samples in an SDK package"
};
export const commandInfo = makeCommandInfo("samples", "manage samples in an SDK package");

export default subCommand(commandInfo, {
dev: () => import("./dev"),
Expand Down
10 changes: 5 additions & 5 deletions common/tools/dev-tool/src/commands/samples/prep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import path from "path";
import { createPrinter } from "../../util/printer";
import { findMatchingFiles } from "../../util/findMatchingFiles";
import { resolveProject } from "../../util/resolveProject";
import { leafCommand } from "../../util/commandBuilder";
import { leafCommand, makeCommandInfo } from "../../framework/command";

const log = createPrinter("prep-samples");

export const commandInfo = {
name: "prep",
description: "prepare samples for local source-linked execution"
} as const;
export const commandInfo = makeCommandInfo(
"prep",
"prepare samples for local source-linked execution"
);

/**
* Replaces package require/import statements with relative paths for CI
Expand Down
10 changes: 5 additions & 5 deletions common/tools/dev-tool/src/commands/samples/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import path from "path";

import { findMatchingFiles } from "../../util/findMatchingFiles";
import { createPrinter } from "../../util/printer";
import { leafCommand } from "../../util/commandBuilder";
import { leafCommand, makeCommandInfo } from "../../framework/command";

const log = createPrinter("run-samples");

const IGNORE = ["node_modules"];

export const commandInfo = {
name: "run",
description: "execute a sample or all samples within a directory"
};
export const commandInfo = makeCommandInfo(
"run",
"execute a sample or all samples within a directory"
);

/**
* Run a single sample file, accumulating any thrown errors into `accumulatedErrors`
Expand Down
Loading