Skip to content

Commit

Permalink
Fix cli option/arg name type-safety
Browse files Browse the repository at this point in the history
  • Loading branch information
jmuzina committed Dec 13, 2024
1 parent eae1b28 commit 7c269f7
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 35 deletions.
40 changes: 14 additions & 26 deletions apps/generator-canonical-ds/src/app/prompting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ It is currently configured to support CLI input.
## Usage
Create a new subgenerator (`src/<generatorName>/index.ts`) and copy the following template:
```typescript
import Generator, {
type BaseOptions,
type PromptAnswers,
} from "yeoman-generator";
import Generator, { type BaseOptions } from "yeoman-generator";
import {
type CLIArgumentAnswer,
type CLIOptionAnswer,
Expand All @@ -22,7 +19,7 @@ import {
Define structure of the answers object
The keys of this type will define the valid "name" values for arguments and options
*/
interface Answers extends PromptAnswers {
interface Answers {
argumentName: string;
optionName: boolean;
}
Expand All @@ -33,54 +30,45 @@ type GeneratorOptions = GeneratorOptionsWithAnswers<
Answers
>;

/*
/*
Define types for the arguments and options
The "name" value for each argument and option must match a key in the Answers type
*/
type CLIArgumentAnswer =
CLIArgumentAnswer<ComponentGeneratorAnswers>;
type MyGeneratorCLIArgumentAnswer = CLIArgumentAnswer<Answers>;

type CLIOptionAnswer =
CLIOptionAnswer<ComponentGeneratorAnswers>;
type MyGeneratorCLIOptionAnswer = CLIOptionAnswer<Answers>;

// Base the custom generator options type into the Generator class
export default class MyGenerator extends Generator<GeneratorOptions> {
// Define arguments and options using the CLIAnswer types
private argumentSpecs: CLIArgumentAnswer[] = [
private argumentSpecs: MyGeneratorCLIArgumentAnswer[] = [
{
type: String,
name: "componentPath",
description: "The path to the component's root directory",
name: "argumentName",
description: "Description of your argument",
required: true,
default: ".",
},
];

private optionSpecs: CLIOptionAnswer[] = [
{
type: Boolean,
name: "includeStyles",
description: "Whether to include styles in the component",
default: false,
},
private optionSpecs: MyGeneratorCLIOptionAnswer[] = [
{
type: Boolean,
name: "includeStory",
description: "Whether to include a storybook story in the component",
name: "optionName",
description: "Description of your option",
default: false,
},
];

answers!: ComponentGeneratorAnswers;
answers!: Answers;

constructor(args: string | string[], options: ComponentGeneratorOptions) {
constructor(args: string | string[], options: GeneratorOptions) {
super(args, options);

// Setup CLI arguments and options and store their answers
// Yeoman will perform CLI input checking and validation for you.
this.answers = getCLIAnswers(this, this.argumentSpecs, this.optionSpecs);
}

writing() {
// consume `this.answers` here...
}
Expand Down
7 changes: 5 additions & 2 deletions apps/generator-canonical-ds/src/app/prompting/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import type {
PromptAnswers,
} from "yeoman-generator";

// The base types from Yeoman for CLI arguments and options. An input can be one of these.
type ArgBaseType = ArgumentSpec | CliOptionSpec;

/**
* A helper type to combine a CLI argument or option with the name of the answer property
* This allows the names of argument objects to be inferred as valid keys of the answers object
* @template TCLIArgType The type of the CLI argument or option
* @template TAnswers The type of the answers object
*/
type CLIArgAnswerHelper<
TCLIArgType,
TCLIArgType extends ArgBaseType,
TAnswers extends PromptAnswers,
> = TCLIArgType & {
name: keyof TAnswers;
name: keyof TAnswers; // Ensure name is one of the keys from TAnswers
};

/**
Expand Down
7 changes: 5 additions & 2 deletions apps/generator-canonical-ds/src/component/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ This generator creates a new React component with a basic structure.

### Getting Started
1. Install [Yeoman](https://yeoman.io/): `bun add -g yo`
2. Run the generator: `yo @canonical/canonical-ds:component`
3. Follow the prompts to create a new React component
2. Run the generator: `yo @canonical/canonical-ds:component path/to/component`

The following optional flags can be used:
- `--withStyles`: Include a CSS file for the component.
- `--withStories`: Include a Storybook file for the component.
7 changes: 2 additions & 5 deletions apps/generator-canonical-ds/src/component/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import path from "node:path";
import Generator, {
type BaseOptions,
type PromptAnswers,
} from "yeoman-generator";
import Generator, { type BaseOptions } from "yeoman-generator";
import globalContext from "../app/global-context.js";
import {
type CLIArgumentAnswer,
Expand All @@ -12,7 +9,7 @@ import {
} from "../app/prompting/index.js";
import casing from "../utils/casing.js";

interface ComponentGeneratorAnswers extends PromptAnswers {
interface ComponentGeneratorAnswers {
/** The path to the component's root directory */
componentPath: string;
/** Whether to include styles in the component */
Expand Down

0 comments on commit 7c269f7

Please sign in to comment.