New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

action-docs

Package Overview
Dependencies
Maintainers
1
Versions
47
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

action-docs - npm Package Compare versions

Comparing version 2.1.0 to 2.2.0

12

CHANGELOG.md
# Changelog
## [2.2.0](https://github.com/npalm/action-docs/compare/v2.1.0...v2.2.0) (2024-03-06)
### Features
* support document generation for workflows ([#523](https://github.com/npalm/action-docs/issues/523)) ([f043f7f](https://github.com/npalm/action-docs/commit/f043f7f0e017821cad293ebd71293127c462663b))
### Bug Fixes
* **deps:** bump yaml from 2.3.4 to 2.4.0 ([#543](https://github.com/npalm/action-docs/issues/543)) ([0c76a5e](https://github.com/npalm/action-docs/commit/0c76a5e8468fc82d71f3a70b2b277b5d366877e3))
## [2.1.0](https://github.com/npalm/action-docs/compare/v2.0.1...v2.1.0) (2024-02-15)

@@ -4,0 +16,0 @@

6

lib/action-docs.d.ts
import { LineBreakType } from "./linebreak.js";
export interface Options {
tocLevel?: number;
actionFile?: string;
sourceFile?: string;
updateReadme?: boolean;

@@ -12,7 +12,7 @@ readmeFile?: string;

tocLevel: number;
actionFile: string;
sourceFile: string;
updateReadme: boolean;
readmeFile: string;
lineBreaks: LineBreakType;
includeNameHeader?: boolean;
includeNameHeader: boolean;
}

@@ -19,0 +19,0 @@ export declare const defaultOptions: DefaultOptions;

@@ -10,3 +10,3 @@ import { getLineBreak } from "./linebreak.js";

tocLevel: 2,
actionFile: "action.yml",
sourceFile: "action.yml",
updateReadme: false,

@@ -17,2 +17,31 @@ readmeFile: "README.md",

};
var InputType;
(function (InputType) {
InputType[InputType["number"] = 0] = "number";
InputType[InputType["string"] = 1] = "string";
InputType[InputType["boolean"] = 2] = "boolean";
})(InputType || (InputType = {}));
var InputOutputType;
(function (InputOutputType) {
InputOutputType[InputOutputType["actionInput"] = 0] = "actionInput";
InputOutputType[InputOutputType["workflowInput"] = 1] = "workflowInput";
InputOutputType[InputOutputType["actionOutput"] = 2] = "actionOutput";
})(InputOutputType || (InputOutputType = {}));
const inputOutputHeaders = {
[InputOutputType.actionInput]: ["name", "description", "required", "default"],
[InputOutputType.workflowInput]: [
"name",
"description",
"type",
"required",
"default",
],
[InputOutputType.actionOutput]: ["name", "description"],
};
const inputOutputDefaults = {
description: "",
type: "",
required: "false",
default: '""',
};
function createMdTable(data, options, type) {

@@ -23,10 +52,3 @@ const tableData = getInputOutput(data, type);

const result = [headers, filler]
.concat(tableData.rows.map((line) => {
return line.map((elem, i) => {
const pretty = i === 0 || i === 2 || i === 3 ? `\`${line[i]}\`` : elem;
const html = i === 1 ? converter.makeHtml(pretty) : pretty;
const htmlNoNewlines = html.replace(/(\r\n|\n|\r)/gm, " ").trim();
return htmlNoNewlines;
});
}))
.concat(tableData.rows)
.filter((x) => x.length > 0)

@@ -37,19 +59,39 @@ .map((x) => `| ${x.join(" | ")} |${getLineBreak(options.lineBreaks)}`)

}
function createMdCodeBlock(data, options) {
function createMdCodeBlock(data, options, isAction = true) {
let codeBlockArray = ["```yaml"];
codeBlockArray.push("- uses: ***PROJECT***@***VERSION***");
codeBlockArray.push(" with:");
const inputs = getInputOutput(data, "input");
for (const input of inputs.rows) {
const inputBlock = [`${input[0]}:`];
inputBlock.push(...input[1]
let indent = "";
if (isAction) {
codeBlockArray.push("- uses: ***PROJECT***@***VERSION***");
indent += " ";
}
else {
codeBlockArray.push("jobs:");
indent += " ";
codeBlockArray.push(`${indent}job1:`);
indent += " ";
codeBlockArray.push(`${indent}uses: ***PROJECT***@***VERSION***`);
}
codeBlockArray.push(`${indent}with:`);
indent += " ";
const inputs = getInputOutput(data, isAction ? InputOutputType.actionInput : InputOutputType.workflowInput, false);
for (const row of inputs.rows) {
const inputName = row[0];
const inputDescCommented = row[1]
.split(/(\r\n|\n|\r)/gm)
.filter((l) => !["", "\r", "\n", "\r\n"].includes(l))
.map((l) => `# ${l}`));
.map((l) => `# ${l}`);
const type = isAction ? undefined : row[2];
const isRequired = isAction ? row[2] : row[3];
const defaultVal = isAction ? row[3] : row[4];
const inputBlock = [`${inputName}:`];
inputBlock.push(...inputDescCommented);
inputBlock.push("#");
inputBlock.push(`# Required: ${input[2].replace(/`/g, "")}`);
if (input[3]) {
inputBlock.push(`# Default: ${input[3]}`);
if (type) {
inputBlock.push(`# Type: ${type}`);
}
codeBlockArray.push(...inputBlock.map((l) => ` ${l}`));
inputBlock.push(`# Required: ${isRequired}`);
if (defaultVal) {
inputBlock.push(`# Default: ${defaultVal}`);
}
codeBlockArray.push(...inputBlock.map((l) => `${indent}${l}`));
codeBlockArray.push("");

@@ -80,43 +122,94 @@ }

};
const docs = generateActionDocs(options);
if (options.updateReadme) {
await updateReadme(options, docs.header, "header", options.actionFile);
await updateReadme(options, docs.description, "description", options.actionFile);
await updateReadme(options, docs.inputs, "inputs", options.actionFile);
await updateReadme(options, docs.outputs, "outputs", options.actionFile);
await updateReadme(options, docs.runs, "runs", options.actionFile);
await updateReadme(options, docs.usage, "usage", options.actionFile);
const docs = generateDocs(options);
let outputString = "";
for (const key in docs) {
const value = docs[key];
if (options.updateReadme) {
await updateReadme(options, value, key, options.sourceFile);
}
outputString += value;
}
return `${docs.header + docs.description + docs.inputs + docs.outputs + docs.runs}`;
return outputString;
}
function generateActionDocs(options) {
const yml = parse(readFileSync(options.actionFile, "utf-8"));
const inputMdTable = createMdTable(yml.inputs, options, "input");
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++;
function generateDocs(options) {
const yml = parse(readFileSync(options.sourceFile, "utf-8"));
if (yml.runs === undefined) {
return generateWorkflowDocs(yml, options);
}
else {
return generateActionDocs(yml, options);
}
}
function generateActionDocs(yml, options) {
return {
header,
header: generateHeader(yml, options),
description: createMarkdownSection(options, yml.description, "Description"),
inputs: createMarkdownSection(options, inputMdTable, "Inputs"),
outputs: createMarkdownSection(options, outputMdTable, "Outputs"),
inputs: generateInputs(yml.inputs, options, InputOutputType.actionInput),
outputs: generateOutputs(yml.outputs, options),
runs: createMarkdownSection(options,
// eslint-disable-next-line i18n-text/no-en
`This action is a \`${yml.runs.using}\` action.`, "Runs"),
usage: createMarkdownSection(options, usageMdCodeBlock, "Usage"),
usage: generateUsage(yml.inputs, options),
};
}
function generateWorkflowDocs(yml, options) {
return {
header: generateHeader(yml, options),
inputs: generateInputs(yml.on.workflow_call?.inputs, options, InputOutputType.workflowInput),
outputs: generateOutputs(yml.on.workflow_call?.outputs, options),
runs: "",
usage: generateUsage(yml.on.workflow_call?.inputs, options, false),
};
}
function generateHeader(yml, options) {
let header = "";
if (options.includeNameHeader) {
header = createMarkdownHeader(options, yml.name);
options.tocLevel++;
}
return header;
}
function generateInputs(data, options, type) {
const inputMdTable = createMdTable(data, options, type);
return createMarkdownSection(options, inputMdTable, "Inputs");
}
function generateOutputs(data, options) {
const outputMdTable = createMdTable(data, options, InputOutputType.actionOutput);
return createMarkdownSection(options, outputMdTable, "Outputs");
}
function generateUsage(data, options, isAction = true) {
const usageMdCodeBlock = createMdCodeBlock(data, options, isAction);
return createMarkdownSection(options, usageMdCodeBlock, "Usage");
}
function escapeRegExp(x) {
return x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
async function updateReadme(options, text, section, actionFile) {
if (section === "usage") {
const readmeFileText = String(readFileSync(options.readmeFile, "utf-8"));
const match = readmeFileText.match(new RegExp(`<!-- action-docs-usage action="${escapeRegExp(actionFile)}" project="(.*)" version="(.*)" -->.?`));
if (match) {
const commentExpression = `<!-- action-docs-usage action="${actionFile}" project="${match[1]}" version="${match[2]}" -->`;
async function updateReadme(options, text, section, sourceFile) {
const lineBreak = getLineBreak(options.lineBreaks);
const readmeFileText = String(readFileSync(options.readmeFile, "utf-8"));
const sourceOrActionMatches = readmeFileText.match(new RegExp(`<!-- action-docs-${section} (source|action)`));
if (sourceOrActionMatches) {
const sourceOrAction = sourceOrActionMatches[1];
if (section === "usage") {
const match = readmeFileText.match(new RegExp(`<!-- action-docs-usage ${sourceOrAction}="${escapeRegExp(sourceFile)}" project="(.*)" version="(.*)" -->.?`));
if (match) {
const commentExpression = `<!-- action-docs-usage ${sourceOrAction}="${sourceFile}" project="${match[1]}" version="${match[2]}" -->`;
const regexp = new RegExp(`${escapeRegExp(commentExpression)}(?:(?:\r\n|\r|\n.*)+${escapeRegExp(commentExpression)})?`);
const processedText = text
.trim()
.replace("***PROJECT***", match[1])
.replace("***VERSION***", match[2]);
await replaceInFile.replaceInFile({
files: options.readmeFile,
from: regexp,
to: commentExpression +
lineBreak +
processedText +
lineBreak +
commentExpression,
});
}
}
else {
const commentExpression = `<!-- action-docs-${section} ${sourceOrAction}="${sourceFile}" -->`;
const regexp = new RegExp(`${escapeRegExp(commentExpression)}(?:(?:\r\n|\r|\n.*)+${escapeRegExp(commentExpression)})?`);

@@ -126,18 +219,10 @@ await replaceInFile.replaceInFile({

from: regexp,
to: `${commentExpression}${getLineBreak(options.lineBreaks)}${text
.trim()
.replace("***PROJECT***", match[1])
.replace("***VERSION***", match[2])}${getLineBreak(options.lineBreaks)}${commentExpression}`,
to: commentExpression +
lineBreak +
text.trim() +
lineBreak +
commentExpression,
});
}
}
else {
const commentExpression = `<!-- action-docs-${section} action="${actionFile}" -->`;
const regexp = new RegExp(`${escapeRegExp(commentExpression)}(?:(?:\r\n|\r|\n.*)+${escapeRegExp(commentExpression)})?`);
await replaceInFile.replaceInFile({
files: options.readmeFile,
from: regexp,
to: `${commentExpression}${getLineBreak(options.lineBreaks)}${text.trim()}${getLineBreak(options.lineBreaks)}${commentExpression}`,
});
}
}

@@ -156,3 +241,9 @@ function createMarkdownSection(options, data, header) {

}
function getInputOutput(data, type) {
function isHtmlColumn(columnName) {
return columnName === "description";
}
function stripNewLines(value) {
return value.replace(/\r\n|\r|\n/g, " ");
}
function getInputOutput(data, type, format = true) {
let headers = [];

@@ -163,6 +254,3 @@ const rows = [];

}
headers =
type === "input"
? ["name", "description", "required", "default"]
: ["name", "description"];
headers = inputOutputHeaders[type];
for (let i = 0; i < Object.keys(data).length; i++) {

@@ -172,12 +260,27 @@ const key = Object.keys(data)[i];

rows[i] = [];
rows[i].push(key);
rows[i].push(value.description ? value.description : "");
if (type === "input") {
rows[i].push(value.required ? String(value.required) : "false");
if (value.default !== undefined && value.default !== "") {
rows[i].push(value.default.toString().replace(/\r\n|\r|\n/g, " "));
for (const columnName of headers) {
let rowValue = "";
if (columnName === "name") {
rowValue = key;
}
else if (columnName === "default") {
rowValue =
value[columnName] !== undefined && value[columnName] !== ""
? stripNewLines(String(value[columnName]))
: inputOutputDefaults[columnName];
}
else {
rows[i].push('""');
rowValue = value[columnName]
? value[columnName]
: inputOutputDefaults[columnName];
}
if (format) {
if (isHtmlColumn(columnName)) {
rowValue = stripNewLines(converter.makeHtml(rowValue)).trim();
}
else {
rowValue = `\`${rowValue}\``;
}
}
rows[i].push(rowValue);
}

@@ -184,0 +287,0 @@ }

@@ -19,6 +19,14 @@ #!/usr/bin/env node

type: "string",
default: defaultOptions.actionFile,
default: defaultOptions.sourceFile,
demandOption: false,
alias: "a",
deprecated: 'use "source" instead',
},
source: {
description: "GitHub source file",
type: "string",
default: defaultOptions.sourceFile,
demandOption: false,
alias: "s",
},
"no-banner": {

@@ -53,3 +61,3 @@ description: "Print no banner",

const options = {
actionFile: args.action,
sourceFile: args.source ?? args.action,
tocLevel: args["toc-level"],

@@ -56,0 +64,0 @@ updateReadme,

{
"name": "action-docs",
"version": "2.1.0",
"version": "2.2.0",
"description": "Generate GitHub action docs based on action.yml",

@@ -20,4 +20,7 @@ "main": "lib/index.js",

"test-cli": "nyc jest --testTimeout=10000 --silent --testMatch=**/cli*test.ts && nyc report --reporter=lcov --reporter=html --report-dir=./coverage_nyc",
"test-action": "nyc jest --testTimeout=10000 --silent --testMatch=**/action-docs.test.ts --coverage=false",
"dev-action": "node lib/cli.js -a __tests__/fixtures/action.yml",
"test-action": "nyc jest --testTimeout=10000 --silent --testMatch=**/action-docs-action.test.ts --coverage=false",
"test-workflow": "nyc jest --testTimeout=10000 --silent --testMatch=**/action-docs-workflow.test.ts --coverage=false",
"dev-action": "yarn run build && node lib/cli.js -s __tests__/fixtures/action/action.yml",
"dev-workflow": "yarn run build && node lib/cli.js -s __tests__/fixtures/workflow/workflow.yml",
"help": "yarn run build && node lib/cli.js --help",
"all": "yarn run build && yarn run format && yarn run lint && yarn test"

@@ -40,3 +43,2 @@ },

"chalk": "^5.3.0",
"eslint-import-resolver-typescript": "^3.6.1",
"figlet": "^1.7.0",

@@ -60,2 +62,3 @@ "replace-in-file": "^7.1.0",

"eslint": "^8.56.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-github": "^4.10.0",

@@ -62,0 +65,0 @@ "eslint-plugin-jest": "^27.6.0",

@@ -9,3 +9,3 @@ <!-- BADGES/ -->

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).

@@ -17,9 +17,11 @@ ## TL;DR

```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
```

@@ -30,3 +32,3 @@

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

@@ -61,11 +63,14 @@

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]
```

@@ -78,11 +83,11 @@

```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" -->
```

@@ -94,4 +99,4 @@

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`

@@ -115,3 +120,3 @@ ### Examples

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

@@ -122,3 +127,3 @@

```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
```

@@ -132,3 +137,3 @@

await generateActionMarkdownDocs({
actionFile: 'action.yml'
sourceFile: 'action.yml'
tocLevel: 2

@@ -135,0 +140,0 @@ updateReadme: true

@@ -11,3 +11,3 @@ import { LineBreakType, getLineBreak } from "./linebreak.js";

tocLevel?: number;
actionFile?: string;
sourceFile?: string;
updateReadme?: boolean;

@@ -19,14 +19,6 @@ readmeFile?: string;

interface ActionMarkdown {
header: string;
description: string;
inputs: string;
outputs: string;
runs: string;
usage: string;
}
interface ActionYml {
interface YmlStructure {
name: string;
description: string;
on: Record<string, WorkflowTriggerEvent>;
inputs: ActionInputsOutputs;

@@ -42,9 +34,17 @@ outputs: ActionInputsOutputs;

interface WorkflowTriggerEvent {
types: string[];
branches: string[];
cron: string[];
inputs: ActionInputsOutputs;
outputs: ActionInputsOutputs;
}
interface DefaultOptions {
tocLevel: number;
actionFile: string;
sourceFile: string;
updateReadme: boolean;
readmeFile: string;
lineBreaks: LineBreakType;
includeNameHeader?: boolean;
includeNameHeader: boolean;
}

@@ -54,3 +54,3 @@

tocLevel: 2,
actionFile: "action.yml",
sourceFile: "action.yml",
updateReadme: false,

@@ -62,18 +62,46 @@ readmeFile: "README.md",

type ActionInputsOutputs = Record<string, ActionInput | ActionOutput>;
type ActionInputsOutputs = Record<string, InputOutput>;
interface ActionInput {
enum InputType {
number,
string,
boolean,
}
enum InputOutputType {
actionInput,
workflowInput,
actionOutput,
}
const inputOutputHeaders: Record<InputOutputType, string[]> = {
[InputOutputType.actionInput]: ["name", "description", "required", "default"],
[InputOutputType.workflowInput]: [
"name",
"description",
"type",
"required",
"default",
],
[InputOutputType.actionOutput]: ["name", "description"],
};
const inputOutputDefaults: Record<string, string> = {
description: "",
type: "",
required: "false",
default: '""',
};
interface InputOutput {
required?: boolean;
description?: string;
default?: string;
type?: InputType;
}
interface ActionOutput {
description: string;
}
function createMdTable(
data: ActionInputsOutputs,
options: DefaultOptions,
type: "input" | "output",
type: InputOutputType,
): string {

@@ -86,13 +114,3 @@ const tableData = getInputOutput(data, type);

const result = [headers, filler]
.concat(
tableData.rows.map((line) => {
return line.map((elem, i) => {
const pretty =
i === 0 || i === 2 || i === 3 ? `\`${line[i]}\`` : elem;
const html = i === 1 ? converter.makeHtml(pretty) : pretty;
const htmlNoNewlines = html.replace(/(\r\n|\n|\r)/gm, " ").trim();
return htmlNoNewlines;
});
}),
)
.concat(tableData.rows)
.filter((x) => x.length > 0)

@@ -108,23 +126,49 @@ .map((x) => `| ${x.join(" | ")} |${getLineBreak(options.lineBreaks)}`)

options: DefaultOptions,
isAction = true,
): string {
let codeBlockArray = ["```yaml"];
codeBlockArray.push("- uses: ***PROJECT***@***VERSION***");
codeBlockArray.push(" with:");
const inputs = getInputOutput(data, "input");
for (const input of inputs.rows) {
const inputBlock = [`${input[0]}:`];
inputBlock.push(
...input[1]
.split(/(\r\n|\n|\r)/gm)
.filter((l) => !["", "\r", "\n", "\r\n"].includes(l))
.map((l) => `# ${l}`),
);
let indent = "";
if (isAction) {
codeBlockArray.push("- uses: ***PROJECT***@***VERSION***");
indent += " ";
} else {
codeBlockArray.push("jobs:");
indent += " ";
codeBlockArray.push(`${indent}job1:`);
indent += " ";
codeBlockArray.push(`${indent}uses: ***PROJECT***@***VERSION***`);
}
codeBlockArray.push(`${indent}with:`);
indent += " ";
const inputs = getInputOutput(
data,
isAction ? InputOutputType.actionInput : InputOutputType.workflowInput,
false,
);
for (const row of inputs.rows) {
const inputName = row[0];
const inputDescCommented = row[1]
.split(/(\r\n|\n|\r)/gm)
.filter((l) => !["", "\r", "\n", "\r\n"].includes(l))
.map((l) => `# ${l}`);
const type = isAction ? undefined : row[2];
const isRequired = isAction ? row[2] : row[3];
const defaultVal = isAction ? row[3] : row[4];
const inputBlock = [`${inputName}:`];
inputBlock.push(...inputDescCommented);
inputBlock.push("#");
inputBlock.push(`# Required: ${input[2].replace(/`/g, "")}`);
if (input[3]) {
inputBlock.push(`# Default: ${input[3]}`);
if (type) {
inputBlock.push(`# Type: ${type}`);
}
inputBlock.push(`# Required: ${isRequired}`);
if (defaultVal) {
inputBlock.push(`# Default: ${defaultVal}`);
}
codeBlockArray.push(...inputBlock.map((l) => ` ${l}`));
codeBlockArray.push(...inputBlock.map((l) => `${indent}${l}`));
codeBlockArray.push("");

@@ -162,38 +206,37 @@ }

const docs = generateActionDocs(options);
if (options.updateReadme) {
await updateReadme(options, docs.header, "header", options.actionFile);
await updateReadme(
options,
docs.description,
"description",
options.actionFile,
);
await updateReadme(options, docs.inputs, "inputs", options.actionFile);
await updateReadme(options, docs.outputs, "outputs", options.actionFile);
await updateReadme(options, docs.runs, "runs", options.actionFile);
await updateReadme(options, docs.usage, "usage", options.actionFile);
const docs = generateDocs(options);
let outputString = "";
for (const key in docs) {
const value = docs[key];
if (options.updateReadme) {
await updateReadme(options, value, key, options.sourceFile);
}
outputString += value;
}
return `${docs.header + docs.description + docs.inputs + docs.outputs + docs.runs}`;
return outputString;
}
function generateActionDocs(options: DefaultOptions): ActionMarkdown {
const yml = parse(readFileSync(options.actionFile, "utf-8")) as ActionYml;
function generateDocs(options: DefaultOptions): Record<string, string> {
const yml = parse(readFileSync(options.sourceFile, "utf-8")) as YmlStructure;
const inputMdTable = createMdTable(yml.inputs, options, "input");
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++;
if (yml.runs === undefined) {
return generateWorkflowDocs(yml, options);
} else {
return generateActionDocs(yml, options);
}
}
function generateActionDocs(
yml: YmlStructure,
options: DefaultOptions,
): Record<string, string> {
return {
header,
header: generateHeader(yml, options),
description: createMarkdownSection(options, yml.description, "Description"),
inputs: createMarkdownSection(options, inputMdTable, "Inputs"),
outputs: createMarkdownSection(options, outputMdTable, "Outputs"),
inputs: generateInputs(yml.inputs, options, InputOutputType.actionInput),
outputs: generateOutputs(yml.outputs, options),
runs: createMarkdownSection(

@@ -205,6 +248,63 @@ options,

),
usage: createMarkdownSection(options, usageMdCodeBlock, "Usage"),
usage: generateUsage(yml.inputs, options),
};
}
function generateWorkflowDocs(
yml: YmlStructure,
options: DefaultOptions,
): Record<string, string> {
return {
header: generateHeader(yml, options),
inputs: generateInputs(
yml.on.workflow_call?.inputs,
options,
InputOutputType.workflowInput,
),
outputs: generateOutputs(yml.on.workflow_call?.outputs, options),
runs: "",
usage: generateUsage(yml.on.workflow_call?.inputs, options, false),
};
}
function generateHeader(yml: YmlStructure, options: DefaultOptions): string {
let header = "";
if (options.includeNameHeader) {
header = createMarkdownHeader(options, yml.name);
options.tocLevel++;
}
return header;
}
function generateInputs(
data: ActionInputsOutputs,
options: DefaultOptions,
type: InputOutputType,
): string {
const inputMdTable = createMdTable(data, options, type);
return createMarkdownSection(options, inputMdTable, "Inputs");
}
function generateOutputs(
data: ActionInputsOutputs,
options: DefaultOptions,
): string {
const outputMdTable = createMdTable(
data,
options,
InputOutputType.actionOutput,
);
return createMarkdownSection(options, outputMdTable, "Outputs");
}
function generateUsage(
data: ActionInputsOutputs,
options: DefaultOptions,
isAction = true,
): string {
const usageMdCodeBlock = createMdCodeBlock(data, options, isAction);
return createMarkdownSection(options, usageMdCodeBlock, "Usage");
}
function escapeRegExp(x: string): string {

@@ -218,14 +318,45 @@ return x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string

section: string,
actionFile: string,
sourceFile: string,
): Promise<void> {
if (section === "usage") {
const readmeFileText = String(readFileSync(options.readmeFile, "utf-8"));
const match = readmeFileText.match(
new RegExp(
`<!-- action-docs-usage action="${escapeRegExp(actionFile)}" project="(.*)" version="(.*)" -->.?`,
),
) as string[];
const lineBreak = getLineBreak(options.lineBreaks);
if (match) {
const commentExpression = `<!-- action-docs-usage action="${actionFile}" project="${match[1]}" version="${match[2]}" -->`;
const readmeFileText = String(readFileSync(options.readmeFile, "utf-8"));
const sourceOrActionMatches = readmeFileText.match(
new RegExp(`<!-- action-docs-${section} (source|action)`),
) as string[];
if (sourceOrActionMatches) {
const sourceOrAction = sourceOrActionMatches[1];
if (section === "usage") {
const match = readmeFileText.match(
new RegExp(
`<!-- action-docs-usage ${sourceOrAction}="${escapeRegExp(sourceFile)}" project="(.*)" version="(.*)" -->.?`,
),
) as string[];
if (match) {
const commentExpression = `<!-- action-docs-usage ${sourceOrAction}="${sourceFile}" project="${match[1]}" version="${match[2]}" -->`;
const regexp = new RegExp(
`${escapeRegExp(commentExpression)}(?:(?:\r\n|\r|\n.*)+${escapeRegExp(commentExpression)})?`,
);
const processedText = text
.trim()
.replace("***PROJECT***", match[1])
.replace("***VERSION***", match[2]);
await replaceInFile.replaceInFile({
files: options.readmeFile,
from: regexp,
to:
commentExpression +
lineBreak +
processedText +
lineBreak +
commentExpression,
});
}
} else {
const commentExpression = `<!-- action-docs-${section} ${sourceOrAction}="${sourceFile}" -->`;
const regexp = new RegExp(

@@ -238,23 +369,10 @@ `${escapeRegExp(commentExpression)}(?:(?:\r\n|\r|\n.*)+${escapeRegExp(commentExpression)})?`,

from: regexp,
to: `${commentExpression}${getLineBreak(options.lineBreaks)}${text
.trim()
.replace("***PROJECT***", match[1])
.replace("***VERSION***", match[2])}${getLineBreak(
options.lineBreaks,
)}${commentExpression}`,
to:
commentExpression +
lineBreak +
text.trim() +
lineBreak +
commentExpression,
});
}
} else {
const commentExpression = `<!-- action-docs-${section} action="${actionFile}" -->`;
const regexp = new RegExp(
`${escapeRegExp(commentExpression)}(?:(?:\r\n|\r|\n.*)+${escapeRegExp(commentExpression)})?`,
);
await replaceInFile.replaceInFile({
files: options.readmeFile,
from: regexp,
to: `${commentExpression}${getLineBreak(
options.lineBreaks,
)}${text.trim()}${getLineBreak(options.lineBreaks)}${commentExpression}`,
});
}

@@ -283,8 +401,18 @@ }

function isHtmlColumn(columnName: string): boolean {
return columnName === "description";
}
function stripNewLines(value: string): string {
return value.replace(/\r\n|\r|\n/g, " ");
}
function getInputOutput(
data: ActionInputsOutputs,
type: "input" | "output",
type: InputOutputType,
format = true,
): { headers: string[]; rows: string[][] } {
let headers: string[] = [];
const rows: string[][] = [];
if (data === undefined) {

@@ -294,22 +422,34 @@ return { headers, rows };

headers =
type === "input"
? ["name", "description", "required", "default"]
: ["name", "description"];
headers = inputOutputHeaders[type];
for (let i = 0; i < Object.keys(data).length; i++) {
const key = Object.keys(data)[i];
const value = data[key] as ActionInput;
const value = data[key] as Record<string, string>;
rows[i] = [];
rows[i].push(key);
rows[i].push(value.description ? value.description : "");
if (type === "input") {
rows[i].push(value.required ? String(value.required) : "false");
for (const columnName of headers) {
let rowValue = "";
if (value.default !== undefined && value.default !== "") {
rows[i].push(value.default.toString().replace(/\r\n|\r|\n/g, " "));
if (columnName === "name") {
rowValue = key;
} else if (columnName === "default") {
rowValue =
value[columnName] !== undefined && value[columnName] !== ""
? stripNewLines(String(value[columnName]))
: inputOutputDefaults[columnName];
} else {
rows[i].push('""');
rowValue = value[columnName]
? value[columnName]
: inputOutputDefaults[columnName];
}
if (format) {
if (isHtmlColumn(columnName)) {
rowValue = stripNewLines(converter.makeHtml(rowValue)).trim();
} else {
rowValue = `\`${rowValue}\``;
}
}
rows[i].push(rowValue);
}

@@ -316,0 +456,0 @@ }

@@ -21,6 +21,14 @@ #!/usr/bin/env node

type: "string",
default: defaultOptions.actionFile,
default: defaultOptions.sourceFile,
demandOption: false,
alias: "a",
deprecated: 'use "source" instead',
},
source: {
description: "GitHub source file",
type: "string",
default: defaultOptions.sourceFile,
demandOption: false,
alias: "s",
},
"no-banner": {

@@ -60,3 +68,3 @@ description: "Print no banner",

const options = {
actionFile: args.action,
sourceFile: args.source ?? args.action,
tocLevel: args["toc-level"],

@@ -63,0 +71,0 @@ updateReadme,

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc