Skip to content

Commit

Permalink
feat: implement scripts report:update and report:discussion
Browse files Browse the repository at this point in the history
  • Loading branch information
WhiteMinds committed Jan 25, 2024
1 parent 6cda32c commit 0c245c4
Show file tree
Hide file tree
Showing 14 changed files with 739 additions and 0 deletions.
58 changes: 58 additions & 0 deletions .github/workflows/update_report_snapshot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Update Report Snapshot

on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 1'

jobs:
default:
runs-on: ubuntu-latest
permissions:
# peter-evans/create-pull-request requires the following permissions:
pull-requests: write
contents: write

steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.ref }}

- name: report:update
id: report_update
env:
# The script requires public_repo, read:org, read:project permissions, and needs a personal access token (classic) to obtain these permissions.
GITHUB_TOKEN: ${{ secrets.REPORT_GITHUB_TOKEN }}
run: |
yarn install
{
echo 'DEVLOG<<EOF'
yarn workspace @magickbase-website/scripts report:update | sed '0,/^generateDevlogFromSnapshotsDiff():$/d'
echo EOF
} >> $GITHUB_OUTPUT
git add packages/scripts/snapshots
- name: Set GPG
uses: crazy-max/ghaction-import-gpg@v5
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true

- name: Open PR to repo
uses: peter-evans/create-pull-request@v5
with:
title: Update Report Snapshot
commit-message: 'feat: update report snapshot'
body: '
After this PR is merged, a corresponding devlog discussion will be automatically created, with the content preview as follows
---
${{ steps.report_update.outputs.DEVLOG }}
'
committer: ${{ vars.COMMITTER }}
branch: update-report-snapshot
3 changes: 3 additions & 0 deletions packages/scripts/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# For accessing api.github.com
# https://docs.github.com/en/rest/overview/authenticating-to-the-rest-api?apiVersion=2022-11-28
GITHUB_TOKEN=
7 changes: 7 additions & 0 deletions packages/scripts/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import("eslint").Linter.Config} */
const config = {
root: true,
extends: ['@magickbase-website/eslint-config/next.js'],
}

module.exports = config
5 changes: 5 additions & 0 deletions packages/scripts/.lintstagedrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const path = require('path')

module.exports = {
'*.{js,cjs,mjs,jsx,ts,tsx}': ['prettier --write', 'eslint --fix'],
}
24 changes: 24 additions & 0 deletions packages/scripts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@magickbase-website/scripts",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"report:update": "node --loader ts-node/esm src/update_report_snapshot.ts",
"report:discussion": "node --loader ts-node/esm src/create_report_discussion.ts"
},
"dependencies": {
"@octokit/plugin-paginate-graphql": "^4.0.0",
"@octokit/plugin-paginate-rest": "^6.1.2",
"@octokit/rest": "^19.0.11"
},
"devDependencies": {
"@magickbase-website/config": "workspace:^",
"@magickbase-website/eslint-config": "workspace:^",
"@types/eslint": "^8.56.0",
"@types/node": "^20.2.5",
"eslint": "^8.56.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.3"
}
}
4 changes: 4 additions & 0 deletions packages/scripts/src/create_report_discussion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import './prepare'
import { createDevlogDiscussion } from './utils/report'

await createDevlogDiscussion()
4 changes: 4 additions & 0 deletions packages/scripts/src/prepare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import pkg from '@next/env'

const { loadEnvConfig } = pkg
loadEnvConfig(process.cwd(), true)
7 changes: 7 additions & 0 deletions packages/scripts/src/update_report_snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import './prepare'
import { generateDevlogFromSnapshotsDiff, updateSnapshots } from './utils/report'

await updateSnapshots()
// These outputs are for use with GitHub Actions.
console.log('generateDevlogFromSnapshotsDiff():')
console.log(generateDevlogFromSnapshotsDiff())
5 changes: 5 additions & 0 deletions packages/scripts/src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function assert(assertion: unknown, msg = 'assertion failed'): asserts assertion {
if (!assertion) {
throw new Error(msg)
}
}
8 changes: 8 additions & 0 deletions packages/scripts/src/utils/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { existsSync, mkdirSync } from 'fs'
import { dirname } from 'path'

export function ensureFileFolderExists(filePath: string) {
const folder = dirname(filePath)
if (existsSync(folder)) return
mkdirSync(folder, { recursive: true })
}
255 changes: 255 additions & 0 deletions packages/scripts/src/utils/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import { Octokit } from '@octokit/rest'
import { paginateRest } from '@octokit/plugin-paginate-rest'
import { paginateGraphql } from '@octokit/plugin-paginate-graphql'
import { assert } from './error'

const TOKEN = process.env.GITHUB_TOKEN
assert(TOKEN != null && TOKEN !== '', 'GITHUB_TOKEN is required')

const EnhancedOctokit = Octokit.plugin(paginateRest, paginateGraphql)
const octokit = new EnhancedOctokit({ auth: TOKEN })

const GQL_ProjectV2FieldCommon_FIELDS = () => `
... on ProjectV2FieldCommon {
name
}
`
const GQL_PROJECTV2ITEMFIELDVALUE_FIELDS = () => `
... on ProjectV2ItemFieldTextValue {
text
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldNumberValue {
number
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldDateValue {
date
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldSingleSelectValue {
name
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldIterationValue {
title
startDate
duration
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldLabelValue {
labels (first: 100) {
nodes {
id
name
description
}
}
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldMilestoneValue {
milestone {
title
dueOn
}
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldPullRequestValue {
pullRequests(first: 100) {
nodes {
title
url
}
}
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldRepositoryValue {
repository {
name
url
}
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldReviewerValue {
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
... on ProjectV2ItemFieldUserValue {
field {${GQL_ProjectV2FieldCommon_FIELDS()}}
}
`

export async function getOrganizationProjects(org: string) {
const res = await octokit.graphql<{
organization: {
projectsV2: {
nodes: {
id: string
number: number
title: string
}[]
}
}
}>(
`
query($login: String!) {
organization(login: $login) {
projectsV2(first: 100, query: "is:open") {
totalCount
nodes {
id
number
title
}
}
}
}
`,
{
login: org,
},
)

return res.organization.projectsV2.nodes
}

export interface ProjectItem {
id: string
content: { title: string; number?: number; url?: string }
fieldValues: {
nodes: {
text?: string
number?: number
date?: unknown
name?: string
title?: string
startDate?: unknown
duration?: unknown
labels?: { nodes: { name: string }[] }
milestone?: { title: string; dueOn: unknown }
pullRequests?: { nodes: { title: string; url: string }[] }
repository?: { name: string; url: string }
field: { name: string }
}[]
}
}

export async function getProjectItems(id: string) {
const res = await octokit.graphql<{
node: {
items: {
nodes: ProjectItem[]
}
}
}>(
`
query($cursor: String, $id: ID!) {
node(id: $id) {
... on ProjectV2 {
items(first: 5, after: $cursor) {
nodes {
id
content {
... on DraftIssue {
title
}
... on Issue {
title
number
url
}
... on PullRequest {
title
number
url
}
}
fieldValues(first: 100) {
nodes {
${GQL_PROJECTV2ITEMFIELDVALUE_FIELDS()}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`,
{
id,
},
)

return res.node.items.nodes
}

export async function createDiscussion(
repoOwner: string,
repoName: string,
category: string,
title: string,
body: string,
) {
const repoRes = await octokit.graphql<{
repository: {
id: string
discussionCategories: {
nodes: {
id: string
name: string
}[]
}
}
}>(
`
query($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
id
discussionCategories(first: 100) {
nodes {
id
name
}
}
}
}
`,
{
owner: repoOwner,
name: repoName,
},
)

const categoryId = repoRes.repository.discussionCategories.nodes.find(n => n.name === category)?.id
assert(categoryId)

const createRes = octokit.graphql<{
createDiscussion: {
discussion: {
id: string
}
}
}>(
`
mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
createDiscussion(input: {repositoryId: $repoId, categoryId: $categoryId, body: $body, title: $title}) {
discussion {
id
}
}
}
`,
{
repoId: repoRes.repository.id,
categoryId,
title,
body,
},
)

return (await createRes).createDiscussion.discussion
}
Loading

0 comments on commit 0c245c4

Please sign in to comment.