1- name : Create README Gist
2-
3- on :
4- workflow_dispatch :
5- inputs :
6- repository :
7- description : " Repo name in the serpgames org, or serpgames/repo"
8- required : true
9- type : string
10- ref :
11- description : " Branch, tag, or SHA to read from. Leave blank to use the repo default branch."
12- required : false
13- default : " "
14- type : string
15- public :
16- description : " Create a public gist"
17- required : false
18- default : true
19- type : boolean
20- gist_description :
21- description : " Optional gist title/description. Defaults to the README H1."
22- required : false
23- default : " "
24- type : string
25- gist_id :
26- description : " Optional existing gist ID or URL to update instead of creating a new gist."
27- required : false
28- default : " "
29- type : string
30- workflow_call :
31- inputs :
32- repository :
33- description : " Repo name in the serpgames org, or serpgames/repo"
34- required : true
35- type : string
36- ref :
37- description : " Branch, tag, or SHA to read from. Leave blank to use the repo default branch."
38- required : false
39- default : " "
40- type : string
41- public :
42- description : " Create a public gist"
43- required : false
44- default : true
45- type : boolean
46- gist_description :
47- description : " Optional gist title/description. Defaults to the README H1."
48- required : false
49- default : " "
50- type : string
51- gist_id :
52- description : " Optional existing gist ID or URL to update instead of creating a new gist."
53- required : false
54- default : " "
55- type : string
56- secrets :
57- SERPGAMEDEVELOPER_GIST_TOKEN :
58- description : " PAT from the serpgamedeveloper account with gist write permission"
59- required : true
60- outputs :
61- gist_url :
62- description : " Created or updated gist URL"
63- value : ${{ jobs.create-readme-gist.outputs.gist_url }}
64- gist_id :
65- description : " Created or updated gist ID"
66- value : ${{ jobs.create-readme-gist.outputs.gist_id }}
67-
68- permissions :
69- contents : read
70-
71- jobs :
72- create-readme-gist :
73- name : Create README gist
74- runs-on : ubuntu-latest
75- outputs :
76- gist_url : ${{ steps.create.outputs.gist_url }}
77- gist_id : ${{ steps.create.outputs.gist_id }}
78- steps :
79- - name : Create gist from repository README
80- id : create
81- shell : bash
82- env :
83- INPUT_REPOSITORY : ${{ inputs.repository }}
84- INPUT_REF : ${{ inputs.ref }}
85- GIST_PUBLIC : ${{ inputs.public }}
86- GIST_DESCRIPTION : ${{ inputs.gist_description }}
87- INPUT_GIST_ID : ${{ inputs.gist_id }}
88- GITHUB_TOKEN : ${{ github.token }}
89- SERPGAMEDEVELOPER_GIST_TOKEN : ${{ secrets.SERPGAMEDEVELOPER_GIST_TOKEN }}
90- run : |
91- node <<'NODE'
92- const fs = require("fs");
93-
94- async function main() {
95- const apiVersion = "2022-11-28";
96- const readToken = process.env.GITHUB_TOKEN || "";
97- const gistToken = process.env.SERPGAMEDEVELOPER_GIST_TOKEN || "";
98-
99- if (!gistToken) {
100- throw new Error("Missing SERPGAMEDEVELOPER_GIST_TOKEN secret. Add a serpgamedeveloper PAT with gist write permission.");
101- }
102-
103- const rawRepository = (process.env.INPUT_REPOSITORY || "").trim();
104- if (!rawRepository) {
105- throw new Error("The repository input is required.");
106- }
107-
108- const parts = rawRepository.includes("/")
109- ? rawRepository.split("/")
110- : ["serpgames", rawRepository];
111-
112- if (parts.length !== 2 || parts[0] !== "serpgames") {
113- throw new Error("repository must be a repo name in serpgames, or serpgames/repo-name.");
114- }
115-
116- const [owner, repo] = parts;
117- if (!/^[A-Za-z0-9_.-]+$/.test(repo)) {
118- throw new Error(`Invalid repository name: ${repo}`);
119- }
120-
121- const githubHeaders = {
122- "Accept": "application/vnd.github+json",
123- "X-GitHub-Api-Version": apiVersion,
124- "User-Agent": "serpgames-readme-gist-action",
125- };
126- if (readToken) {
127- githubHeaders.Authorization = `Bearer ${readToken}`;
128- }
129-
130- async function requestJson(url, options = {}) {
131- const response = await fetch(url, {
132- ...options,
133- headers: {
134- ...githubHeaders,
135- ...(options.headers || {}),
136- },
137- });
138- const text = await response.text();
139- let body = null;
140- if (text) {
141- try {
142- body = JSON.parse(text);
143- } catch {
144- body = text;
145- }
146- }
147- if (!response.ok) {
148- const details = typeof body === "string" ? body : JSON.stringify(body);
149- throw new Error(`GitHub API request failed: ${response.status} ${response.statusText} ${url} ${details}`);
150- }
151- return body;
152- }
153-
154- function cleanReadmeForGist(markdown) {
155- return markdown
156- .replace(/\r\n/g, "\n")
157- .replace(/\\n/g, "\n")
158- .replace(/^\[!\[START GAME\]\(\.\/start-game\.(?:gif|svg)\)\]\(([^)]+)\)\s*$/m, "**[START GAME]($1)**")
159- .trimEnd() + "\n";
160- }
161-
162- function readTitle(markdown, fallback) {
163- const h1 = markdown.match(/^#\s+(.+?)\s*$/m);
164- return h1 ? h1[1].trim() : fallback;
165- }
166-
167- function normalizeGistId(input) {
168- const value = (input || "").trim();
169- if (!value) return "";
170- const match = value.match(/([0-9a-f]{20,})$/i);
171- return match ? match[1] : value;
172- }
173-
174- const repoInfo = await requestJson(`https://api.github.com/repos/${owner}/${repo}`);
175- const requestedRef = (process.env.INPUT_REF || "").trim();
176- const ref = requestedRef || repoInfo.default_branch;
177- const readme = await requestJson(
178- `https://api.github.com/repos/${owner}/${repo}/readme?ref=${encodeURIComponent(ref)}`
179- );
180-
181- if (readme.encoding !== "base64") {
182- throw new Error(`Unsupported README encoding: ${readme.encoding}`);
183- }
184-
185- const rawReadmeMarkdown = Buffer.from(readme.content.replace(/\n/g, ""), "base64").toString("utf8");
186- const readmeMarkdown = cleanReadmeForGist(rawReadmeMarkdown);
187- const title = (process.env.GIST_DESCRIPTION || "").trim()
188- || readTitle(readmeMarkdown, `Play ${repo.replace(/-game$/, "").replace(/-/g, " ")} Game Online - Free Unblocked`);
189- const gistPublic = String(process.env.GIST_PUBLIC || "true").toLowerCase() !== "false";
190- const existingGistId = normalizeGistId(process.env.INPUT_GIST_ID);
191-
192- let gist;
193- if (existingGistId) {
194- const existingGist = await requestJson(`https://api.github.com/gists/${existingGistId}`, {
195- headers: {
196- Authorization: `Bearer ${gistToken}`,
197- },
198- });
199- const files = {};
200- for (const filename of Object.keys(existingGist.files || {})) {
201- files[filename] = null;
202- }
203- const firstMarkdownFile = Object.keys(existingGist.files || {}).find((filename) => filename.toLowerCase().endsWith(".md"));
204- files[firstMarkdownFile || "README.md"] = {
205- filename: "README.md",
206- content: readmeMarkdown,
207- };
208- gist = await requestJson(`https://api.github.com/gists/${existingGistId}`, {
209- method: "PATCH",
210- headers: {
211- Authorization: `Bearer ${gistToken}`,
212- },
213- body: JSON.stringify({
214- description: title,
215- files,
216- }),
217- });
218- } else {
219- gist = await requestJson("https://api.github.com/gists", {
220- method: "POST",
221- headers: {
222- Authorization: `Bearer ${gistToken}`,
223- },
224- body: JSON.stringify({
225- description: title,
226- public: gistPublic,
227- files: {
228- "README.md": {
229- content: readmeMarkdown,
230- },
231- },
232- }),
233- });
234- }
235-
236- fs.appendFileSync(process.env.GITHUB_OUTPUT, `gist_url=${gist.html_url}\n`);
237- fs.appendFileSync(process.env.GITHUB_OUTPUT, `gist_id=${gist.id}\n`);
238-
239- if (process.env.GITHUB_STEP_SUMMARY) {
240- fs.appendFileSync(
241- process.env.GITHUB_STEP_SUMMARY,
242- [
243- existingGistId ? "## README gist updated" : "## README gist created",
244- "",
245- `- Title: ${title}`,
246- `- Repository: ${owner}/${repo}`,
247- `- Ref: ${ref}`,
248- `- README: ${readme.html_url}`,
249- `- Gist: ${gist.html_url}`,
250- `- Public: ${gist.public}`,
251- "",
252- ].join("\n")
253- );
254- }
255- }
256-
257- main().catch((error) => {
258- console.error(error);
259- process.exit(1);
260- });
261- NODE
0 commit comments