Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: vercast 1.0.0 #18

Merged
merged 5 commits into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions extensions/vercast/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"root": true,
"env": {
"es2020": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
]
}
2 changes: 2 additions & 0 deletions extensions/vercast/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.DS_Store
4 changes: 4 additions & 0 deletions extensions/vercast/.prettierrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trailingComma: 'es5'
tabWidth: 2
semi: false
singleQuote: true
373 changes: 373 additions & 0 deletions extensions/vercast/LICENSE

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions extensions/vercast/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# vercast

🚥 View your vercel deployments with raycast

![example img](example.png)
Binary file added extensions/vercast/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added extensions/vercast/assets/icon@dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added extensions/vercast/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions extensions/vercast/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "vercast",
"title": "Search Vercel Deployments",
"description": "Search Vercel Deployments",
"icon": "icon.png",
"author": "matt",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add this field to the manifest:
"license": "MIT"

"license": "MPL-2.0",
"commands": [
{
"name": "index",
"title": "Vercast",
"subtitle": "",
"description": "Search Vercel Deployments",
"mode": "view",
"preferences": [
{
"name": "token",
"type": "password",
"required": true,
"title": "Token",
"description": "Account Token",
"link": "https://vercel.com/account/tokens"
}
]
}
],
"dependencies": {
"@raycast/api": "^1.25.0",
"dayjs": "^1.10.7",
"node-fetch": "^2.6.1"
},
"devDependencies": {
"@types/node": "^16.4.3",
"@types/node-fetch": "^2.5.12",
"@types/react": "^17.0.15",
"@typescript-eslint/eslint-plugin": "^4.28.5",
"@typescript-eslint/parser": "^4.28.5",
"eslint": "^7.31.0",
"eslint-config-prettier": "^8.3.0",
"typescript": "^4.3.5"
},
"scripts": {
"build": "ray build -e dist",
"dev": "ray develop"
}
}
87 changes: 87 additions & 0 deletions extensions/vercast/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
render,
ActionPanel,
Color,
Icon,
List,
OpenInBrowserAction,
preferences,
showToast,
ToastStyle,
} from '@raycast/api'
import { useEffect, useState } from 'react'
import useInterval from './use-interval'
import {
Deployment,
DeploymentState,
fetchDeployments,
fetchUsername,
} from './vercel'

render(<Main />)

function Main(): JSX.Element {
const token = String(preferences.token.value)
if (token.length !== 24) {
showToast(ToastStyle.Failure, 'Invalid token detected')
throw new Error('Invalid token length detected')
}

const [username, setUsername] = useState('')
const [deployments, setDeployments] = useState<Deployment[]>()

useEffect(() => {
const call = async () => setUsername(await fetchUsername())
if (username === '') {
call()
}
})
useEffect(() => {
const call = async () => setDeployments(await fetchDeployments(username))
if (!deployments) {
call()
}
})

useInterval(async () => {
setDeployments(await fetchDeployments(username))
}, 1000)

return (
<List isLoading={!deployments}>
{deployments?.map((d) => {
let iconSource = Icon.Globe
let iconTintColor = Color.PrimaryText
switch (d.state) {
case DeploymentState.ready:
iconSource = Icon.Checkmark
iconTintColor = Color.Green
break
case DeploymentState.deploying:
iconSource = Icon.Hammer
iconTintColor = Color.Yellow
break
case DeploymentState.failed:
iconSource = Icon.XmarkCircle
iconTintColor = Color.Red
break
}
return (
<List.Item
key={d.id}
id={d.id}
title={d.project}
subtitle={d.domain}
accessoryTitle={d.time}
icon={{ tintColor: iconTintColor, source: iconSource }}
actions={
<ActionPanel>
<OpenInBrowserAction url={d.url} />
</ActionPanel>
}
/>
)
})}
</List>
)
}
39 changes: 39 additions & 0 deletions extensions/vercast/src/use-interval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// The code for this file is pulled from https://github.com/Hermanya/use-interval/blob/master/src/index.tsx
// This is done because of react dependency problems from the package.json of use-interval

import { useEffect, useRef } from 'react'

/* istanbul ignore next */
/** keep typescript happy */
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {}

export function useInterval(
callback: () => void,
delay: number | null | false,
immediate?: boolean
): void {
const savedCallback = useRef(noop)

// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback
})

// Execute callback if immediate is set.
useEffect(() => {
if (!immediate) return
if (delay === null || delay === false) return
savedCallback.current()
}, [immediate])

// Set up the interval.
useEffect(() => {
if (delay === null || delay === false) return undefined
const tick = () => savedCallback.current()
const id = setInterval(tick, delay)
return () => clearInterval(id)
}, [delay])
}

export default useInterval
90 changes: 90 additions & 0 deletions extensions/vercast/src/vercel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { preferences, showToast, ToastStyle } from '@raycast/api'
import fetch, { Headers } from 'node-fetch'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'

const headers = new Headers({
Authorization: 'Bearer ' + preferences.token.value,
})
const apiURL = 'https://api.vercel.com/'

export enum DeploymentState {
ready,
failed,
deploying,
}

export interface Deployment {
project: string
state: DeploymentState
time: string
id: string
url: string
domain: string
}

export async function fetchUsername(): Promise<string> {
try {
const response = await fetch(apiURL + 'www/user', {
method: 'get',
headers: headers,
})
const json = await response.json()
return json.user.username
} catch (err) {
console.error(err)
showToast(ToastStyle.Failure, 'Failed to fetch username')
throw new Error('Failed to fetch username')
}
return Promise.resolve('')
}

export async function fetchDeployments(
username: string
): Promise<Deployment[]> {
dayjs.extend(relativeTime)

try {
const response = await fetch(apiURL + 'v8/projects', {
method: 'get',
headers: headers,
})
const json = await response.json()
const deployments: Deployment[] = []
for (const project of json.projects) {
for (const deployment of project.latestDeployments) {
if (deployment.creator.username === username) {
let state: DeploymentState
switch (deployment.readyState.toUpperCase()) {
case 'READY':
state = DeploymentState.ready
break
case 'BUILDING':
case 'QUEUED':
state = DeploymentState.deploying
break
default:
state = DeploymentState.failed
break
}
deployments.push({
project: project.name,
state: state,
time: dayjs(deployment.createdAt).fromNow(),
id: deployment.id,
url: `https://vercel.com/${username}/${
project.name
}/${deployment.id.replace('dpl_', '')}`,
domain: deployment.alias[0],
})
break
}
}
}
return deployments
} catch (err) {
console.error(err)
showToast(ToastStyle.Failure, 'Failed to fetch deployments')
throw new Error('Failed to fetch deployments')
}
}
16 changes: 16 additions & 0 deletions extensions/vercast/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node 16",
"include": ["src/**/*"],
"compilerOptions": {
"lib": ["es2020"],
"module": "commonjs",
"target": "es2020",
"strict": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react-jsx"
}
}