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