Skip to content

Commit

Permalink
build(build-tools): Upgrade oclif to latest (#14204)
Browse files Browse the repository at this point in the history
oclif released a new version; this PR upgrades the build-tools projects
that use oclif to the latest.

Most notable improvements for us:

- Simplifies typing in inherited classes.
- More consistent handling of args -- they're handled like flags now.
- ESM-ready, but will wait to switch to ESM until we're using node 16.

I also set the NODE_OPTIONS='--experimental-abortcontroller' environment
variable as a workaround for oclif/core#630 in
Node 14.

More upgrade info at
https://github.com/oclif/core/blob/main/MIGRATION.md.
  • Loading branch information
tylerbutler authored Mar 2, 2023
1 parent ce73505 commit fe5149e
Show file tree
Hide file tree
Showing 35 changed files with 1,344 additions and 1,029 deletions.
1 change: 1 addition & 0 deletions build-tools/.npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
engine-strict=true
strict-peer-dependencies=true
5 changes: 4 additions & 1 deletion build-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,15 @@
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^7.0.0",
"inquirer": "^8.0.0",
"lerna": "^5.1.8",
"lerna": "^5.6.2",
"prettier": "~2.6.2",
"rimraf": "^2.6.2",
"run-script-os": "^1.1.6",
"typescript": "~4.5.5"
},
"engines": {
"node": ">=14.17.0"
},
"packageManager": "[email protected]",
"pnpm": {
"peerDependencyRules": {
Expand Down
8 changes: 2 additions & 6 deletions build-tools/packages/build-cli/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,12 @@ module.exports = {
// oclif uses default exports for commands
"import/no-default-export": "off",

// This package uses interfaces and types that are not exposed directly by oclif and npm-check-updates.
// This package uses interfaces and types that are not exposed directly by npm-check-updates.
// We also call commands' run method directly in some cases, so these are all excluded.
"import/no-internal-modules": [
"error",
{
allow: [
"@oclif/core/lib/interfaces",
"npm-check-updates/build/src/types/**",
"**/commands/**",
],
allow: ["npm-check-updates/build/src/types/**", "**/commands/**"],
},
],

Expand Down
5 changes: 5 additions & 0 deletions build-tools/packages/build-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ This package outputs its build files to `lib/` instead of `dist/` like most of o
oclif uses the lib folder by convention, and there are oclif bugs that can be avoided by putting stuff in lib. See the
PR here for an example: <https://github.com/microsoft/FluidFramework/pull/12155>

---

Due to https://github.com/oclif/core/issues/630, the `build:manifest` node script uses an experimental flag. This can be
removed once we have upgraded to Node 16 in the repo.

### Testing

The `release` command provides a `testMode` flag, which subclasses are expected to check when handling states. If in
Expand Down
21 changes: 5 additions & 16 deletions build-tools/packages/build-cli/bin/dev
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
#!/usr/bin/env node

const oclif = require('@oclif/core')

const path = require('path')
const project = path.join(__dirname, '..', 'tsconfig.json')

// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development'

require('ts-node').register({project})

// In dev mode, always show stack traces
oclif.settings.debug = true;

// Start the CLI
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
// eslint-disable-next-line node/shebang
(async () => {
const oclif = await import("@oclif/core");
await oclif.execute({ type: "cjs", development: true, dir: __dirname });
})();
2 changes: 1 addition & 1 deletion build-tools/packages/build-cli/docs/autocomplete.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ EXAMPLES
$ flub autocomplete --refresh-cache
```

_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v1.3.6/src/commands/autocomplete/index.ts)_
_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v2.1.3/src/commands/autocomplete/index.ts)_
9 changes: 5 additions & 4 deletions build-tools/packages/build-cli/docs/bump.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ Bumps the version of a release group or package to the next minor, major, or pat

```
USAGE
$ flub bump [PACKAGE_OR_RELEASE_GROUP] [-v] [-t major|minor|patch | --exact <value>] [--exactDepType
^|~| ] [--scheme semver|internal|virtualPatch | ] [-x | --install | --commit | | | ]
$ flub bump PACKAGE_OR_RELEASE_GROUP [-v] [-t major|minor|patch | --exact <value>] [--exactDepType ^|~|
] [--scheme semver|internal|virtualPatch | ] [-x | --install | --commit | | | ]
ARGUMENTS
PACKAGE_OR_RELEASE_GROUP The name of a package or a release group.
Expand All @@ -25,8 +25,9 @@ FLAGS
-x, --skipChecks Skip all checks.
--[no-]commit Commit changes to a new branch.
--exact=<value> An exact string to use as the version. The string must be a valid semver string.
--exactDepType=(^|~|) [default: ^] When using the exact flag, controls the type of dependency that is used between
--exactDepType=<option> [default: ^] When using the exact flag, controls the type of dependency that is used between
packages within the release group.
<options: ^|~|>
--[no-]install Update lockfiles by running 'npm install' automatically.
--scheme=<option> Override the version scheme used by the release group or package.
<options: semver|internal|virtualPatch>
Expand Down Expand Up @@ -61,7 +62,7 @@ Update the dependency version of a specified package or release group. That is,

```
USAGE
$ flub bump deps [PACKAGE_OR_RELEASE_GROUP] [-v] [--prerelease -t
$ flub bump deps PACKAGE_OR_RELEASE_GROUP [-v] [--prerelease -t
latest|newest|greatest|minor|patch|@next|@canary] [--onlyBumpPrerelease] [-g client|server|azure|build-tools | -p
<value>] [-x | --install | --commit | | | ]
Expand Down
2 changes: 1 addition & 1 deletion build-tools/packages/build-cli/docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ DESCRIPTION
list all the commands
```

_See code: [@oclif/plugin-commands](https://github.com/oclif/plugin-commands/blob/v2.2.0/src/commands/commands.ts)_
_See code: [@oclif/plugin-commands](https://github.com/oclif/plugin-commands/blob/v2.2.10/src/commands/commands.ts)_
5 changes: 0 additions & 5 deletions build-tools/packages/build-cli/docs/generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,10 @@ DESCRIPTION
The readme must have any of the following tags inside of it for it to be replaced or else it will do nothing:
# Usage
<!-- usage -->
# Commands
<!-- commands -->
# Table of contents
<!-- toc -->
Customize the code URL prefix by setting oclif.repositoryPrefix in package.json.
Expand Down
10 changes: 5 additions & 5 deletions build-tools/packages/build-cli/docs/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@

Display help for flub.

* [`flub help [COMMAND]`](#flub-help-command)
* [`flub help [COMMANDS]`](#flub-help-commands)

## `flub help [COMMAND]`
## `flub help [COMMANDS]`

Display help for flub.

```
USAGE
$ flub help [COMMAND] [-n]
$ flub help [COMMANDS] [-n]
ARGUMENTS
COMMAND Command to show help for.
COMMANDS Command to show help for.
FLAGS
-n, --nested-commands Include all nested commands in the output.
Expand All @@ -23,4 +23,4 @@ DESCRIPTION
Display help for flub.
```

_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.14/src/commands/help.ts)_
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.2.6/src/commands/help.ts)_
23 changes: 12 additions & 11 deletions build-tools/packages/build-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
"build:copy": "copyfiles -u 1 \"src/**/*.fsl\" lib",
"build:diagrams": "jssm-viz -i \"./src/machines/*.fsl\"",
"build:docs": "api-extractor run --local",
"build:full": "npm run format && npm run build && npm run build:manifest && npm run build:readme && npm run build:diagrams",
"build:full": "npm run build && npm run build:manifest && npm run build:readme && npm run build:diagrams",
"build:full:compile": "npm run build:full",
"build:genver": "gen-version",
"build:machine-diagram": "jssm-viz -i \"./src/machines/*.fsl\"",
"build:manifest": "oclif manifest",
"build:manifest": "cross-env NODE_OPTIONS='--experimental-abortcontroller' oclif manifest",
"build:readme": "node \"./bin/dev\" generate readme --multi",
"ci:build:docs": "api-extractor run",
"clean": "rimraf dist lib oclif.manifest.json *.tsbuildinfo *.build.log",
Expand Down Expand Up @@ -73,13 +73,13 @@
"@fluid-tools/version-tools": "workspace:^",
"@fluidframework/build-tools": "workspace:^",
"@fluidframework/bundle-size-tools": "workspace:^",
"@oclif/core": "^1.9.5",
"@oclif/plugin-autocomplete": "^1.3.5",
"@oclif/plugin-commands": "^2.2.0",
"@oclif/plugin-help": "^5",
"@oclif/plugin-not-found": "^2.3.1",
"@oclif/plugin-plugins": "^2.0.1",
"@oclif/test": "^2",
"@oclif/core": "^2.4.0",
"@oclif/plugin-autocomplete": "^2.1.3",
"@oclif/plugin-commands": "^2.2.10",
"@oclif/plugin-help": "^5.2.6",
"@oclif/plugin-not-found": "^2.3.21",
"@oclif/plugin-plugins": "^2.3.2",
"@oclif/test": "^2.3.8",
"@octokit/core": "^4.0.5",
"@rushstack/node-core-library": "^3.51.1",
"chalk": "^2.4.2",
Expand All @@ -91,7 +91,7 @@
"jssm-viz-cli": "^5.83.0",
"minimatch": "^7.3.0",
"npm-check-updates": "^16.0.0",
"oclif": "^3",
"oclif": "^3.7.0",
"semver": "^7.3.7",
"semver-utils": "^1.1.4",
"simple-git": "^3.14.1",
Expand Down Expand Up @@ -119,9 +119,10 @@
"chai-arrays": "^2.2.0",
"concurrently": "^6.2.0",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.2",
"eslint": "~8.6.0",
"eslint-config-oclif": "^4",
"eslint-config-oclif-typescript": "^1.0.2",
"eslint-config-oclif-typescript": "^1.0.3",
"eslint-config-prettier": "~8.5.0",
"eslint-plugin-eslint-comments": "~3.2.0",
"eslint-plugin-import": "~2.25.4",
Expand Down
6 changes: 3 additions & 3 deletions build-tools/packages/build-cli/src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/
import { type Arg } from "@oclif/core/lib/interfaces";
import { Args } from "@oclif/core";

/**
* A re-usable CLI argument for package or release group names.
*/
export const packageOrReleaseGroupArg: Arg = {
export const packageOrReleaseGroupArg = Args.string({
name: "package_or_release_group",
required: true,
description: "The name of a package or a release group.",
};
});
92 changes: 34 additions & 58 deletions build-tools/packages/build-cli/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/
import { Command, Flags } from "@oclif/core";
import {
FlagInput,
OutputFlags,
ParserOutput,
PrettyPrintableError,
} from "@oclif/core/lib/interfaces";
import { Command, Flags, Interfaces } from "@oclif/core";
// eslint-disable-next-line import/no-internal-modules
import { type PrettyPrintableError } from "@oclif/core/lib/interfaces";
import chalk from "chalk";

import { Context, GitRepo, getResolvedFluidRoot } from "@fluidframework/build-tools";
Expand All @@ -18,22 +14,29 @@ import { indentString } from "./lib";
import { CommandLogger } from "./logging";

/**
* @remarks This is needed to get type safety working in derived classes.
* See {@link https://github.com/oclif/oclif.github.io/pull/142}.
* A type representing all the flags of the base commands and subclasses.
*/
export type InferredFlagsType<T> = T extends FlagInput<infer F>
? F & { json: boolean | undefined }
: any;
export type Flags<T extends typeof Command> = Interfaces.InferredFlags<
typeof BaseCommand["baseFlags"] & T["flags"]
>;
export type Args<T extends typeof Command> = Interfaces.InferredArgs<T["args"]>;

/**
* A base command that sets up common flags that all commands should have. All commands should have this class in their
* A base command that sets up common flags that all commands should have. Most commands should have this class in their
* inheritance chain.
*
* @remarks
*
* This implementation is based on the documentation at https://oclif.io/docs/base_class
*/
export abstract class BaseCommand<T extends typeof BaseCommand.flags>
export abstract class BaseCommand<T extends typeof Command>
extends Command
implements CommandLogger
{
static flags = {
/**
* The flags defined on the base class.
*/
static baseFlags = {
root: rootPathFlag(),
verbose: Flags.boolean({
char: "v",
Expand All @@ -46,60 +49,33 @@ export abstract class BaseCommand<T extends typeof BaseCommand.flags>
}),
};

protected parsedOutput?: ParserOutput;

/**
* The processed arguments that were passed to the CLI.
*/
get processedArgs(): { [name: string]: any } {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this.parsedOutput?.args ?? {};
}

/**
* The processed flags that were passed to the CLI.
*/
get processedFlags(): InferredFlagsType<T> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this.parsedOutput?.flags ?? {};
}

/**
* The flags defined on the base class.
*/
private get baseFlags() {
return this.processedFlags as Partial<OutputFlags<typeof BaseCommand.flags>>;
}

protected flags!: Flags<T>;
protected args!: Args<T>;
private _context: Context | undefined;
private _logger: CommandLogger | undefined;

/**
* Parses the command arguments and stores them in parsedOutput.
*
* @remarks
*
* This function does nothing if parsedOutput is already defined.
*/
protected async parseCmdArgs() {
if (this.parsedOutput === undefined) {
this.parsedOutput = await this.parse(this.ctor);
}
}
public async init(): Promise<void> {
await super.init();

async init() {
await this.parseCmdArgs();
const { args, flags } = await this.parse({
flags: this.ctor.flags,
baseFlags: (super.ctor as typeof BaseCommand).baseFlags,
args: this.ctor.args,
strict: this.ctor.strict,
});
this.flags = flags as Flags<T>;
this.args = args as Args<T>;
}

async catch(err: any) {
protected async catch(err: Error & { exitCode?: number }): Promise<any> {
// add any custom logic to handle errors from the command
// or simply return the parent class error handling
return super.catch(err);
}

async finally(err: any) {
protected async finally(_: Error | undefined): Promise<any> {
// called after run and catch regardless of whether or not the command errored
return super.finally(err);
return super.finally(_);
}

/**
Expand Down Expand Up @@ -250,7 +226,7 @@ export abstract class BaseCommand<T extends typeof BaseCommand.flags>
* Logs a verbose log statement.
*/
public verbose(message: string | Error | undefined): void {
if (this.baseFlags.verbose === true) {
if (this.flags.verbose === true) {
if (typeof message === "string") {
this.log(chalk.grey(`VERBOSE: ${message}`));
} else {
Expand Down
Loading

0 comments on commit fe5149e

Please sign in to comment.