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: azure deployment #217

Merged
merged 45 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5c1fae4
chore: vercel
gentlementlegen Dec 2, 2024
e41fbb5
Add or update the Azure App Service build and deployment workflow config
gentlementlegen Dec 2, 2024
61c8985
chore: added Azure
gentlementlegen Dec 2, 2024
4f3c8ea
chore: azure build flow
gentlementlegen Dec 2, 2024
a838d94
chore: added hono
gentlementlegen Dec 2, 2024
2bfccf4
chore: updated workflow
gentlementlegen Dec 2, 2024
c198930
chore: updated workflow
gentlementlegen Dec 2, 2024
e48f2c5
Add or update the Azure App Service build and deployment workflow config
gentlementlegen Dec 2, 2024
23b7350
chore: updated workflow
gentlementlegen Dec 2, 2024
471cca0
chore: tsup compilation
gentlementlegen Dec 2, 2024
0a1b606
chore: added post route
gentlementlegen Dec 2, 2024
3ca4e9f
chore: fixed hono env
gentlementlegen Dec 2, 2024
9f7901a
chore: added frozen lockfile
gentlementlegen Dec 2, 2024
0f7534f
chore: test diminish package
gentlementlegen Dec 2, 2024
a8e29de
feat: hono
whilefoo Dec 2, 2024
64681b8
fix: knip
whilefoo Dec 2, 2024
b053c86
chore: added Azure
gentlementlegen Dec 2, 2024
8161d7e
chore: added hono
gentlementlegen Dec 2, 2024
1334073
chore: moved entry point
gentlementlegen Dec 2, 2024
5a2dc5a
chore: updated deploy branch
gentlementlegen Dec 2, 2024
3f8adf3
Merge branch 'development' into feat/azure
gentlementlegen Dec 3, 2024
658b19f
chore: added welcome message
gentlementlegen Dec 3, 2024
d8531b5
chore: test generate settings
gentlementlegen Dec 3, 2024
161d7d1
chore: fixed bun deps
gentlementlegen Dec 3, 2024
26130a5
chore: disable gen
gentlementlegen Dec 3, 2024
e3c9776
chore: node 3
gentlementlegen Dec 3, 2024
70b83c3
chore: renamed secrets
gentlementlegen Dec 4, 2024
82b3136
Add or update the Azure App Service build and deployment workflow config
gentlementlegen Dec 4, 2024
51ebe87
chore: renamed secrets
gentlementlegen Dec 4, 2024
2cc2833
chore: renamed secrets
gentlementlegen Dec 4, 2024
1730972
chore: added local settings generation
gentlementlegen Dec 4, 2024
b9ee268
chore: added build step
gentlementlegen Dec 4, 2024
600d9f3
chore: added build step ubuntu
gentlementlegen Dec 4, 2024
705f520
chore: test no env
gentlementlegen Dec 4, 2024
09f2fa3
chore: cli install
gentlementlegen Dec 4, 2024
3cef087
chore: az functions
gentlementlegen Dec 4, 2024
9c9eb25
chore: removed vercel file
gentlementlegen Dec 4, 2024
711a807
chore: fixed cspell
gentlementlegen Dec 4, 2024
0791eb5
chore: fixed knip
gentlementlegen Dec 4, 2024
b73ed34
chore: updated example env
gentlementlegen Dec 4, 2024
72c1a92
chore: fixed formatting
gentlementlegen Dec 4, 2024
6ac73c8
chore: renamed example settings
gentlementlegen Dec 4, 2024
ab4c959
chore: updated deployment workflow
gentlementlegen Dec 5, 2024
fc13b67
chore: updated deployment workflow
gentlementlegen Dec 5, 2024
3da026b
chore: fixed spacing in package.json
gentlementlegen Dec 8, 2024
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
22 changes: 20 additions & 2 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,26 @@
"ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log"],
"useGitignore": true,
"language": "en",
"words": ["dataurl", "devpool", "fkey", "mswjs", "outdir", "servedir", "supabase", "typebox", "ubiquity-os",
"smee", "tomlify", "hono", "cfworker", "pavlovcik", "ghijklmnopqrstuvwxyz", "GHIJKLMNOPQRSTUVWXYZ", "ubiquityos"],
"words": [
"dataurl",
"devpool",
"fkey",
"mswjs",
"outdir",
"servedir",
"supabase",
"typebox",
"ubiquity-os",
"smee",
"tomlify",
"hono",
"cfworker",
"workerd",
"pavlovcik",
gentlementlegen marked this conversation as resolved.
Show resolved Hide resolved
"ghijklmnopqrstuvwxyz",
"GHIJKLMNOPQRSTUVWXYZ",
"ubiquityos"
],
"dictionaries": ["typescript", "node", "software-terms"],
"import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"],
"ignoreRegExpList": ["[0-9a-fA-F]{6}"]
Expand Down
10 changes: 10 additions & 0 deletions .funcignore
Copy link
Member

Choose a reason for hiding this comment

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

What is this file

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Why are we releasing packages for Azure?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's what Azure will pack to serve on the endpoint, files listed here won't be included in the build.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.js.map
*.ts
.git*
.vscode
__azurite_db*__.json
__blobstorage__
__queuestorage__
local.settings.json
test
tsconfig.json
2 changes: 1 addition & 1 deletion .github/knip.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { KnipConfig } from "knip";

const config: KnipConfig = {
entry: ["src/worker.ts", "deploy/setup-kv-namespace.ts"],
entry: ["src/kernel.ts", "src/adapters/cloudflare-worker.ts", "deploy/setup-kv-namespace.ts"],
project: ["src/**/*.ts"],
ignore: ["jest.config.ts"],
ignoreBinaries: ["i", "publish"],
Expand Down
74 changes: 74 additions & 0 deletions .github/workflows/development-azure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy Node.js project to Azure Function App - ubiquity-os

on:
push:
branches:
- feat/azure
workflow_dispatch:

env:
AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root
NODE_VERSION: '20.x' # set this to the node version to use (supports 8.x, 10.x, 12.x)

jobs:
build:
runs-on: windows-latest
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@v4

- name: Setup Node ${{ env.NODE_VERSION }} Environment
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}

- uses: oven-sh/setup-bun@v2

- name: 'Resolve Project Dependencies Using Bun'
shell: pwsh
run: |
pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
bun install --frozen-lockfile
bun run jest:test
bun run build
bun rimraf node_modules
bun install --frozen-lockfile --production
popd

- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: node-app
path: .

deploy:
runs-on: windows-latest
needs: build

permissions:
id-token: write #This is required for requesting the JWT

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: node-app

- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_FF1543FDA6074B97A548B4AFA510042F }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_58410EA2C4124ABF95F7CE33E01A187E }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_4D4EF1EB2DF646689862334A9DE27D20 }}

- name: 'Run Azure Functions Action'
uses: Azure/functions-action@v1
id: fa
with:
app-name: 'ubiquity-os'
slot-name: 'Production'
package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}

9 changes: 7 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{
"recommendations": ["arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
"recommendations": [
"ms-azuretools.vscode-azurefunctions",
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
13 changes: 11 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
{
"typescript.enablePromptUseWorkspaceTsdk": true,
"cSpell.words": ["smee"]
}
"cSpell.words": [
"smee"
],
"azureFunctions.deploySubpath": ".",
"azureFunctions.postDeployTask": "npm install (functions)",
"azureFunctions.projectLanguage": "TypeScript",
"azureFunctions.projectRuntime": "~4",
"debug.internalConsoleOptions": "neverOpen",
"azureFunctions.projectLanguageModel": 4,
"azureFunctions.preDeployTask": "npm prune (functions)"
}
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default tsEslint.config({
"@typescript-eslint": tsEslint.plugin,
"check-file": checkFile,
},
ignores: [".github/knip.ts", "**/.wrangler/**", "jest.config.ts", ".husky/**"],
ignores: [".github/knip.ts", "**/.wrangler/**", "jest.config.ts", ".husky/**", "dist/**"],
extends: [eslint.configs.recommended, ...tsEslint.configs.recommended, sonarjs.configs.recommended],
languageOptions: {
parser: tsEslint.parser,
Expand Down
20 changes: 20 additions & 0 deletions host.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"extensions": {
"http": {
"routePrefix": ""
}
}
}
7 changes: 7 additions & 0 deletions local.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "node"
}
}
19 changes: 16 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"version": "2.1.5",
"private": false,
"description": "The kernel for UbiquityOS.",
"module": "dist/index.mjs",
"main": "dist/index.js",
"main": "dist/*.js",
"typings": "dist/index.d.ts",
"files": [
"dist"
Expand All @@ -14,6 +13,7 @@
"engines": {
"node": ">=20.10.0"
},
"type": "module",
"scripts": {
"dev": "run-p worker proxy",
"predev": "tsx predev.ts",
Expand All @@ -32,7 +32,13 @@
"jest:test": "jest --coverage",
"plugin:hello-world": "tsx tests/__mocks__/hello-world-plugin.ts",
"setup-kv": "bun --env-file=.dev.vars scripts/setup-kv-namespace.ts",
"setup": "tsx ./scripts/setup.ts"
"setup": "tsx ./scripts/setup.ts",
"start:local": "run-p proxy build:watch start",
"start": "func start",
"prestart": "bun run build",
"build": "tsup",
"build:watch": "tsup --watch",
"prebuild": "rimraf dist"
},
"keywords": [
"typescript",
Expand All @@ -42,7 +48,9 @@
"open-source"
],
"dependencies": {
"@azure/functions": "^4.6.0",
"@cfworker/json-schema": "2.0.1",
"@marplex/hono-azurefunc-adapter": "^1.0.1",
"@octokit/auth-app": "7.1.0",
"@octokit/core": "6.1.2",
"@octokit/plugin-paginate-rest": "11.3.3",
Expand All @@ -56,6 +64,7 @@
"@sinclair/typebox": "0.34.3",
"@ubiquity-os/plugin-sdk": "^1.1.0",
"dotenv": "16.4.5",
"hono": "^4.6.12",
"openai": "^4.70.2",
"typebox-validators": "0.3.5",
"yaml": "2.4.5"
Expand All @@ -77,13 +86,15 @@
"@types/jest": "29.5.12",
"@types/node": "20.14.10",
"@types/node-rsa": "^1.1.4",
"azure-functions-core-tools": "^4.0.6610",
"cspell": "8.9.0",
"esbuild": "0.23.0",
"eslint": "9.7.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-check-file": "2.8.0",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-sonarjs": "1.0.3",
"glob": "^11.0.0",
"husky": "9.1.6",
"jest": "29.7.0",
"jest-junit": "16.0.0",
Expand All @@ -94,10 +105,12 @@
"open": "^10.1.0",
"ora": "^8.1.1",
"prettier": "3.3.3",
"rimraf": "^6.0.1",
"smee-client": "^2.0.4",
"toml": "3.0.0",
"tomlify-j0.4": "3.0.0",
"ts-node": "^10.9.2",
"tsup": "^8.3.5",
"tsx": "4.16.2",
"typescript": "5.6.3",
"typescript-eslint": "7.16.0",
Expand Down
3 changes: 3 additions & 0 deletions src/adapters/cloudflare-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { app } from "../kernel";
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
import { app } from "../kernel";
export { app } from "../kernel";

Can't you just do that


export default app;
10 changes: 10 additions & 0 deletions src/functions/http-trigger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { app } from "@azure/functions";
import { azureHonoHandler } from "@marplex/hono-azurefunc-adapter";
import { app as honoApp } from "../kernel";

app.http("http-trigger", {
methods: ["GET", "POST"],
authLevel: "anonymous",
route: "{*proxy}",
handler: azureHonoHandler(honoApp.fetch),
});
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { app } from "@azure/functions";

app.setup({
enableHttpStream: true,
});
93 changes: 93 additions & 0 deletions src/kernel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { emitterEventNames } from "@octokit/webhooks";
import { Value } from "@sinclair/typebox/value";
import { GitHubEventHandler } from "./github/github-event-handler";
import { bindHandlers } from "./github/handlers";
import { Env, envSchema } from "./github/types/env";
import { EmptyStore } from "./github/utils/kv-store";
import { WebhookEventName } from "@octokit/webhooks-types";
import OpenAI from "openai";
import { Context, Hono, HonoRequest } from "hono";
import { env as honoEnv, getRuntimeKey } from "hono/adapter";
import { StatusCode } from "hono/utils/http-status";

export const app = new Hono();

app.post("/", async (ctx: Context) => {
try {
const env = honoEnv(ctx);
const request = ctx.req;

validateEnv(env);
const eventName = getEventName(request);
const signatureSha256 = getSignature(request);
const id = getId(request);
const openAiClient = new OpenAI({
apiKey: env.OPENAI_API_KEY,
});
const eventHandler = new GitHubEventHandler({
environment: env.ENVIRONMENT,
webhookSecret: env.APP_WEBHOOK_SECRET,
appId: env.APP_ID,
privateKey: env.APP_PRIVATE_KEY,
pluginChainState: new EmptyStore(),
openAiClient,
});
bindHandlers(eventHandler);

// if running in Cloudflare Worker, handle the webhook in the background and return a response immediately
if (getRuntimeKey() === "workerd") {
ctx.executionCtx.waitUntil(eventHandler.webhooks.verifyAndReceive({ id, name: eventName, payload: await request.text(), signature: signatureSha256 }));
} else {
await eventHandler.webhooks.verifyAndReceive({ id, name: eventName, payload: await request.text(), signature: signatureSha256 });
}
return ctx.text("ok\n", 200);
} catch (error) {
return handleUncaughtError(ctx, error);
}
});

function handleUncaughtError(ctx: Context, error: unknown) {
console.error(error);
let status = 500;
let errorMessage = "An uncaught error occurred";
if (error instanceof AggregateError) {
const err = error.errors[0];
errorMessage = err.message ? `${err.name}: ${err.message}` : `Error: ${errorMessage}`;
status = typeof err.status !== "undefined" && typeof err.status === "number" ? err.status : 500;
} else {
errorMessage = error instanceof Error ? `${error.name}: ${error.message}` : `Error: ${error}`;
}
return ctx.json({ error: errorMessage }, status as StatusCode);
}

function validateEnv(env: Env): void {
if (!Value.Check(envSchema, env)) {
const errors = [...Value.Errors(envSchema, env)];
console.error("Invalid environment variables", errors);
throw new Error("Invalid environment variables");
}
}

function getEventName(request: HonoRequest): WebhookEventName {
const eventName = request.header("x-github-event");
if (!eventName || !emitterEventNames.includes(eventName as WebhookEventName)) {
throw new Error(`Unsupported or missing "x-github-event" header value: ${eventName}`);
}
return eventName as WebhookEventName;
}

function getSignature(request: HonoRequest): string {
const signatureSha256 = request.header("x-hub-signature-256");
if (!signatureSha256) {
throw new Error(`Missing "x-hub-signature-256" header`);
}
return signatureSha256;
}

function getId(request: HonoRequest): string {
const id = request.header("x-github-delivery");
if (!id) {
throw new Error(`Missing "x-github-delivery" header`);
}
return id;
}
2 changes: 1 addition & 1 deletion src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ dotenv.config({ path: ".dev.vars" });

const smee = new SmeeClient({
source: process.env.WEBHOOK_PROXY_URL || "https://smee.io/new",
target: "http://localhost:8787/events",
target: "http://localhost:7071",
logger: console,
});

Expand Down
Loading