Skip to content

Commit

Permalink
feat: support action name attribute (#526)
Browse files Browse the repository at this point in the history
* GH-240 update CONTRIBUTING where command does not work and add additional helper scripts

* GH-240 add option to have a top level header containing the name

* GH-240 add header to readme and tests

* GH-240 update name header shorthand to nh

* GH-240 update name header shorthand to n

---------

Co-authored-by: Niek Palm <npalm@users.noreply.github.com>
  • Loading branch information
larmitage-bjss and npalm committed Feb 15, 2024
1 parent 2147d09 commit 0e99848
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 19 deletions.
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,24 @@ The following options are available via the CLI

```
Options:
--help Show help [boolean]
--version Show version number [boolean]
-t, --toc-level TOC level used for markdown [number] [default: 2]
-a, --action GitHub action file [string] [default: "action.yml"]
--no-banner Print no banner
-u, --update-readme Update readme file. [string]
-l, --line-breaks Used line breaks in the generated docs.
[string] [choices: "CR", "LF", "CRLF"] [default: "LF"]
--help Show help [boolean]
--version Show version number [boolean]
-t, --toc-level TOC level used for markdown [number] [default: 2]
-a, --action GitHub action file [string] [default: "action.yml"]
--no-banner Print no banner
-u, --update-readme Update readme file. [string]
-l, --line-breaks Used line breaks in the generated docs.
[string] [choices: "CR", "LF", "CRLF"] [default: "LF"]
-n, --include-name-header Include a header with the action/workflow name.
```

### Update the README

Action-docs can update your README based on the `action.yml`. The following sections can be updated: description, inputs, outputs and runs. Add the following tags to your README and run `action-docs -u`.

```md
<!-- action-docs-header action="action.yml" -->

<!-- action-docs-description action="action.yml" -->

<!-- action-docs-inputs action="action.yml" -->
Expand Down
26 changes: 26 additions & 0 deletions __tests__/action-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ describe("Test output", () => {
expect(markdown).toEqual(expected);
});

test("With name header included.", async () => {
const markdown = await generateActionMarkdownDocs({
includeNameHeader: true,
});
const expected = <string>(
readFileSync(path.join(fixtureDir, "default-with-header.output"), "utf-8")
);

expect(markdown).toEqual(expected);
});

test("A minimal action definition.", async () => {
const markdown = await generateActionMarkdownDocs({
actionFile: path.join(fixtureDir, "minimal_action.yml"),
Expand Down Expand Up @@ -65,6 +76,19 @@ describe("Test update readme ", () => {
});
});

test("Filled readme (all fields) with header", async () => {
await testReadme(
{
actionFile: path.join(fixtureDir, "all_fields_action.yml"),
originalReadme: path.join(fixtureDir, "all_fields_readme.input"),
fixtureReadme: path.join(fixtureDir, "all_fields_readme_header.output"),
},
{},
false,
true,
);
});

test("Readme (all fields) with CRLF line breaks", async () => {
await testReadme(
{
Expand Down Expand Up @@ -131,13 +155,15 @@ async function testReadme(
files: ReadmeTestFixtures,
overwriteOptions?: Options,
doExpect: boolean = true,
includeNameHeader: boolean = false,
) {
const expected = <string>readFileSync(files.fixtureReadme, "utf-8");
const original = <string>readFileSync(files.originalReadme, "utf-8");

await generateActionMarkdownDocs({
actionFile: files.actionFile,
updateReadme: true,
includeNameHeader: includeNameHeader,
readmeFile: files.originalReadme,
...overwriteOptions,
});
Expand Down
13 changes: 13 additions & 0 deletions __tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ describe("CLI tests", () => {
expect(result.code).toBe(0);
expect(result.stdout).toEqual(`${expected}\n`);
});

test("Console output including name header and no banner.", async () => {
const result = await cli(
`-a ${path.join(fixtureDir, "action.yml")} -n true --no-banner`,
);

const expected = <string>(
readFileSync(path.join(fixtureDir, "default-with-header.output"), "utf-8")
);

expect(result.code).toBe(0);
expect(result.stdout).toEqual(`${expected}\n`);
});
});

interface CliResponse {
Expand Down
65 changes: 65 additions & 0 deletions __tests__/fixtures/all_fields_readme.input
Original file line number Diff line number Diff line change
@@ -1,9 +1,74 @@
<!-- action-docs-header action="__tests__/fixtures/all_fields_action.yml" -->
## An Action
<!-- action-docs-header action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-description action="__tests__/fixtures/all_fields_action.yml" -->
### Description

Default test
<!-- action-docs-description action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-usage action="__tests__/fixtures/all_fields_action.yml" project="npalm/action-docs" version="v1" -->
### Usage

```yaml
- uses: npalm/action-docs@v1
with:
inputA:
# A description A
#
# Required: false
# Default: ""

inputB:
# A description B
#
# Required: true
# Default: ""

inputC:
# A description C
#
# Required: true
# Default: C

inputD:
# A description D
#
# Required: false
# Default: D

inputE:
# A description E
#
# Required: false
# Default: false
```
<!-- action-docs-usage action="__tests__/fixtures/all_fields_action.yml" project="npalm/action-docs" version="v1" -->

<!-- action-docs-inputs action="__tests__/fixtures/all_fields_action.yml" -->
### Inputs

| name | description | required | default |
| --- | --- | --- | --- |
| `inputA` | <p>A description A</p> | `false` | `""` |
| `inputB` | <p>A description B</p> | `true` | `""` |
| `inputC` | <p>A description C</p> | `true` | `C` |
| `inputD` | <p>A description D</p> | `false` | `D` |
| `inputE` | <p>A description E</p> | `false` | `false` |
<!-- action-docs-inputs action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-outputs action="__tests__/fixtures/all_fields_action.yml" -->
### Outputs

| name | description |
| --- | --- |
| `outputA` | <p>A description A</p> |
| `outputB` | <p>A description B</p> |
<!-- action-docs-outputs action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-runs action="__tests__/fixtures/all_fields_action.yml" -->
### Runs

This action is a `node12` action.
<!-- action-docs-runs action="__tests__/fixtures/all_fields_action.yml" -->
4 changes: 4 additions & 0 deletions __tests__/fixtures/all_fields_readme.output
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<!-- action-docs-header action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-header action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-description action="__tests__/fixtures/all_fields_action.yml" -->
## Description

Expand Down
74 changes: 74 additions & 0 deletions __tests__/fixtures/all_fields_readme_header.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!-- action-docs-header action="__tests__/fixtures/all_fields_action.yml" -->
# Test Action
<!-- action-docs-header action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-description action="__tests__/fixtures/all_fields_action.yml" -->
## Description

Default test
<!-- action-docs-description action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-usage action="__tests__/fixtures/all_fields_action.yml" project="npalm/action-docs" version="v1" -->
## Usage

```yaml
- uses: npalm/action-docs@v1
with:
inputA:
# A description A
#
# Required: false
# Default: ""

inputB:
# A description B
#
# Required: true
# Default: ""

inputC:
# A description C
#
# Required: true
# Default: C

inputD:
# A description D
#
# Required: false
# Default: D

inputE:
# A description E
#
# Required: false
# Default: false
```
<!-- action-docs-usage action="__tests__/fixtures/all_fields_action.yml" project="npalm/action-docs" version="v1" -->

<!-- action-docs-inputs action="__tests__/fixtures/all_fields_action.yml" -->
## Inputs

| name | description | required | default |
| --- | --- | --- | --- |
| `inputA` | <p>A description A</p> | `false` | `""` |
| `inputB` | <p>A description B</p> | `true` | `""` |
| `inputC` | <p>A description C</p> | `true` | `C` |
| `inputD` | <p>A description D</p> | `false` | `D` |
| `inputE` | <p>A description E</p> | `false` | `false` |
<!-- action-docs-inputs action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-outputs action="__tests__/fixtures/all_fields_action.yml" -->
## Outputs

| name | description |
| --- | --- |
| `outputA` | <p>A description A</p> |
| `outputB` | <p>A description B</p> |
<!-- action-docs-outputs action="__tests__/fixtures/all_fields_action.yml" -->

<!-- action-docs-runs action="__tests__/fixtures/all_fields_action.yml" -->
## Runs

This action is a `node12` action.
<!-- action-docs-runs action="__tests__/fixtures/all_fields_action.yml" -->
25 changes: 25 additions & 0 deletions __tests__/fixtures/default-with-header.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## An Action

### Description

Default test

### Inputs

| name | description | required | default |
| --- | --- | --- | --- |
| `inputA` | <ul> <li>Item 1<ul> <li>foo, bar, baz</li></ul></li> <li>Item 2<ul> <li><a href="https://github.com/">github</a></li> <li><strong>blah</strong></li></ul></li> <li>Item 3</li> </ul> | `false` | `test` |
| `inputB` | <p>This is a multiline description</p> | `false` | `test` |


### Outputs

| name | description |
| --- | --- |
| `outputA` | <p>A description</p> |


### Runs

This action is a `node12` action.

34 changes: 26 additions & 8 deletions src/action-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ export interface Options {
updateReadme?: boolean;
readmeFile?: string;
lineBreaks?: LineBreakType;
includeNameHeader?: boolean;
}

interface ActionMarkdown {
header: string;
description: string;
inputs: string;
outputs: string;
Expand All @@ -41,6 +43,7 @@ interface DefaultOptions {
updateReadme: boolean;
readmeFile: string;
lineBreaks: LineBreakType;
includeNameHeader?: boolean;
}

export const defaultOptions: DefaultOptions = {
Expand All @@ -49,6 +52,7 @@ export const defaultOptions: DefaultOptions = {
updateReadme: false,
readmeFile: "README.md",
lineBreaks: "LF",
includeNameHeader: false,
};

type ActionInputsOutputs = Record<string, ActionInput | ActionOutput>;
Expand Down Expand Up @@ -150,6 +154,7 @@ export async function generateActionMarkdownDocs(

const docs = generateActionDocs(options);
if (options.updateReadme) {
await updateReadme(options, docs.header, "header", options.actionFile);
await updateReadme(
options,
docs.description,
Expand All @@ -162,7 +167,7 @@ export async function generateActionMarkdownDocs(
await updateReadme(options, docs.usage, "usage", options.actionFile);
}

return `${docs.description + docs.inputs + docs.outputs + docs.runs}`;
return `${docs.header + docs.description + docs.inputs + docs.outputs + docs.runs}`;
}

function generateActionDocs(options: DefaultOptions): ActionMarkdown {
Expand All @@ -172,7 +177,14 @@ function generateActionDocs(options: DefaultOptions): ActionMarkdown {
const usageMdCodeBlock = createMdCodeBlock(yml.inputs, options);
const outputMdTable = createMdTable(yml.outputs, options, "output");

let header = "";
if (options.includeNameHeader) {
header = createMarkdownHeader(options, yml.name);
options.tocLevel++;
}

return {
header,
description: createMarkdownSection(options, yml.description, "Description"),
inputs: createMarkdownSection(options, inputMdTable, "Inputs"),
outputs: createMarkdownSection(options, outputMdTable, "Outputs"),
Expand Down Expand Up @@ -242,13 +254,19 @@ function createMarkdownSection(
data: string,
header: string,
): string {
return data !== ""
? `${getToc(options.tocLevel)} ${header}${getLineBreak(
options.lineBreaks,
)}${getLineBreak(options.lineBreaks)}${data}${getLineBreak(
options.lineBreaks,
)}${getLineBreak(options.lineBreaks)}`
: "";
const lineBreak = getLineBreak(options.lineBreaks);

return data === "" || data === undefined
? ""
: `${createMarkdownHeader(options, header)}${data}` +
`${lineBreak}` +
`${lineBreak}`;
}

function createMarkdownHeader(options: DefaultOptions, header: string): string {
const lineBreak = getLineBreak(options.lineBreaks);

return `${getToc(options.tocLevel)} ${header}${lineBreak}${lineBreak}`;
}

function getInputOutput(
Expand Down

0 comments on commit 0e99848

Please sign in to comment.