-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #144 from JasonEtco/zod
Add Zod to validate front-matter
- Loading branch information
Showing
12 changed files
with
465 additions
and
354 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,4 +8,4 @@ coverage: | |
default: | ||
threshold: 3 | ||
|
||
comment: false | ||
comment: false |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,113 +1,155 @@ | ||
import * as core from '@actions/core' | ||
import { Toolkit } from 'actions-toolkit' | ||
import fm from 'front-matter' | ||
import nunjucks from 'nunjucks' | ||
// @ts-ignore | ||
import dateFilter from 'nunjucks-date-filter' | ||
import { FrontMatterAttributes, listToArray, setOutputs } from './helpers' | ||
|
||
function logError(tools: Toolkit, template: string, action: 'creating' | 'updating', err: any) { | ||
import * as core from "@actions/core"; | ||
import { Toolkit } from "actions-toolkit"; | ||
import fm from "front-matter"; | ||
import nunjucks from "nunjucks"; | ||
// @ts-expect-error | ||
import dateFilter from "nunjucks-date-filter"; | ||
import { ZodError } from "zod"; | ||
import { | ||
FrontMatterAttributes, | ||
frontmatterSchema, | ||
listToArray, | ||
setOutputs, | ||
} from "./helpers"; | ||
|
||
function logError( | ||
tools: Toolkit, | ||
template: string, | ||
action: "creating" | "updating" | "parsing", | ||
err: any | ||
) { | ||
// Log the error message | ||
const errorMessage = `An error occurred while ${action} the issue. This might be caused by a malformed issue title, or a typo in the labels or assignees. Check ${template}!` | ||
tools.log.error(errorMessage) | ||
tools.log.error(err) | ||
const errorMessage = `An error occurred while ${action} the issue. This might be caused by a malformed issue title, or a typo in the labels or assignees. Check ${template}!`; | ||
tools.log.error(errorMessage); | ||
tools.log.error(err); | ||
|
||
// The error might have more details | ||
if (err.errors) tools.log.error(err.errors) | ||
if (err.errors) tools.log.error(err.errors); | ||
|
||
// Exit with a failing status | ||
core.setFailed(errorMessage + '\n\n' + err.message) | ||
return tools.exit.failure() | ||
core.setFailed(errorMessage + "\n\n" + err.message); | ||
return tools.exit.failure(); | ||
} | ||
|
||
export async function createAnIssue (tools: Toolkit) { | ||
const template = tools.inputs.filename || '.github/ISSUE_TEMPLATE.md' | ||
const assignees = tools.inputs.assignees | ||
export async function createAnIssue(tools: Toolkit) { | ||
const template = tools.inputs.filename || ".github/ISSUE_TEMPLATE.md"; | ||
const assignees = tools.inputs.assignees; | ||
|
||
let updateExisting: Boolean | null = null | ||
let updateExisting: Boolean | null = null; | ||
if (tools.inputs.update_existing) { | ||
if (tools.inputs.update_existing === 'true') { | ||
updateExisting = true | ||
} else if (tools.inputs.update_existing === 'false') { | ||
updateExisting = false | ||
if (tools.inputs.update_existing === "true") { | ||
updateExisting = true; | ||
} else if (tools.inputs.update_existing === "false") { | ||
updateExisting = false; | ||
} else { | ||
tools.exit.failure(`Invalid value update_existing=${tools.inputs.update_existing}, must be one of true or false`) | ||
tools.exit.failure( | ||
`Invalid value update_existing=${tools.inputs.update_existing}, must be one of true or false` | ||
); | ||
} | ||
} | ||
|
||
const env = nunjucks.configure({ autoescape: false }) | ||
env.addFilter('date', dateFilter) | ||
const env = nunjucks.configure({ autoescape: false }); | ||
env.addFilter("date", dateFilter); | ||
|
||
const templateVariables = { | ||
...tools.context, | ||
repo: tools.context.repo, | ||
env: process.env, | ||
date: Date.now() | ||
} | ||
date: Date.now(), | ||
}; | ||
|
||
// Get the file | ||
tools.log.debug('Reading from file', template) | ||
const file = await tools.readFile(template) as string | ||
tools.log.debug("Reading from file", template); | ||
const file = (await tools.readFile(template)) as string; | ||
|
||
// Grab the front matter as JSON | ||
const { attributes, body } = fm<FrontMatterAttributes>(file) | ||
tools.log(`Front matter for ${template} is`, attributes) | ||
const { attributes: rawAttributes, body } = fm<FrontMatterAttributes>(file); | ||
|
||
let attributes: FrontMatterAttributes; | ||
try { | ||
attributes = await frontmatterSchema.parseAsync(rawAttributes); | ||
} catch (err) { | ||
if (err instanceof ZodError) { | ||
const formatted = err.format(); | ||
return logError(tools, template, "parsing", formatted); | ||
} | ||
throw err; | ||
} | ||
|
||
tools.log(`Front matter for ${template} is`, attributes); | ||
|
||
const templated = { | ||
body: env.renderString(body, templateVariables), | ||
title: env.renderString(attributes.title, templateVariables) | ||
} | ||
tools.log.debug('Templates compiled', templated) | ||
title: env.renderString(attributes.title, templateVariables), | ||
}; | ||
tools.log.debug("Templates compiled", templated); | ||
|
||
if (updateExisting !== null) { | ||
tools.log.info(`Fetching issues with title "${templated.title}"`) | ||
tools.log.info(`Fetching issues with title "${templated.title}"`); | ||
|
||
let query = `is:issue repo:${process.env.GITHUB_REPOSITORY} in:title "${templated.title.replace(/['"]/g, "\\$&")}"` | ||
let query = `is:issue repo:${ | ||
process.env.GITHUB_REPOSITORY | ||
} in:title "${templated.title.replace(/['"]/g, "\\$&")}"`; | ||
|
||
const searchExistingType = tools.inputs.search_existing || 'open' | ||
const allowedStates = ['open', 'closed'] | ||
const searchExistingType = tools.inputs.search_existing || "open"; | ||
const allowedStates = ["open", "closed"]; | ||
if (allowedStates.includes(searchExistingType)) { | ||
query += ` is:${searchExistingType}` | ||
query += ` is:${searchExistingType}`; | ||
} | ||
|
||
const existingIssues = await tools.github.search.issuesAndPullRequests({ q: query }) | ||
const existingIssue = existingIssues.data.items.find(issue => issue.title === templated.title) | ||
const existingIssues = await tools.github.search.issuesAndPullRequests({ | ||
q: query, | ||
}); | ||
const existingIssue = existingIssues.data.items.find( | ||
(issue) => issue.title === templated.title | ||
); | ||
if (existingIssue) { | ||
if (updateExisting === false) { | ||
tools.exit.success(`Existing issue ${existingIssue.title}#${existingIssue.number}: ${existingIssue.html_url} found but not updated`) | ||
tools.exit.success( | ||
`Existing issue ${existingIssue.title}#${existingIssue.number}: ${existingIssue.html_url} found but not updated` | ||
); | ||
} else { | ||
try { | ||
tools.log.info(`Updating existing issue ${existingIssue.title}#${existingIssue.number}: ${existingIssue.html_url}`) | ||
tools.log.info( | ||
`Updating existing issue ${existingIssue.title}#${existingIssue.number}: ${existingIssue.html_url}` | ||
); | ||
const issue = await tools.github.issues.update({ | ||
...tools.context.repo, | ||
issue_number: existingIssue.number, | ||
body: templated.body | ||
}) | ||
setOutputs(tools, issue.data) | ||
tools.exit.success(`Updated issue ${existingIssue.title}#${existingIssue.number}: ${existingIssue.html_url}`) | ||
body: templated.body, | ||
}); | ||
setOutputs(tools, issue.data); | ||
tools.exit.success( | ||
`Updated issue ${existingIssue.title}#${existingIssue.number}: ${existingIssue.html_url}` | ||
); | ||
} catch (err: any) { | ||
return logError(tools, template, 'updating', err) | ||
return logError(tools, template, "updating", err); | ||
} | ||
} | ||
} else { | ||
tools.log.info('No existing issue found to update') | ||
tools.log.info("No existing issue found to update"); | ||
} | ||
} | ||
|
||
// Create the new issue | ||
tools.log.info(`Creating new issue ${templated.title}`) | ||
tools.log.info(`Creating new issue ${templated.title}`); | ||
try { | ||
const issue = await tools.github.issues.create({ | ||
...tools.context.repo, | ||
...templated, | ||
assignees: assignees ? listToArray(assignees) : listToArray(attributes.assignees), | ||
assignees: assignees | ||
? listToArray(assignees) | ||
: listToArray(attributes.assignees), | ||
labels: listToArray(attributes.labels), | ||
milestone: Number(tools.inputs.milestone || attributes.milestone) || undefined | ||
}) | ||
|
||
setOutputs(tools, issue.data) | ||
tools.log.success(`Created issue ${issue.data.title}#${issue.data.number}: ${issue.data.html_url}`) | ||
milestone: | ||
Number(tools.inputs.milestone || attributes.milestone) || undefined, | ||
}); | ||
|
||
setOutputs(tools, issue.data); | ||
tools.log.success( | ||
`Created issue ${issue.data.title}#${issue.data.number}: ${issue.data.html_url}` | ||
); | ||
} catch (err: any) { | ||
return logError(tools, template, 'creating', err) | ||
return logError(tools, template, "creating", err); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,26 @@ | ||
import { Toolkit } from 'actions-toolkit' | ||
import { Toolkit } from "actions-toolkit"; | ||
import { z } from "zod"; | ||
|
||
export interface FrontMatterAttributes { | ||
title: string | ||
assignees?: string[] | string | ||
labels?: string[] | string | ||
milestone?: string | number | ||
} | ||
export const frontmatterSchema = z | ||
.object({ | ||
title: z.string(), | ||
assignees: z.union([z.array(z.string()), z.string()]).optional(), | ||
labels: z.union([z.array(z.string()), z.string()]).optional(), | ||
milestone: z.union([z.string(), z.number()]).optional(), | ||
}) | ||
.strict(); | ||
|
||
export type FrontMatterAttributes = z.infer<typeof frontmatterSchema>; | ||
|
||
export function setOutputs (tools: Toolkit, issue: { number: number, html_url: string }) { | ||
tools.outputs.number = String(issue.number) | ||
tools.outputs.url = issue.html_url | ||
export function setOutputs( | ||
tools: Toolkit, | ||
issue: { number: number; html_url: string } | ||
) { | ||
tools.outputs.number = String(issue.number); | ||
tools.outputs.url = issue.html_url; | ||
} | ||
|
||
export function listToArray (list?: string[] | string) { | ||
if (!list) return [] | ||
return Array.isArray(list) ? list : list.split(', ') | ||
} | ||
export function listToArray(list?: string[] | string) { | ||
if (!list) return []; | ||
return Array.isArray(list) ? list : list.split(", "); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import { Toolkit } from 'actions-toolkit' | ||
import { createAnIssue } from './action' | ||
import { Toolkit } from "actions-toolkit"; | ||
import { createAnIssue } from "./action"; | ||
|
||
Toolkit.run(createAnIssue, { | ||
secrets: ['GITHUB_TOKEN'] | ||
}) | ||
secrets: ["GITHUB_TOKEN"], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
name: "Not a title" | ||
labels: 123 | ||
not_a_thing: "testing" | ||
--- | ||
Hi! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
{ | ||
"repository": { | ||
"owner": { "login": "JasonEtco" }, | ||
"owner": { | ||
"login": "JasonEtco" | ||
}, | ||
"name": "waddup" | ||
} | ||
} | ||
} |
Oops, something went wrong.