Skip to content

Commit d3018f2

Browse files
deregtdjaredallard
andauthored
feat: README arguments table auto-generation (#15)
* feat: README arguments table auto-generation * PR feedback * fixes * feedback * feedback * removing --force * Update templates/.mise.toml.tpl Co-authored-by: Jared Allard <jaredallard@users.noreply.github.com> --------- Co-authored-by: Jared Allard <jaredallard@users.noreply.github.com>
1 parent ee39e23 commit d3018f2

File tree

6 files changed

+197
-5
lines changed

6 files changed

+197
-5
lines changed

manifest.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,7 @@ modules:
3939

4040
## <</Stencil::Block>>
4141
## <<Stencil::Block(extra)>>
42-
42+
postRunCommand:
43+
- name: Install/Update pnpm deps to lockfile
44+
command: "pnpm install"
4345
## <</Stencil::Block>>

templates/.github/workflows/build-release.yml.tpl

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,25 @@ jobs:
104104
## <<Stencil::Block(runTestEnvVars)>>
105105
{{ file.Block "runTestEnvVars" }}
106106
## <</Stencil::Block>>
107+
- name: Install JS Deps
108+
run: pnpm install
109+
- name: Update README.md if needed
110+
run: mise run gentable
111+
env:
112+
# Fill in GH_TOKEN env with a different token here if you need a custom token to read module dependencies
113+
## <<Stencil::Block(readmeUpdateGhToken)>>
114+
{{- if empty (file.Block "readmeUpdateGhToken") }}
115+
GH_TOKEN: {{ "${{ github.token }}" }}
116+
{{- else }}
117+
{{ file.Block "readmeUpdateGhToken" }}
118+
{{- end }}
119+
## <</Stencil::Block>>
120+
- name: Commit back any changes
121+
uses: stefanzweifel/git-auto-commit-action@e348103e9026cc0eee72ae06630dbe30c8bf7a79 #v5
122+
with:
123+
commit_message: Update README.md manifest options table
107124
## <<Stencil::Block(buildteststeps)>>
108-
{{ file.Block "arguments" }}
125+
{{ file.Block "buildteststeps" }}
109126
## <</Stencil::Block>>
110127
{{- end }}
111128
@@ -179,7 +196,7 @@ jobs:
179196
{{ file.Block "goreleaserEnvVars" }}
180197
## <</Stencil::Block>>
181198
{{- else }}
182-
- name: Install Semantic-Release
199+
- name: Install JS Deps
183200
run: pnpm install
184201
- name: Release
185202
env:

templates/.mise.toml.tpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ echo "Tests are running"
8484
## <</Stencil::Block>>
8585
{{- end }}
8686

87+
[tasks.gentable]
88+
description = 'Generate the README.md table of arguments'
89+
run = "node scripts/yamltotable.js"
90+
8791
## <<Stencil::Block(tasks)>>
8892
{{ file.Block "tasks" }}
8993
## <</Stencil::Block>>

templates/package-lock.json.tpl

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates/package.json.tpl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
{{- range $key, $value := (stencil.Arg "packageJsonDeps") }}
1818
"{{ $key }}": "{{ $value }}",
1919
{{- end }}
20-
"semantic-release": "24.2.5"
20+
"semantic-release": "24.2.6"
2121
},
2222
"dependencies": {
2323
"@semantic-release/exec": "7.1.0",
24-
"@semantic-release/git": "10.0.1"
24+
"@semantic-release/git": "10.0.1",
25+
"js-yaml": "^4.1.0"
2526
}
2627
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
const fs = require('fs');
2+
const yaml = require('js-yaml');
3+
const { execSync } = require('child_process');
4+
5+
const file = fs.readFileSync('manifest.yaml', 'utf8');
6+
const data = yaml.load(file);
7+
8+
if (!data.arguments) {
9+
console.log("No arguments found, skipping...");
10+
process.exit(0);
11+
}
12+
13+
const headLines = [
14+
"## Manifest Arguments",
15+
"",
16+
"In the `arguments` section of your project's `stencil.yaml` file, you can specify the following options:",
17+
"",
18+
];
19+
20+
function splitName(name) {
21+
const mp = name.split('/');
22+
if (mp.length !== 3) {
23+
throw new Error(`Invalid name: ${name}`);
24+
}
25+
if (mp[0] != "github.com") {
26+
throw new Error(`Invalid name: ${name}`);
27+
}
28+
// Check for characters outside of a-zA-Z0-9-_.
29+
const allowedChars = /^[a-zA-Z0-9-_.]+$/;
30+
for (let i = 1; i <= 2; i++) {
31+
if (!allowedChars.test(mp[i])) {
32+
throw new Error(`Invalid character in ${name}: ${mp[i]}`);
33+
}
34+
}
35+
return [mp[1], mp[2]];
36+
}
37+
38+
let modules = { [data.name]: data };
39+
if (data.modules) {
40+
let toFetch = data.modules.map(m => m.name);
41+
while (toFetch.length > 0) {
42+
const m = toFetch.shift();
43+
if (m in modules) {
44+
continue;
45+
}
46+
47+
console.log(`Fetching ${m} manifest...`);
48+
const mp = splitName(m);
49+
try {
50+
const result = execSync(`gh api repos/${mp[0]}/${mp[1]}/contents/manifest.yaml --header "Accept: application/vnd.github.v3.raw"`);
51+
const manifest = yaml.load(result.toString());
52+
modules[m] = manifest;
53+
if (manifest.modules) {
54+
toFetch.push(...manifest.modules.map(m => m.name));
55+
}
56+
} catch (e) {
57+
throw new Error(`Error fetching ${m}: ${e.message}`);
58+
}
59+
}
60+
}
61+
62+
function getArgInfo(m, k) {
63+
if (!modules[m]) {
64+
throw new Error(`Module ${m} not found`);
65+
}
66+
if (!modules[m].arguments) {
67+
throw new Error(`No arguments found for ${m}`);
68+
}
69+
const v = modules[m].arguments[k];
70+
if (!v) {
71+
throw new Error(`Argument ${k} not found for ${m}`);
72+
}
73+
if (v.from) {
74+
return getArgInfo(v.from, k);
75+
}
76+
return v;
77+
}
78+
79+
let out = `| Option | Default | Description |
80+
| ------ | ------- | ----------- |
81+
`;
82+
83+
for (const [k, v] of Object.entries(data.arguments)) {
84+
let fromHead = "";
85+
if (v.from) {
86+
const vp = splitName(v.from);
87+
fromHead = `(From [${vp[0]}/${vp[1]}](https://github.com/${vp[0]}/${vp[1]})) `;
88+
}
89+
90+
let ai = getArgInfo(data.name, k);
91+
let def = "";
92+
if (typeof ai.default !== 'undefined') {
93+
if (typeof ai.default === 'string') {
94+
def = `"${ai.default}"`;
95+
} else {
96+
def = ai.default.toString();
97+
}
98+
} else if (ai.required) {
99+
def = '[required]';
100+
} else if (!ai.required) {
101+
if (ai.schema && ai.schema.type === 'boolean') {
102+
def = 'false';
103+
} else if (ai.schema && ai.schema.type === 'string') {
104+
def = '""';
105+
} else {
106+
def = '[none]';
107+
}
108+
}
109+
let desc = (ai.description || "").trim();
110+
const hasDef = desc.indexOf("(default: ");
111+
if (hasDef !== -1) {
112+
desc = desc.substring(0, hasDef).trim();
113+
}
114+
desc = desc.replace(/\n/g, '<br>');
115+
out += `| ${k} | ${def} | ${fromHead}${desc} |\n`;
116+
}
117+
118+
let readme = "";
119+
try {
120+
readme = fs.readFileSync("README.md", "utf8");
121+
} catch (e) {
122+
console.log("No README.md found, creating one...");
123+
readme = "";
124+
}
125+
const lines = readme.split('\n');
126+
let firstLine = lines.indexOf("## Manifest Arguments");
127+
let lastLine = -1;
128+
if (firstLine !== -1) {
129+
let inTable = false;
130+
let otherSection = -1;
131+
for (let i = firstLine + 1; i < lines.length; i++) {
132+
if (otherSection === -1 && lines[i - 1] === "" && lines[i].startsWith("#")) {
133+
otherSection = i;
134+
}
135+
const isTable = lines[i].startsWith("|");
136+
if (inTable) {
137+
if (!isTable) {
138+
lastLine = i;
139+
break;
140+
}
141+
} else if (isTable) {
142+
inTable = true;
143+
}
144+
}
145+
if (lastLine === -1) {
146+
// Use last section if exists
147+
if (otherSection !== -1) {
148+
lastLine = Math.max(firstLine, otherSection - 2);
149+
} else {
150+
// Use end of file
151+
lastLine = lines.length - 1;
152+
}
153+
}
154+
}
155+
156+
// Reassemble
157+
const outLines = out.split("\n");
158+
159+
let newReadme = "";
160+
if (firstLine !== -1 && lastLine !== -1) {
161+
newReadme = [...lines.slice(0, firstLine), ...headLines, ...outLines, ...lines.slice(lastLine + 1)].join('\n');
162+
} else {
163+
newReadme = [...lines, "", ...headLines, ...outLines, ""].join('\n');
164+
}
165+
fs.writeFileSync("README.md", newReadme);
166+
167+
console.log("Updated README.md!");

0 commit comments

Comments
 (0)