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

feat: Generator CLI support #80

Merged
merged 7 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
8 changes: 4 additions & 4 deletions apps/generator-canonical-ds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
},
"repository": {
"type": "git",
"url": "https://github.com/canonical/vanilla-monorepo"
"url": "https://github.com/canonical/ds25"
},
"bugs": {
"url": "https://github.com/canonical/vanilla-monorepo/issues"
"url": "https://github.com/canonical/ds25/issues"
},
"homepage": "https://github.com/canonical/vanilla-monorepo#readme",
"homepage": "https://github.com/canonical/ds25#readme",
"files": ["generators"],
"type": "module",
"main": "generators/index.js",
Expand All @@ -28,7 +28,7 @@
"check:ts": "tsc --noEmit"
},
"dependencies": {
"yeoman-generator": "7.3.3"
"yeoman-generator": "^7.4.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
Expand Down
5 changes: 5 additions & 0 deletions apps/generator-canonical-ds/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export default class BaseGenerator extends Generator {
]);
return this.composeWith(
`${globalContext.generatorScriptIdentifer}:${answers.subgenerator || this.subgenerators[0].name}`,
// Pass CLI args to the subgenerator
{
generatorArgs: this.args,
generatorOptions: this.options,
},
);
}

Expand Down
76 changes: 76 additions & 0 deletions apps/generator-canonical-ds/src/app/prompting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Prompting module

This module contains types and functionality allowing Yeoman generators to quickly prompt users for input.
This allows generators to prompt for input and receive answers with less repetitive code.
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 } from "yeoman-generator";
import {
type CLIArgumentAnswer,
type CLIOptionAnswer,
type GeneratorOptionsWithAnswers,
getCLIAnswers,
} from "../app/prompting/index.js";

/*
Define structure of the answers object
The keys of this type will define the valid "name" values for arguments and options
*/
interface Answers {
argumentName: string;
optionName: boolean;
}

type GeneratorOptions = GeneratorOptionsWithAnswers<
// You may also use some other extension of `BaseOptions`
BaseOptions,
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 MyGeneratorCLIArgumentAnswer = CLIArgumentAnswer<Answers>;

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: MyGeneratorCLIArgumentAnswer[] = [
{
type: String,
name: "argumentName",
description: "Description of your argument",
required: true,
},
];

private optionSpecs: MyGeneratorCLIOptionAnswer[] = [
{
type: Boolean,
name: "optionName",
description: "Description of your option",
default: false,
},
];

answers!: Answers;

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...
}
}
```
2 changes: 2 additions & 0 deletions apps/generator-canonical-ds/src/app/prompting/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./prompting.js";
export * from "./types.js";
54 changes: 54 additions & 0 deletions apps/generator-canonical-ds/src/app/prompting/prompting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { BaseOptions, PromptAnswers } from "yeoman-generator";

import type Generator from "yeoman-generator";
import type {
CLIArgumentAnswer,
CLIOptionAnswer,
GeneratorOptionsWithAnswers,
} from "./types.js";

/**
* Configure a generator to use CLI arguments and options, and return the answers
* This function must be called from a generator constructor to register the args with the --help output.
* @template TAnswers The type of answers to the CLI arguments and options
* @template TGeneratorOptions The type of the generator options to configure
* @template TGenerator The type of the generator to configure
* @template TArg The type of the CLI arguments to use
* @template TOpt The type of the CLI options to use
* @param generator The generator to configure
* @param cliArgs The CLI arguments (positional CLI args) to use
* @param cliOptions The CLI options (flags) to use
* @returns The answers to the CLI arguments and options
*/
export function getCLIAnswers<
TAnswers extends PromptAnswers,
TGeneratorOptions extends BaseOptions,
TGenerator extends Generator<
GeneratorOptionsWithAnswers<TGeneratorOptions, TAnswers>
>,
TArg extends CLIArgumentAnswer<TAnswers>,
TOpt extends CLIOptionAnswer<TAnswers>,
>(
generator: TGenerator,
cliArgs: TArg[] = [],
cliOptions: TOpt[] = [],
): TAnswers {
// Initialize the answers object with the default values
const answers: TAnswers = Object.fromEntries(
[...cliArgs, ...cliOptions].map((cliArg) => [cliArg.name, cliArg.default]),
) as TAnswers;

// Add the CLI arguments and options to the generator
for (const argument of cliArgs) {
generator.argument(argument.name, argument);
answers[argument.name] = generator.options[argument.name];
}

// Add the CLI options to the generator
for (const option of cliOptions) {
generator.option(option.name, option);
answers[option.name] = generator.options[option.name];
}

return answers;
}
47 changes: 47 additions & 0 deletions apps/generator-canonical-ds/src/app/prompting/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type {
ArgumentSpec,
BaseOptions,
CliOptionSpec,
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 extends ArgBaseType,
TAnswers extends PromptAnswers,
> = TCLIArgType & {
name: keyof TAnswers; // Ensure name is one of the keys from TAnswers
};

/**
* A CLI argument answer whose name matches some key in the answers object
* @template TAnswers The type of the answers object
*/
export type CLIArgumentAnswer<TAnswers extends PromptAnswers> =
CLIArgAnswerHelper<ArgumentSpec, TAnswers>;

/**
* A CLI option answer whose name matches some key in the answers object
* @template TAnswers The type of the answers object
*/
export type CLIOptionAnswer<TAnswers extends PromptAnswers> =
CLIArgAnswerHelper<CliOptionSpec, TAnswers>;

/**
* A generator options object that includes the answers to CLI arguments and options
* This allows keys in `generator.options` to be inferred as valid keys of the answers object
* @template TOptions The type of the generator options object
* @template TAnswers The type of the answers object
*/
export type GeneratorOptionsWithAnswers<
TOptions extends BaseOptions,
TAnswers extends PromptAnswers,
> = TOptions & TAnswers;
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.
Loading
Loading