···11+/**
22+ * Unit tests for the action's main functionality, src/main.ts
33+ *
44+ * These should be run as if the action was called from a workflow.
55+ * Specifically, the inputs listed in `action.yml` should be set as environment
66+ * variables following the pattern `INPUT_<INPUT_NAME>`.
77+ */
88+99+// import * as core from '@actions/core'
1010+// import * as main from '../src/main'
1111+1212+// // Mock the action's main function
1313+// const runMock = jest.spyOn(main, 'run')
1414+1515+// // Other utilities
1616+// const timeRegex = /^\d{2}:\d{2}:\d{2}/
1717+1818+// // Mock the GitHub Actions core library
1919+// let debugMock: jest.SpiedFunction<typeof core.debug>
2020+// let errorMock: jest.SpiedFunction<typeof core.error>
2121+// let getInputMock: jest.SpiedFunction<typeof core.getInput>
2222+// let setFailedMock: jest.SpiedFunction<typeof core.setFailed>
2323+// let setOutputMock: jest.SpiedFunction<typeof core.setOutput>
2424+2525+// describe('action', () => {
2626+// beforeEach(() => {
2727+// jest.clearAllMocks()
2828+2929+// debugMock = jest.spyOn(core, 'debug').mockImplementation()
3030+// errorMock = jest.spyOn(core, 'error').mockImplementation()
3131+// getInputMock = jest.spyOn(core, 'getInput').mockImplementation()
3232+// setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation()
3333+// setOutputMock = jest.spyOn(core, 'setOutput').mockImplementation()
3434+// })
3535+3636+// it('sets the time output', async () => {
3737+// // Set the action's inputs as return values from core.getInput()
3838+// getInputMock.mockImplementation(name => {
3939+// switch (name) {
4040+// case 'milliseconds':
4141+// return '500'
4242+// default:
4343+// return ''
4444+// }
4545+// })
4646+4747+// await main.run()
4848+// expect(runMock).toHaveReturned()
4949+5050+// // Verify that all of the core library functions were called correctly
5151+// expect(debugMock).toHaveBeenNthCalledWith(1, 'Waiting 500 milliseconds ...')
5252+// expect(debugMock).toHaveBeenNthCalledWith(
5353+// 2,
5454+// expect.stringMatching(timeRegex)
5555+// )
5656+// expect(debugMock).toHaveBeenNthCalledWith(
5757+// 3,
5858+// expect.stringMatching(timeRegex)
5959+// )
6060+// expect(setOutputMock).toHaveBeenNthCalledWith(
6161+// 1,
6262+// 'time',
6363+// expect.stringMatching(timeRegex)
6464+// )
6565+// expect(errorMock).not.toHaveBeenCalled()
6666+// })
6767+6868+// it('sets a failed status', async () => {
6969+// // Set the action's inputs as return values from core.getInput()
7070+// getInputMock.mockImplementation(name => {
7171+// switch (name) {
7272+// case 'milliseconds':
7373+// return 'this is not a number'
7474+// default:
7575+// return ''
7676+// }
7777+// })
7878+7979+// await main.run()
8080+// expect(runMock).toHaveReturned()
8181+8282+// // Verify that all of the core library functions were called correctly
8383+// expect(setFailedMock).toHaveBeenNthCalledWith(
8484+// 1,
8585+// 'milliseconds not a number'
8686+// )
8787+// expect(errorMock).not.toHaveBeenCalled()
8888+// })
8989+// })
···11+#!/bin/bash
22+33+# About:
44+#
55+# This is a helper script to tag and push a new release. GitHub Actions use
66+# release tags to allow users to select a specific version of the action to use.
77+#
88+# See: https://github.com/actions/typescript-action#publishing-a-new-release
99+#
1010+# This script will do the following:
1111+#
1212+# 1. Get the latest release tag
1313+# 2. Prompt the user for a new release tag
1414+# 3. Tag the new release
1515+# 4. Push the new tag to the remote
1616+#
1717+# Usage:
1818+#
1919+# script/release
2020+2121+# Terminal colors
2222+OFF='\033[0m'
2323+RED='\033[0;31m'
2424+GREEN='\033[0;32m'
2525+BLUE='\033[0;34m'
2626+2727+# Get the latest release tag
2828+latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
2929+3030+if [[ -z "$latest_tag" ]]; then
3131+ # There are no existing release tags
3232+ echo -e "No tags found (yet) - Continue to create and push your first tag"
3333+ latest_tag="[unknown]"
3434+fi
3535+3636+# Display the latest release tag
3737+echo -e "The latest release tag is: ${BLUE}${latest_tag}${OFF}"
3838+3939+# Prompt the user for the new release tag
4040+read -r -p 'Enter a new release tag (vX.X.X format): ' new_tag
4141+4242+# Validate the new release tag
4343+tag_regex='v[0-9]+\.[0-9]+\.[0-9]+$'
4444+if echo "$new_tag" | grep -q -E "$tag_regex"; then
4545+ echo -e "Tag: ${BLUE}$new_tag${OFF} is valid"
4646+else
4747+ # Release tag is not `vX.X.X` format
4848+ echo -e "Tag: ${BLUE}$new_tag${OFF} is ${RED}not valid${OFF} (must be in vX.X.X format)"
4949+ exit 1
5050+fi
5151+5252+# Tag the new release
5353+git tag -a "$new_tag" -m "$new_tag Release"
5454+echo -e "${GREEN}Tagged: $new_tag${OFF}"
5555+5656+# Push the new tag to the remote
5757+git push --tags
5858+echo -e "${GREEN}Release tag pushed to remote${OFF}"
5959+echo -e "${GREEN}Done!${OFF}"
+7
packages/upload-openapi-spec/src/index.ts
···11+/**
22+ * The entrypoint for the action.
33+ */
44+import { run } from './main'
55+66+// eslint-disable-next-line @typescript-eslint/no-floating-promises
77+run()
+22
packages/upload-openapi-spec/src/main.ts
···11+import * as core from '@actions/core'
22+import { upload } from './upload'
33+44+/**
55+ * The main function for the action.
66+ * @returns {Promise<void>} Resolves when the action is complete.
77+ */
88+export async function run(): Promise<void> {
99+ try {
1010+ const pathToOpenApi: string = core.getInput('path-to-openapi', {
1111+ required: true
1212+ })
1313+1414+ core.debug(`Path to OpenAPI: ${pathToOpenApi}`)
1515+1616+ core.debug(`Upload started: ${new Date().toTimeString()}`)
1717+ await upload(pathToOpenApi)
1818+ core.debug(`Upload completed: ${new Date().toTimeString()}`)
1919+ } catch (error) {
2020+ if (error instanceof Error) core.setFailed(error.message)
2121+ }
2222+}
+34
packages/upload-openapi-spec/src/upload.ts
···11+import { createClient } from '@supabase/supabase-js'
22+import { readFileSync } from 'node:fs'
33+44+const supabaseUrl = process.env.SUPABASE_URL!
55+const supabaseKey = process.env.SUPABASE_KEY!
66+const supabase = createClient(supabaseUrl, supabaseKey)
77+88+/**
99+ * Wait for a number of milliseconds.
1010+ * @param milliseconds The number of milliseconds to wait.
1111+ * @returns {Promise<string>} Resolves with 'done!' after the wait is over.
1212+ */
1313+export async function upload(pathToOpenApi: string): Promise<void> {
1414+ return new Promise(async resolve => {
1515+ // TODO: throw if path is invalid
1616+ if (!pathToOpenApi) {
1717+ throw new Error('OpenAPI path is invalid')
1818+ }
1919+2020+ // TODO: add timestamp/user id to name/bucket
2121+ const fileBody = readFileSync(pathToOpenApi)
2222+ const bucketId = 'openapi-specs'
2323+ const parts = pathToOpenApi.split('/')
2424+ const name = `test/${parts[parts.length - 1]}`
2525+ const { data, error } = await supabase.storage
2626+ .from(bucketId)
2727+ .upload(name, fileBody, {
2828+ cacheControl: '3600',
2929+ upsert: false
3030+ })
3131+3232+ resolve()
3333+ })
3434+}