Skip to content

Commit

Permalink
feat: support document generation for workflows (#523)
Browse files Browse the repository at this point in the history
* GH-240 upgrade packages to remove vulnerabilities

* GH-240 move test fixtures to folders to separate action and workflow tests

* GH-240 duplicate tests for workflows

* GH-420 add run scripts to run action/workflow manually and run tests for action/workflow separately

* WIP

* 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

* Remove unused files

* GH-240 fix error with enum

* GH-240 ability to display workflow inputs and outputs

* GH-240 add usage

* GH-240 start adding workflow tests

* GH-240 get a few more workflow tests working

* Update action test files with main

* GH-240 fix test for readme

* GH-240 all fields readme test working

* GH-240 sort out renaming of action > workflow of test files

* GH-240 fix crlf test

* GH-240 all tests fixed

* GH-240 amend readme for workflow functionality

* GH-240 add back in new lines at end of files

* GH-240 tweak to readme to clarify description is only for actions

* GH-240 Fix tests

* GH-240 fix workflow syntax

* GH-240 add source option and deprecate action option

* GH-240 Make replace in readme backwards compatible with source/action

* GH-240 add backwards compatibility test

* Fix missed -a to -s in readme

Co-authored-by: Niek Palm <npalm@users.noreply.github.com>

* GH-420 rename ActionYml to YmlStructure, and actionFile to sourceFile

---------

Co-authored-by: Niek Palm <npalm@users.noreply.github.com>
  • Loading branch information
larmitage-bjss and npalm committed Mar 6, 2024
1 parent 7d4beb6 commit f043f7f
Show file tree
Hide file tree
Showing 72 changed files with 1,645 additions and 494 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error"
"@typescript-eslint/unbound-method": "error",
"no-shadow": "off",
"@typescript-eslint/no-shadow": "error"
},
"env": {
"node": true,
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ Please provide tests for your contribution.
To generate a new fixture for Markdown output run

```bash
./lib/cli.js --no-banner -a __tests__/fixtures/<action>.yml | \
./lib/cli.js --no-banner -s __tests__/fixtures/<action>.yml | \
tail -r | tail -n +2 | tail -r > <action_md>.output
```

For creating a README.md fixture, first create the readme input file for the test, and copy the same file to the an output file. Next run the following command to update the output which can be used to verify the test.


```bash
./lib/cli.js --no-banner -a __tests__/fixtures/<action>.yml -u __tests_/fixtures/<readme>.output
./lib/cli.js --no-banner -s __tests__/fixtures/<action>.yml -u __tests_/fixtures/<readme>.output
```

### Use a Consistent Coding Style
Expand Down
55 changes: 30 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,28 @@

# Action docs

A CLI to generate and update documentation for GitHub actions, based on the action definition `.yml`. To update your README in a GitHub workflow you can use the [action-docs-action](https://github.com/npalm/action-docs-action).
A CLI to generate and update documentation for GitHub actions or workflows, based on the definition `.yml`. To update your README in a GitHub workflow you can use the [action-docs-action](https://github.com/npalm/action-docs-action).

## TL;DR

### Add the following comment blocks to your README.md

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

<!-- action-docs-inputs action="action.yml" -->
<!-- action-docs-description source="action.yml" --> # applicable for actions only

<!-- action-docs-outputs action="action.yml" -->
<!-- action-docs-inputs source="action.yml" -->

<!-- action-docs-runs action="action.yml" -->
<!-- action-docs-outputs source="action.yml" -->

<!-- action-docs-runs source="action.yml" --> # applicable for actions only
```

Optionally you can also add the following section to generate a usage guide, replacing \<project\> and \<version\> with the name and version of your project you would like to appear in your usage guide.

```md
<!-- action-docs-usage action="action.yml" project="<project>" version="<version>" -->
<!-- action-docs-usage source="action.yml" project="<project>" version="<version>" -->
```

### Generate docs via CLI
Expand Down Expand Up @@ -55,39 +57,42 @@ 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"]
-n, --include-name-header Include a header with the action/workflow name.
--version Show version number [boolean]
-t, --toc-level TOC level used for markdown [number] [default: 2]
-a, --action GitHub action file
[deprecated: use "source" instead] [string] [default: "action.yml"]
-s, --source GitHub source 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
[boolean]
--help Show help [boolean]
```

### 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-header source="action.yml" -->

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

<!-- action-docs-inputs action="action.yml" -->
<!-- action-docs-inputs source="action.yml" -->

<!-- action-docs-outputs action="action.yml" -->
<!-- action-docs-outputs source="action.yml" -->

<!-- action-docs-runs action="action.yml" -->
<!-- action-docs-runs source="action.yml" -->
```

For updating other Markdown files add the name of the file to the command `action-docs -u <file>`.

If you need to use `another/action.yml`:

1. write it in tags like `action="another/action.yml"`;
1. specify in a command via the `-a` option like `action-docs -a another/action.yml`
1. write it in tags like `source="another/action.yml"`;
2. specify in a command via the `-s` option like `action-docs -s another/action.yml`

### Examples

Expand All @@ -106,13 +111,13 @@ action-docs --update-readme
#### Print action markdown for non default action file

```bash
action-docs --action another/action.yaml
action-docs --source another/action.yaml
```

#### Update readme, custom action file and set TOC level 3, custom readme

```bash
action-docs --action ./some-dir/action.yml --toc-level 3 --update-readme docs.md
action-docs --source ./some-dir/action.yml --toc-level 3 --update-readme docs.md
```

## API
Expand All @@ -121,7 +126,7 @@ action-docs --action ./some-dir/action.yml --toc-level 3 --update-readme docs.md
import { generateActionMarkdownDocs } from 'action-docs'

await generateActionMarkdownDocs({
actionFile: 'action.yml'
sourceFile: 'action.yml'
tocLevel: 2
updateReadme: true
readmeFile: 'README.md'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { generateActionMarkdownDocs, Options } from "../src";
import { readFileSync, writeFileSync, copyFileSync, unlink } from "fs";
import * as path from "path";

const fixtureDir = path.join("__tests__", "fixtures");
const fixtureDir = path.join("__tests__", "fixtures", "action");

// By default an 'action.yml' is expected at the runtime location. Therefore we copy one during th test.
// By default an 'action.yml' is expected at the runtime location. Therefore we copy one during the test.
beforeAll(() => {
copyFileSync(path.join(fixtureDir, "action.yml"), "action.yml");
});
Expand Down Expand Up @@ -38,7 +38,7 @@ describe("Test output", () => {

test("A minimal action definition.", async () => {
const markdown = await generateActionMarkdownDocs({
actionFile: path.join(fixtureDir, "minimal_action.yml"),
sourceFile: path.join(fixtureDir, "minimal_action.yml"),
});
const expected = <string>(
readFileSync(path.join(fixtureDir, "minimal_action.output"), "utf-8")
Expand All @@ -49,7 +49,7 @@ describe("Test output", () => {

test("All fields action definition.", async () => {
const markdown = await generateActionMarkdownDocs({
actionFile: path.join(fixtureDir, "all_fields_action.yml"),
sourceFile: path.join(fixtureDir, "all_fields_action.yml"),
});
const expected = <string>(
readFileSync(path.join(fixtureDir, "all_fields_action.output"), "utf-8")
Expand All @@ -61,16 +61,35 @@ describe("Test output", () => {

describe("Test update readme ", () => {
test("Empty readme (all fields)", 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.output"),
});
await testReadme(
{
sourceFile: path.join(fixtureDir, "all_fields_action.yml"),
originalReadme: path.join(fixtureDir, "all_fields_readme.input"),
fixtureReadme: path.join(fixtureDir, "all_fields_readme.output"),
},
{
includeNameHeader: true,
},
);
});
test("Empty readme (all fields) with header", async () => {
await testReadme(
{
sourceFile: path.join(fixtureDir, "all_fields_action.yml"),
originalReadme: path.join(fixtureDir, "all_fields_readme.input"),
fixtureReadme: path.join(fixtureDir, "all_fields_readme_header.output"),
},
{
includeNameHeader: true,
},
false,
true,
);
});

test("Filled readme (all fields)", async () => {
await testReadme({
actionFile: path.join(fixtureDir, "all_fields_action.yml"),
sourceFile: path.join(fixtureDir, "all_fields_action.yml"),
originalReadme: path.join(fixtureDir, "all_fields_readme_filled.input"),
fixtureReadme: path.join(fixtureDir, "all_fields_readme_filled.output"),
});
Expand All @@ -79,11 +98,13 @@ describe("Test update readme ", () => {
test("Filled readme (all fields) with header", async () => {
await testReadme(
{
actionFile: path.join(fixtureDir, "all_fields_action.yml"),
sourceFile: path.join(fixtureDir, "all_fields_action.yml"),
originalReadme: path.join(fixtureDir, "all_fields_readme.input"),
fixtureReadme: path.join(fixtureDir, "all_fields_readme_header.output"),
},
{},
{
includeNameHeader: true,
},
false,
true,
);
Expand All @@ -92,7 +113,7 @@ describe("Test update readme ", () => {
test("Readme (all fields) with CRLF line breaks", async () => {
await testReadme(
{
actionFile: path.join(fixtureDir, "all_fields_action.yml.crlf"),
sourceFile: path.join(fixtureDir, "all_fields_action.yml.crlf"),
originalReadme: path.join(fixtureDir, "all_fields_readme.input.crlf"),
fixtureReadme: path.join(fixtureDir, "all_fields_readme.output.crlf"),
},
Expand All @@ -102,7 +123,7 @@ describe("Test update readme ", () => {

test("Readme (inputs) for action-docs-action", async () => {
await testReadme({
actionFile: path.join(fixtureDir, "action_docs_action_action.yml"),
sourceFile: path.join(fixtureDir, "action_docs_action_action.yml"),
originalReadme: path.join(fixtureDir, "action_docs_action_readme.input"),
fixtureReadme: path.join(fixtureDir, "action_docs_action_readme.output"),
});
Expand All @@ -111,7 +132,7 @@ describe("Test update readme ", () => {
test("Readme for two action.yml-s", async () => {
await testReadme(
{
actionFile: path.join(fixtureDir, "action_docs_action_action.yml"),
sourceFile: path.join(fixtureDir, "action_docs_action_action.yml"),
originalReadme: path.join(fixtureDir, "two_actions_readme.input"),
fixtureReadme: path.join(fixtureDir, "two_actions_readme.output"),
},
Expand All @@ -120,7 +141,7 @@ describe("Test update readme ", () => {
);

await testReadme({
actionFile: path.join(fixtureDir, "all_fields_action.yml"),
sourceFile: path.join(fixtureDir, "all_fields_action.yml"),
originalReadme: path.join(fixtureDir, "two_actions_readme.input"),
fixtureReadme: path.join(fixtureDir, "two_actions_readme.output"),
});
Expand All @@ -130,23 +151,38 @@ describe("Test update readme ", () => {
describe("Test usage format", () => {
test("Multi-line descriptions.", async () => {
await testReadme({
actionFile: path.join(fixtureDir, "action.yml"),
sourceFile: path.join(fixtureDir, "action.yml"),
originalReadme: path.join(fixtureDir, "action_usage_readme.input"),
fixtureReadme: path.join(fixtureDir, "action_usage_readme.output"),
});
});

test("With and without defaults.", async () => {
await testReadme({
actionFile: path.join(fixtureDir, "all_fields_action.yml"),
sourceFile: path.join(fixtureDir, "all_fields_action.yml"),
originalReadme: path.join(fixtureDir, "all_fields_usage_readme.input"),
fixtureReadme: path.join(fixtureDir, "all_fields_usage_readme.output"),
});
});
});

describe("Backwards compatibility", () => {
test("Deprecated action option still works correctly", async () => {
await testReadme(
{
sourceFile: path.join(fixtureDir, "all_fields_action.yml"),
originalReadme: path.join(fixtureDir, "action_deprecated.input"),
fixtureReadme: path.join(fixtureDir, "action_deprecated.output"),
},
{
includeNameHeader: true,
},
);
});
});

interface ReadmeTestFixtures {
actionFile: string;
sourceFile: string;
originalReadme: string;
fixtureReadme: string;
}
Expand All @@ -160,18 +196,21 @@ async function testReadme(
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,
});
try {
await generateActionMarkdownDocs({
sourceFile: files.sourceFile,
updateReadme: true,
includeNameHeader: includeNameHeader,
readmeFile: files.originalReadme,
...overwriteOptions,
});

const updated = <string>readFileSync(files.originalReadme, "utf-8");
const updated = <string>readFileSync(files.originalReadme, "utf-8");

if (doExpect) {
if (doExpect) {
expect(updated).toEqual(expected);
}
} finally {
writeFileSync(files.originalReadme, original);
expect(updated).toEqual(expected);
}
}

0 comments on commit f043f7f

Please sign in to comment.