diff --git a/.cspell.json b/.cspell.json index 04c71a4..b24526b 100644 --- a/.cspell.json +++ b/.cspell.json @@ -28,7 +28,8 @@ "typebox", "typeguards", "mswjs", - "ubiquity-os" + "ubiquity-os", + "hono" ], "dictionaries": ["typescript", "node", "software-terms"], "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], diff --git a/.dev.vars.example b/.dev.vars.example index e602c48..cda5c21 100644 --- a/.dev.vars.example +++ b/.dev.vars.example @@ -1,2 +1,3 @@ SUPABASE_URL= -SUPABASE_KEY= \ No newline at end of file +SUPABASE_KEY= +KERNEL_PUBLIC_KEY= diff --git a/.env.example b/.env.example index e602c48..cda5c21 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ SUPABASE_URL= -SUPABASE_KEY= \ No newline at end of file +SUPABASE_KEY= +KERNEL_PUBLIC_KEY= diff --git a/.github/workflows/worker-deploy.yml b/.github/workflows/worker-deploy.yml index 88c690e..3eced74 100644 --- a/.github/workflows/worker-deploy.yml +++ b/.github/workflows/worker-deploy.yml @@ -41,9 +41,11 @@ jobs: secrets: | SUPABASE_URL SUPABASE_KEY + KERNEL_PUBLIC_KEY env: SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + KERNEL_PUBLIC_KEY: ${{ secrets.KERNEL_PUBLIC_KEY }} - name: Write Deployment URL to Summary run: | diff --git a/jest.config.ts b/jest.config.ts index a6555b0..5bb35e7 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -9,4 +9,16 @@ module.exports = { coverageReporters: ["json", "lcov", "text", "clover", "json-summary"], reporters: ["default", "jest-junit", "jest-md-dashboard"], coverageDirectory: "coverage", + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + transform: { + "^.+\\.tsx?$": [ + "ts-jest", + { + useESM: true, + }, + ], + }, } as JestConfigWithTsJest; diff --git a/package.json b/package.json index 43fad12..086e141 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "engines": { "node": ">=20.10.0" }, + "type": "module", "scripts": { "worker": "wrangler dev --env dev --port 4000", "start": "tsx src/index.ts", @@ -19,7 +20,7 @@ "knip": "knip --config .github/knip.ts", "knip-ci": "knip --no-exit-code --reporter json --config .github/knip.ts", "prepare": "husky install", - "test": "jest --setupFiles dotenv/config --coverage", + "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --setupFiles dotenv/config --coverage", "supabase:generate:local": "supabase gen types typescript --local > src/adapters/supabase/types/database.ts", "supabase:generate:remote": "dotenv -- cross-env-shell supabase gen types typescript --project-id $SUPABASE_PROJECT_ID --schema public > src/adapters/supabase/types/database.ts" }, @@ -31,22 +32,19 @@ "open-source" ], "dependencies": { - "@actions/core": "^1.10.1", - "@actions/github": "^6.0.0", "@commander-js/extra-typings": "12.0.1", "@octokit/plugin-retry": "^7.1.2", "@octokit/plugin-throttling": "^9.3.2", "@octokit/rest": "^20.0.2", "@octokit/webhooks": "^13.1.0", - "@sinclair/typebox": "^0.32.15", + "@sinclair/typebox": "^0.33.17", "@supabase/supabase-js": "2.43.1", - "@ubiquity-os/ubiquity-os-kernel": "^2.4.0", + "@ubiquity-os/ubiquity-os-kernel": "^2.5.1", "@ubiquity-os/ubiquity-os-logger": "^1.3.2", "commander": "12.0.0", "decimal.js": "^10.4.3", "dotenv": "^16.4.5", - "hono": "^4.6.5", - "typebox-validators": "0.3.5" + "hono": "^4.6.7" }, "devDependencies": { "@commitlint/cli": "^18.6.1", @@ -78,7 +76,7 @@ "ts-node": "10.9.2", "tsx": "^4.7.1", "typescript": "5.6.2", - "wrangler": "3.79.0" + "wrangler": "^3.83.0" }, "lint-staged": { "*.ts": [ diff --git a/src/adapters/index.ts b/src/adapters/index.ts index ef7d6c9..941ac2c 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -1,9 +1,9 @@ import { SupabaseClient } from "@supabase/supabase-js"; -import { Context } from "../types/context"; +import { Context } from "@ubiquity-os/ubiquity-os-kernel"; import { Access } from "./supabase/helpers/tables/access"; -import { User } from "./supabase/helpers/tables/user"; import { Label } from "./supabase/helpers/tables/label"; import { Super } from "./supabase/helpers/tables/super"; +import { User } from "./supabase/helpers/tables/user"; export function createAdapters(supabaseClient: SupabaseClient, context: Context) { return { diff --git a/src/adapters/supabase/helpers/tables/access.ts b/src/adapters/supabase/helpers/tables/access.ts index 6269fea..650c7af 100644 --- a/src/adapters/supabase/helpers/tables/access.ts +++ b/src/adapters/supabase/helpers/tables/access.ts @@ -1,5 +1,5 @@ import { SupabaseClient } from "@supabase/supabase-js"; -import { Context } from "../../../../types/context"; +import { Context } from "@ubiquity-os/ubiquity-os-kernel"; import { Database } from "../../types/database"; import { Super } from "./super"; diff --git a/src/adapters/supabase/helpers/tables/label.ts b/src/adapters/supabase/helpers/tables/label.ts index a5f48ee..6239e94 100644 --- a/src/adapters/supabase/helpers/tables/label.ts +++ b/src/adapters/supabase/helpers/tables/label.ts @@ -1,8 +1,8 @@ import { SupabaseClient } from "@supabase/supabase-js"; +import { Context } from "@ubiquity-os/ubiquity-os-kernel"; import { Database } from "../../types/database"; import { Super } from "./super"; -import { Context } from "../../../../types/context"; type LabelRow = Database["public"]["Tables"]["labels"]["Row"]; diff --git a/src/adapters/supabase/helpers/tables/super.ts b/src/adapters/supabase/helpers/tables/super.ts index 662fb69..9bbaf81 100644 --- a/src/adapters/supabase/helpers/tables/super.ts +++ b/src/adapters/supabase/helpers/tables/super.ts @@ -1,5 +1,5 @@ import { SupabaseClient } from "@supabase/supabase-js"; -import { Context } from "../../../../types/context"; +import { Context } from "@ubiquity-os/ubiquity-os-kernel"; import { Database } from "../../types/database"; export class Super { diff --git a/src/adapters/supabase/helpers/tables/user.ts b/src/adapters/supabase/helpers/tables/user.ts index b41130f..a4f52e2 100644 --- a/src/adapters/supabase/helpers/tables/user.ts +++ b/src/adapters/supabase/helpers/tables/user.ts @@ -1,6 +1,6 @@ import { SupabaseClient } from "@supabase/supabase-js"; +import { Context } from "@ubiquity-os/ubiquity-os-kernel"; import { Super } from "./super"; -import { Context } from "../../../../types/context"; export class User extends Super { constructor(supabase: SupabaseClient, context: Context) { diff --git a/src/handlers/check-modified-base-rate.ts b/src/handlers/check-modified-base-rate.ts index fcbdeba..ab77de1 100644 --- a/src/handlers/check-modified-base-rate.ts +++ b/src/handlers/check-modified-base-rate.ts @@ -1,12 +1,12 @@ import { CONFIG_FULL_PATH, DEV_CONFIG_FULL_PATH } from "@ubiquity-os/ubiquity-os-kernel"; -import { Context } from "../types/context"; +import { ContextPlugin } from "../types/plugin-input"; import { isPushEvent } from "../types/typeguards"; import { getCommitChanges } from "./get-commit-changes"; export const ZERO_SHA = "0000000000000000000000000000000000000000"; const BASE_RATE_FILES = [DEV_CONFIG_FULL_PATH, CONFIG_FULL_PATH]; -export async function isConfigModified(context: Context): Promise { +export async function isConfigModified(context: ContextPlugin): Promise { if (!isPushEvent(context)) { context.logger.debug("Not a push event"); return false; diff --git a/src/handlers/comment.ts b/src/handlers/comment.ts index bd07617..8528a14 100644 --- a/src/handlers/comment.ts +++ b/src/handlers/comment.ts @@ -1,9 +1,9 @@ import { addCommentToIssue, isUserAdminOrBillingManager } from "../shared/issue"; -import { Context } from "../types/context"; +import { ContextPlugin } from "../types/plugin-input"; import { isCommentEvent } from "../types/typeguards"; import commandParser, { AllowedCommand, CommandArguments, isValidCommand } from "./command-parser"; -const commandHandlers: { [k in AllowedCommand]: (context: Context, commandArguments: CommandArguments) => Promise } = { +const commandHandlers: { [k in AllowedCommand]: (context: ContextPlugin, commandArguments: CommandArguments) => Promise } = { async allow(context, { username, labels }: CommandArguments) { const logger = context.logger; if (!isCommentEvent(context)) { @@ -25,7 +25,7 @@ const commandHandlers: { [k in AllowedCommand]: (context: Context, commandArgume }, }; -export async function handleComment(context: Context) { +export async function handleComment(context: ContextPlugin) { const logger = context.logger; if (!isCommentEvent(context)) { return logger.debug("Not an comment event"); diff --git a/src/handlers/get-base-rate-changes.ts b/src/handlers/get-base-rate-changes.ts index 80392fd..689524d 100644 --- a/src/handlers/get-base-rate-changes.ts +++ b/src/handlers/get-base-rate-changes.ts @@ -1,5 +1,4 @@ -import { Context } from "../types/context"; -import { Rates } from "../types/plugin-input"; +import { ContextPlugin, Rates } from "../types/plugin-input"; import { isPushEvent } from "../types/typeguards"; /** @@ -7,7 +6,7 @@ import { isPushEvent } from "../types/typeguards"; * * This will capture changes to either the plugin's config or the global basePriceMultiplier. */ -export async function getBaseRateChanges(context: Context): Promise { +export async function getBaseRateChanges(context: ContextPlugin): Promise { if (!isPushEvent(context)) { context.logger.debug("Not a push event"); return { @@ -32,7 +31,7 @@ export async function getBaseRateChanges(context: Context): Promise { } try { - commitData = await context.octokit.repos.getCommit({ + commitData = await context.octokit.rest.repos.getCommit({ owner, repo: repository.name, ref: commitSha, diff --git a/src/handlers/global-config-update.ts b/src/handlers/global-config-update.ts index 85fc0bf..4c631b4 100644 --- a/src/handlers/global-config-update.ts +++ b/src/handlers/global-config-update.ts @@ -1,13 +1,13 @@ +import { isUserAdminOrBillingManager, listOrgRepos, listRepoIssues } from "../shared/issue"; +import { Label } from "../types/github"; +import { ContextPlugin } from "../types/plugin-input"; +import { isPushEvent } from "../types/typeguards"; import { isConfigModified } from "./check-modified-base-rate"; import { getBaseRateChanges } from "./get-base-rate-changes"; -import { Context } from "../types/context"; -import { syncPriceLabelsToConfig } from "./sync-labels-to-config"; import { setPriceLabel } from "./pricing-label"; -import { isPushEvent } from "../types/typeguards"; -import { isUserAdminOrBillingManager, listOrgRepos, listRepoIssues } from "../shared/issue"; -import { Label } from "../types/github"; +import { syncPriceLabelsToConfig } from "./sync-labels-to-config"; -async function isAuthed(context: Context): Promise { +async function isAuthed(context: ContextPlugin): Promise { if (!isPushEvent(context)) { context.logger.debug("Not a push event"); return false; @@ -33,7 +33,7 @@ async function isAuthed(context: Context): Promise { return !!(isPusherAuthed && isSenderAuthed); } -export async function globalLabelUpdate(context: Context) { +export async function globalLabelUpdate(context: ContextPlugin) { if (!isPushEvent(context)) { context.logger.debug("Not a push event"); return; @@ -68,7 +68,7 @@ export async function globalLabelUpdate(context: Context) { payload: { repository: repo, }, - } as Context; + } as ContextPlugin; // this should create labels on the repos that are missing await syncPriceLabelsToConfig(ctx); @@ -80,7 +80,7 @@ export async function globalLabelUpdate(context: Context) { } } -async function updateAllIssuePriceLabels(context: Context) { +async function updateAllIssuePriceLabels(context: ContextPlugin) { const { logger, config } = context; const repos = await listOrgRepos(context); @@ -97,7 +97,7 @@ async function updateAllIssuePriceLabels(context: Context) { repository: repo, issue, }, - } as Context, + } as ContextPlugin, issue.labels as Label[], config ); diff --git a/src/handlers/handle-parent-issue.ts b/src/handlers/handle-parent-issue.ts index 7fb2ff3..8e6dee9 100644 --- a/src/handlers/handle-parent-issue.ts +++ b/src/handlers/handle-parent-issue.ts @@ -1,9 +1,9 @@ import { clearAllPriceLabelsOnIssue } from "../shared/label"; import { calculateLabelValue } from "../shared/pricing"; -import { Context } from "../types/context"; import { Label } from "../types/github"; +import { ContextPlugin } from "../types/plugin-input"; -export async function handleParentIssue(context: Context, labels: Label[]) { +export async function handleParentIssue(context: ContextPlugin, labels: Label[]) { const issuePrices = labels.filter((label) => label.name.toString().startsWith("Price:")); if (issuePrices.length) { await clearAllPriceLabelsOnIssue(context); diff --git a/src/handlers/label-change.ts b/src/handlers/label-change.ts index 1ffa6c6..b5d9d56 100644 --- a/src/handlers/label-change.ts +++ b/src/handlers/label-change.ts @@ -1,8 +1,8 @@ import { isUserAdminOrBillingManager } from "../shared/issue"; -import { Context } from "../types/context"; +import { ContextPlugin } from "../types/plugin-input"; import { isLabelEditedEvent } from "../types/typeguards"; -export async function watchLabelChange(context: Context) { +export async function watchLabelChange(context: ContextPlugin) { const logger = context.logger; if (!isLabelEditedEvent(context)) { logger.debug("Not a label event"); @@ -44,7 +44,7 @@ export async function watchLabelChange(context: Context) { return logger.debug("label name change saved to db"); } -async function hasLabelEditPermission(context: Context, label: string, caller: string) { +async function hasLabelEditPermission(context: ContextPlugin, label: string, caller: string) { const sufficientPrivileges = await isUserAdminOrBillingManager(context, caller); // get text before : diff --git a/src/handlers/pricing-label.ts b/src/handlers/pricing-label.ts index 1c6118c..82fe1e4 100644 --- a/src/handlers/pricing-label.ts +++ b/src/handlers/pricing-label.ts @@ -1,14 +1,12 @@ -import { Context } from "../types/context"; - import { addLabelToIssue, clearAllPriceLabelsOnIssue, createLabel, listLabelsForRepo, removeLabelFromIssue } from "../shared/label"; import { labelAccessPermissionsCheck } from "../shared/permissions"; import { Label, UserType } from "../types/github"; import { getPrice } from "../shared/pricing"; import { handleParentIssue, isParentIssue, sortLabelsByValue } from "./handle-parent-issue"; -import { AssistivePricingSettings } from "../types/plugin-input"; +import { AssistivePricingSettings, ContextPlugin } from "../types/plugin-input"; import { isIssueLabelEvent } from "../types/typeguards"; -export async function onLabelChangeSetPricing(context: Context): Promise { +export async function onLabelChangeSetPricing(context: ContextPlugin): Promise { if (!isIssueLabelEvent(context)) { context.logger.debug("Not an issue event"); return; @@ -50,7 +48,7 @@ export async function onLabelChangeSetPricing(context: Context): Promise { const smallestPriceLabelName = smallestPriceLabel?.name; if (smallestPriceLabelName) { for (const label of sortedPriceLabels) { - await context.octokit.issues.removeLabel({ + await context.octokit.rest.issues.removeLabel({ owner, repo: payload.repository.name, issue_number: payload.issue.number, @@ -65,7 +63,7 @@ export async function onLabelChangeSetPricing(context: Context): Promise { await setPriceLabel(context, labels, config); } -export async function setPriceLabel(context: Context, issueLabels: Label[], config: AssistivePricingSettings) { +export async function setPriceLabel(context: ContextPlugin, issueLabels: Label[], config: AssistivePricingSettings) { const logger = context.logger; const labelNames = issueLabels.map((i) => i.name); @@ -121,7 +119,7 @@ function getMinLabels(recognizedLabels: { time: Label[]; priority: Label[] }) { return { time: minTimeLabel, priority: minPriorityLabel }; } -async function handleTargetPriceLabel(context: Context, targetPriceLabel: string, labelNames: string[]) { +async function handleTargetPriceLabel(context: ContextPlugin, targetPriceLabel: string, labelNames: string[]) { const { repository } = context.payload; if (repository.name === "devpool-directory") { targetPriceLabel = targetPriceLabel.replace("Price: ", "Pricing: "); @@ -139,7 +137,7 @@ async function handleTargetPriceLabel(context: Context, targetPriceLabel: string } } -async function handleExistingPriceLabel(context: Context, targetPriceLabel: string) { +async function handleExistingPriceLabel(context: ContextPlugin, targetPriceLabel: string) { const logger = context.logger; let labeledEvents = await getAllLabeledEvents(context); if (!labeledEvents) return logger.error("No labeled events found"); @@ -154,25 +152,25 @@ async function handleExistingPriceLabel(context: Context, targetPriceLabel: stri } } -async function addPriceLabelToIssue(context: Context, targetPriceLabel: string) { +async function addPriceLabelToIssue(context: ContextPlugin, targetPriceLabel: string) { await clearAllPriceLabelsOnIssue(context); await addLabelToIssue(context, targetPriceLabel); } -async function getAllLabeledEvents(context: Context) { +async function getAllLabeledEvents(context: ContextPlugin) { const events = await getAllIssueEvents(context); if (!events) return null; return events.filter((event) => event.event === "labeled"); } -async function getAllIssueEvents(context: Context) { +async function getAllIssueEvents(context: ContextPlugin) { if (!("issue" in context.payload) || !context.payload.issue) { context.logger.debug("Not an issue event"); return; } try { - return await context.octokit.paginate(context.octokit.issues.listEvents, { + return await context.octokit.paginate(context.octokit.rest.issues.listEvents, { owner: context.payload.repository.owner.login, repo: context.payload.repository.name, issue_number: context.payload.issue.number, diff --git a/src/handlers/return-data-to-kernel.ts b/src/handlers/return-data-to-kernel.ts deleted file mode 100644 index e2bc6d7..0000000 --- a/src/handlers/return-data-to-kernel.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as github from "@actions/github"; -import { Octokit } from "@octokit/rest"; - -export async function returnDataToKernel(repoToken: string, stateId: string, output: object, eventType = "return-data-to-ubiquity-os-kernel") { - const octokit = new Octokit({ auth: repoToken }); - return octokit.repos.createDispatchEvent({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - event_type: eventType, - client_payload: { - state_id: stateId, - output: JSON.stringify(output), - }, - }); -} diff --git a/src/handlers/sync-labels-to-config.ts b/src/handlers/sync-labels-to-config.ts index 61d81a2..9320c69 100644 --- a/src/handlers/sync-labels-to-config.ts +++ b/src/handlers/sync-labels-to-config.ts @@ -1,13 +1,13 @@ import { Logs } from "@ubiquity-os/ubiquity-os-logger"; import { COLORS, createLabel, listLabelsForRepo } from "../shared/label"; import { calculateLabelValue, calculateTaskPrice } from "../shared/pricing"; -import { Context } from "../types/context"; +import { ContextPlugin } from "../types/plugin-input"; import { Label } from "../types/github"; // This just checks all the labels in the config have been set in gh issue // If there's something missing, they will be added -export async function syncPriceLabelsToConfig(context: Context): Promise { +export async function syncPriceLabelsToConfig(context: ContextPlugin): Promise { const { config, logger } = context; const priceLabels: string[] = []; @@ -63,7 +63,7 @@ export async function syncPriceLabelsToConfig(context: Context): Promise { } } -async function handleGlobalUpdate(context: Context, logger: Logs, incorrectPriceLabels: Label[]) { +async function handleGlobalUpdate(context: ContextPlugin, logger: Logs, incorrectPriceLabels: Label[]) { logger.info("Incorrect price labels found, removing them", { incorrectPriceLabels: incorrectPriceLabels.map((label) => label.name) }); const owner = context.payload.repository.owner?.login; if (!owner) { diff --git a/src/handlers/validator.ts b/src/handlers/validator.ts deleted file mode 100644 index f2f96c3..0000000 --- a/src/handlers/validator.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TransformDecodeCheckError, TransformDecodeError, Value, ValueError } from "@sinclair/typebox/value"; -import { Env, envConfigValidator, envSchema } from "../types/env"; -import { assistivePricingSchemaValidator, AssistivePricingSettings, pluginSettingsSchema } from "../types/plugin-input"; - -export function validateAndDecodeSchemas(env: Env, rawSettings: object) { - const errors: ValueError[] = []; - const settings = Value.Default(pluginSettingsSchema, rawSettings) as AssistivePricingSettings; - - if (!assistivePricingSchemaValidator.test(settings)) { - for (const error of assistivePricingSchemaValidator.errors(settings)) { - errors.push(error); - } - } - - if (!envConfigValidator.test(env)) { - for (const error of envConfigValidator.errors(env)) { - errors.push(error); - } - } - - if (errors.length) { - throw { errors }; - } - - try { - const decodedEnv = Value.Decode(envSchema, env); - const decodedSettings = Value.Decode(pluginSettingsSchema, settings); - return { decodedEnv, decodedSettings }; - } catch (e) { - if (e instanceof TransformDecodeCheckError || e instanceof TransformDecodeError) { - throw { errors: [e.error] }; - } - throw e; - } -} diff --git a/src/index.ts b/src/index.ts index 83dd394..80fd386 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,41 +1,21 @@ -import * as github from "@actions/github"; -import * as core from "@actions/core"; -import { Value } from "@sinclair/typebox/value"; -import { envSchema } from "./types/env"; -import { pluginSettingsSchema, PluginInputs } from "./types/plugin-input"; +import { createClient } from "@supabase/supabase-js"; +import { createActionsPlugin } from "@ubiquity-os/ubiquity-os-kernel"; +import { LogLevel } from "@ubiquity-os/ubiquity-os-logger"; +import { createAdapters } from "./adapters"; import { run } from "./run"; -import { returnDataToKernel } from "./handlers/return-data-to-kernel"; +import { SupportedEvents } from "./types/context"; +import { Env, envSchema } from "./types/env"; +import { AssistivePricingSettings, pluginSettingsSchema } from "./types/plugin-input"; -/** - * Run the plugin as a GitHub Action instance. - */ -async function actionRun() { - const payloadEnv = { - SUPABASE_KEY: process.env.SUPABASE_KEY, - SUPABASE_URL: process.env.SUPABASE_URL, - }; - - const env = Value.Decode(envSchema, payloadEnv); - - const webhookPayload = github.context.payload.inputs; - const settings = Value.Decode(pluginSettingsSchema, Value.Default(pluginSettingsSchema, JSON.parse(webhookPayload.settings))); - - const inputs: PluginInputs = { - stateId: webhookPayload.stateId, - eventName: webhookPayload.eventName, - eventPayload: JSON.parse(webhookPayload.eventPayload), - settings: settings, - authToken: webhookPayload.authToken, - ref: webhookPayload.ref, - }; - await run(inputs, env); - - return await returnDataToKernel(inputs.authToken, inputs.stateId, {}); -} - -actionRun() - .then((result) => core.setOutput("result", result)) - .catch((error) => { - console.error(error); - core.setFailed(error); - }); +createActionsPlugin( + (context) => { + return run({ ...context, adapters: createAdapters(createClient(context.env.SUPABASE_URL, context.env.SUPABASE_KEY), context) }); + }, + { + envSchema: envSchema, + postCommentOnError: true, + settingsSchema: pluginSettingsSchema, + logLevel: process.env.LOG_LEVEL as LogLevel, + kernelPublicKey: process.env.KERNEL_PUBLIC_KEY, + } +).catch(console.error); diff --git a/src/run.ts b/src/run.ts index 67f8b41..05f1d6a 100644 --- a/src/run.ts +++ b/src/run.ts @@ -1,33 +1,14 @@ -import { Octokit } from "@octokit/rest"; -import { createClient } from "@supabase/supabase-js"; -import { createAdapters } from "./adapters"; import { handleComment } from "./handlers/comment"; import { watchLabelChange } from "./handlers/label-change"; import { onLabelChangeSetPricing } from "./handlers/pricing-label"; import { syncPriceLabelsToConfig } from "./handlers/sync-labels-to-config"; -import { Context } from "./types/context"; -import { Env } from "./types/env"; -import { PluginInputs } from "./types/plugin-input"; +import { ContextPlugin } from "./types/plugin-input"; import { globalLabelUpdate } from "./handlers/global-config-update"; import { isIssueLabelEvent } from "./types/typeguards"; -import { Logs } from "@ubiquity-os/ubiquity-os-logger"; -export async function run(inputs: PluginInputs, env: Env) { - const octokit = new Octokit({ auth: inputs.authToken }); - const supabaseClient = createClient(env.SUPABASE_URL, env.SUPABASE_KEY); +export async function run(context: ContextPlugin) { + const { eventName, logger } = context; - const context: Context = { - eventName: inputs.eventName, - payload: inputs.eventPayload, - config: inputs.settings, - octokit, - logger: new Logs("info"), - adapters: {} as ReturnType, - env, - }; - context.adapters = createAdapters(supabaseClient, context); - - const eventName = inputs.eventName; switch (eventName) { case "issues.opened": case "repository.created": @@ -50,6 +31,7 @@ export async function run(inputs: PluginInputs, env: Env) { await globalLabelUpdate(context); break; default: - context.logger.error(`Event ${eventName} is not supported`); + logger.error(`Event ${eventName} is not supported`); } + return { message: "OK" }; } diff --git a/src/shared/issue.ts b/src/shared/issue.ts index 47df0da..7da8914 100644 --- a/src/shared/issue.ts +++ b/src/shared/issue.ts @@ -1,6 +1,6 @@ -import { Context } from "../types/context"; +import { ContextPlugin } from "../types/plugin-input"; -async function checkIfIsAdmin(context: Context, username: string) { +async function checkIfIsAdmin(context: ContextPlugin, username: string) { const owner = context.payload.repository.owner?.login; if (!owner) throw context.logger.error("No owner found in the repository!"); const response = await context.octokit.rest.repos.getCollaboratorPermissionLevel({ @@ -11,7 +11,7 @@ async function checkIfIsAdmin(context: Context, username: string) { return response.data.permission === "admin"; } -async function checkIfIsBillingManager(context: Context, username: string) { +async function checkIfIsBillingManager(context: ContextPlugin, username: string) { if (!context.payload.organization) throw context.logger.error(`No organization found in payload!`); try { @@ -30,7 +30,7 @@ async function checkIfIsBillingManager(context: Context, username: string) { return membership.role === "billing_manager"; } -export async function isUserAdminOrBillingManager(context: Context, username?: string): Promise<"admin" | "billing_manager" | false> { +export async function isUserAdminOrBillingManager(context: ContextPlugin, username?: string): Promise<"admin" | "billing_manager" | false> { if (!username) return false; const isAdmin = await checkIfIsAdmin(context, username); if (isAdmin) return "admin"; @@ -41,13 +41,13 @@ export async function isUserAdminOrBillingManager(context: Context, username?: s return false; } -export async function addCommentToIssue(context: Context, message: string, issueNumber: number, repoOwner?: string, repo?: string) { +export async function addCommentToIssue(context: ContextPlugin, message: string, issueNumber: number, repoOwner?: string, repo?: string) { const payload = context.payload; const owner = repoOwner || payload.repository.owner?.login; if (!owner) throw context.logger.error("No owner found in the repository!"); try { - await context.octokit.issues.createComment({ + await context.octokit.rest.issues.createComment({ owner, repo: repo ?? payload.repository.name, issue_number: issueNumber, @@ -58,7 +58,7 @@ export async function addCommentToIssue(context: Context, message: string, issue } } -export async function listOrgRepos(context: Context) { +export async function listOrgRepos(context: ContextPlugin) { const org = context.payload.organization?.login; if (!org) throw context.logger.error("No organization found in payload!"); @@ -72,7 +72,7 @@ export async function listOrgRepos(context: Context) { } } -export async function listRepoIssues(context: Context, owner: string, repo: string) { +export async function listRepoIssues(context: ContextPlugin, owner: string, repo: string) { try { const response = await context.octokit.rest.issues.listForRepo({ owner, diff --git a/src/shared/label.ts b/src/shared/label.ts index fd91d36..39646b6 100644 --- a/src/shared/label.ts +++ b/src/shared/label.ts @@ -1,5 +1,5 @@ -import { Context } from "../types/context"; import { Label } from "../types/github"; +import { ContextPlugin } from "../types/plugin-input"; // cspell:disable export const COLORS = { default: "ededed", price: "1f883d" }; @@ -7,7 +7,7 @@ export const COLORS = { default: "ededed", price: "1f883d" }; const NO_REPO_OWNER = "No owner found in the repository!"; -export async function listLabelsForRepo(context: Context): Promise { +export async function listLabelsForRepo(context: ContextPlugin): Promise { const { payload, octokit } = context; const owner = payload.repository.owner?.login; @@ -31,7 +31,7 @@ export async function listLabelsForRepo(context: Context): Promise { throw context.logger.error("Failed to fetch lists of labels", { status: 500 }); } -export async function createLabel(context: Context, name: string, labelType = "default" as keyof typeof COLORS): Promise { +export async function createLabel(context: ContextPlugin, name: string, labelType = "default" as keyof typeof COLORS): Promise { const payload = context.payload; const color = name.startsWith("Price: ") ? COLORS.price : COLORS[labelType]; @@ -52,7 +52,7 @@ export async function createLabel(context: Context, name: string, labelType = "d } } -export async function clearAllPriceLabelsOnIssue(context: Context) { +export async function clearAllPriceLabelsOnIssue(context: ContextPlugin) { const payload = context.payload; if (!("issue" in payload) || !payload.issue) { return; @@ -65,7 +65,7 @@ export async function clearAllPriceLabelsOnIssue(context: Context) { for (const label of issuePriceLabels) { try { - await context.octokit.issues.removeLabel({ + await context.octokit.rest.issues.removeLabel({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: payload.issue.number, @@ -76,14 +76,14 @@ export async function clearAllPriceLabelsOnIssue(context: Context) { } } } -export async function addLabelToIssue(context: Context, labelName: string) { +export async function addLabelToIssue(context: ContextPlugin, labelName: string) { const payload = context.payload; if (!("issue" in payload) || !payload.issue) { return; } try { - await context.octokit.issues.addLabels({ + await context.octokit.rest.issues.addLabels({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: payload.issue.number, @@ -94,14 +94,14 @@ export async function addLabelToIssue(context: Context, labelName: string) { } } -export async function removeLabelFromIssue(context: Context, labelName: string) { +export async function removeLabelFromIssue(context: ContextPlugin, labelName: string) { const payload = context.payload; if (!("issue" in payload) || !payload.issue) { return; } try { - await context.octokit.issues.removeLabel({ + await context.octokit.rest.issues.removeLabel({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: payload.issue.number, diff --git a/src/shared/permissions.ts b/src/shared/permissions.ts index 9baaee3..86c8bfa 100644 --- a/src/shared/permissions.ts +++ b/src/shared/permissions.ts @@ -1,10 +1,10 @@ -import { Context } from "../types/context"; import { UserType } from "../types/github"; +import { ContextPlugin } from "../types/plugin-input"; import { isIssueLabelEvent } from "../types/typeguards"; import { addCommentToIssue, isUserAdminOrBillingManager } from "./issue"; import { addLabelToIssue, removeLabelFromIssue } from "./label"; -export async function labelAccessPermissionsCheck(context: Context) { +export async function labelAccessPermissionsCheck(context: ContextPlugin) { if (!isIssueLabelEvent(context)) { context.logger.debug("Not an issue event"); return; @@ -57,10 +57,10 @@ export async function labelAccessPermissionsCheck(context: Context) { } async function handleInsufficientPrivileges( - context: Context, + context: ContextPlugin, labelType: string, sender: string, - repo: Context["payload"]["repository"], + repo: ContextPlugin["payload"]["repository"], action: string, labelName: string, eventName: string diff --git a/src/shared/pricing.ts b/src/shared/pricing.ts index a0a790d..2018a55 100644 --- a/src/shared/pricing.ts +++ b/src/shared/pricing.ts @@ -1,14 +1,14 @@ -import { Context } from "../types/context"; -import { Label } from "../types/github"; import { Decimal } from "decimal.js"; +import { Label } from "../types/github"; +import { ContextPlugin } from "../types/plugin-input"; -export function calculateTaskPrice(context: Context, timeValue: number, priorityValue: number, baseValue?: number): string { +export function calculateTaskPrice(context: ContextPlugin, timeValue: number, priorityValue: number, baseValue?: number): string { const base = baseValue ?? context.config.basePriceMultiplier; const priority = new Decimal(priorityValue).div(10); // floats cause bad math return new Decimal(base).mul(1000).mul(timeValue).mul(priority).toDecimalPlaces(2).toString(); } -export function getPrice(context: Context, timeLabel: Label, priorityLabel: Label) { +export function getPrice(context: ContextPlugin, timeLabel: Label, priorityLabel: Label) { const logger = context.logger; const { labels } = context.config; diff --git a/src/types/env.ts b/src/types/env.ts index fe18b8f..f0a09c7 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -1,12 +1,11 @@ import { StaticDecode, Type as T } from "@sinclair/typebox"; -import "dotenv/config"; -import { StandardValidator } from "typebox-validators"; +import { LOG_LEVEL } from "@ubiquity-os/ubiquity-os-logger"; export const envSchema = T.Object({ SUPABASE_URL: T.String(), SUPABASE_KEY: T.String(), + LOG_LEVEL: T.Optional(T.Enum(LOG_LEVEL)), + KERNEL_PUBLIC_KEY: T.Optional(T.String()), }); -export const envConfigValidator = new StandardValidator(envSchema); - export type Env = StaticDecode; diff --git a/src/types/plugin-input.ts b/src/types/plugin-input.ts index 7da4600..9ec551a 100644 --- a/src/types/plugin-input.ts +++ b/src/types/plugin-input.ts @@ -1,16 +1,8 @@ -import { EmitterWebhookEvent as WebhookEvent, EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks"; -import { StandardValidator } from "typebox-validators"; -import { SupportedEvents } from "./context"; import { StaticDecode, Type as T } from "@sinclair/typebox"; - -export interface PluginInputs { - stateId: string; - eventName: T; - eventPayload: WebhookEvent["payload"]; - settings: AssistivePricingSettings; - authToken: string; - ref: string; -} +import { Context } from "@ubiquity-os/ubiquity-os-kernel"; +import { createAdapters } from "../adapters"; +import { SupportedEvents } from "./context"; +import { Env } from "./env"; export const pluginSettingsSchema = T.Object( { @@ -38,10 +30,10 @@ export const pluginSettingsSchema = T.Object( { default: {} } ); -export const assistivePricingSchemaValidator = new StandardValidator(pluginSettingsSchema); - export type AssistivePricingSettings = StaticDecode; export type Rates = { previousBaseRate: number | null; newBaseRate: number | null; }; + +export type ContextPlugin = Context & { adapters: ReturnType }; diff --git a/src/types/typeguards.ts b/src/types/typeguards.ts index 6e50cd3..2e61434 100644 --- a/src/types/typeguards.ts +++ b/src/types/typeguards.ts @@ -1,12 +1,13 @@ import { Context } from "./context"; +import { ContextPlugin } from "./plugin-input"; export function isCommentEvent( - context: Context -): context is Context & { payload: { issue: Context<"issue_comment">["payload"]["issue"]; comment: Context<"issue_comment">["payload"]["comment"] } } { + context: ContextPlugin +): context is ContextPlugin & { payload: { issue: Context<"issue_comment">["payload"]["issue"]; comment: Context<"issue_comment">["payload"]["comment"] } } { return context.eventName.startsWith("issue_comment."); } -export function isIssueLabelEvent(context: Context): context is Context & { +export function isIssueLabelEvent(context: ContextPlugin): context is ContextPlugin & { payload: { issue: Context<"issues.labeled" | "issues.unlabeled">["payload"]["issue"]; label: Context<"issues.labeled" | "issues.unlabeled">["payload"]["label"]; @@ -15,11 +16,11 @@ export function isIssueLabelEvent(context: Context): context is Context & { return context.eventName === "issues.labeled" || context.eventName === "issues.unlabeled"; } -export function isPushEvent(context: Context): context is Context & { payload: Context<"push">["payload"] } { +export function isPushEvent(context: ContextPlugin): context is ContextPlugin & { payload: Context<"push">["payload"] } { return context.eventName === "push"; } -export function isLabelEditedEvent(context: Context): context is Context & { +export function isLabelEditedEvent(context: ContextPlugin): context is ContextPlugin & { payload: { label: Context<"label.edited">["payload"]["label"]; changes: Context<"label.edited">["payload"]["changes"]; diff --git a/src/worker.ts b/src/worker.ts index 93a5a3d..5720630 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,51 +1,31 @@ -import manifest from "../manifest.json"; -import { validateAndDecodeSchemas } from "./handlers/validator"; +import { createClient } from "@supabase/supabase-js"; +import { createPlugin } from "@ubiquity-os/ubiquity-os-kernel"; +import type { ExecutionContext } from "hono"; +import { createAdapters } from "./adapters"; import { run } from "./run"; -import { Env } from "./types/env"; +import { SupportedEvents } from "./types/context"; +import { Env, envSchema } from "./types/env"; +import { AssistivePricingSettings, pluginSettingsSchema } from "./types/plugin-input"; +import manifest from "../manifest.json"; export default { - async fetch(request: Request, env: Env): Promise { - try { - const url = new URL(request.url); - if (url.pathname === "/manifest.json" && request.method === "GET") { - return new Response(JSON.stringify(manifest), { - headers: { "content-type": "application/json" }, - }); - } else if (url.pathname === "/manifest.json" && request.method === "POST") { - const webhookPayload = await request.json(); - validateAndDecodeSchemas(env, webhookPayload.settings); - return new Response(JSON.stringify({ message: "Schema is valid" }), { status: 200, headers: { "content-type": "application/json" } }); - } - - if (request.method !== "POST") { - return new Response(JSON.stringify({ error: `Only POST requests are supported.` }), { - status: 405, - headers: { "content-type": "application/json", Allow: "POST" }, + async fetch(request: Request, env: Env, executionCtx?: ExecutionContext) { + return createPlugin( + (context) => { + return run({ + ...context, + adapters: createAdapters(createClient(context.env.SUPABASE_URL, context.env.SUPABASE_KEY), context), }); + }, + //@ts-expect-error types are ok + manifest, + { + envSchema: envSchema, + postCommentOnError: true, + settingsSchema: pluginSettingsSchema, + logLevel: env.LOG_LEVEL, + kernelPublicKey: env.KERNEL_PUBLIC_KEY, } - const contentType = request.headers.get("content-type"); - if (contentType !== "application/json") { - return new Response(JSON.stringify({ error: `Bad request: ${contentType} is not a valid content type` }), { - status: 400, - headers: { "content-type": "application/json" }, - }); - } - - const webhookPayload = await request.json(); - const { decodedSettings, decodedEnv } = validateAndDecodeSchemas(env, webhookPayload.settings); - - webhookPayload.env = decodedEnv; - webhookPayload.settings = decodedSettings; - await run(webhookPayload, decodedEnv); - return new Response(JSON.stringify("OK"), { status: 200, headers: { "content-type": "application/json" } }); - } catch (error) { - return handleUncaughtError(error); - } + ).fetch(request, env, executionCtx); }, }; - -function handleUncaughtError(errors: unknown) { - console.error(errors); - const status = 500; - return new Response(JSON.stringify(errors), { status: status, headers: { "content-type": "application/json" } }); -} diff --git a/tests/__mocks__/handlers.ts b/tests/__mocks__/handlers.ts index 3ac2e6e..2905cfe 100644 --- a/tests/__mocks__/handlers.ts +++ b/tests/__mocks__/handlers.ts @@ -194,6 +194,7 @@ export const handlers = [ }); return HttpResponse.json(labels); }), + http.post("http://localhost:65432/rest/v1/access", () => HttpResponse.json()), ]; async function getLabel(body: ReadableStream | null) { diff --git a/tests/__mocks__/requests/issue-comment-post.json b/tests/__mocks__/requests/issue-comment-post.json index 6ff490c..fdbb2ec 100644 --- a/tests/__mocks__/requests/issue-comment-post.json +++ b/tests/__mocks__/requests/issue-comment-post.json @@ -1,5 +1,5 @@ { - "stateId": "", + "stateId": "1234", "eventName": "issue_comment.created", "eventPayload": { "action": "created", diff --git a/tests/global-update.test.ts b/tests/global-update.test.ts index fa7d222..ee4cd42 100644 --- a/tests/global-update.test.ts +++ b/tests/global-update.test.ts @@ -1,5 +1,6 @@ import { drop } from "@mswjs/data"; import { Context } from "../src/types/context"; +import { ContextPlugin } from "../src/types/plugin-input"; import { db } from "./__mocks__/db"; import { server } from "./__mocks__/node"; import { it, describe, beforeAll, beforeEach, afterAll, expect, afterEach, jest } from "@jest/globals"; @@ -553,7 +554,7 @@ function createContext( globalConfigUpdate?: { excludeRepos: string[]; } -): Context { +): ContextPlugin { return { adapters: {} as never, payload: { @@ -614,6 +615,7 @@ function createContext( }, basePriceMultiplier: 2, }, + // @ts-expect-error ESM makes types incompatible. octokit: octokit, eventName: "push", env: { diff --git a/tests/main.test.ts b/tests/main.test.ts index e18ab8e..cc3d3fe 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -1,19 +1,19 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "@jest/globals"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals"; import { drop } from "@mswjs/data"; import commandParser, { CommandArguments } from "../src/handlers/command-parser"; import { Env } from "../src/types/env"; +import { ContextPlugin } from "../src/types/plugin-input"; import workerFetch from "../src/worker"; import { db } from "./__mocks__/db"; import { server } from "./__mocks__/node"; import issueCommented from "./__mocks__/requests/issue-comment-post.json"; import usersGet from "./__mocks__/users-get.json"; -import * as crypto from "crypto"; +import * as crypto from "node:crypto"; import { AssistivePricingSettings, pluginSettingsSchema } from "../src/types/plugin-input"; import { Value } from "@sinclair/typebox/value"; import { calculateLabelValue, calculateTaskPrice } from "../src/shared/pricing"; -import { Context } from "../src/types/context"; -const { privateKey } = crypto.generateKeyPairSync("rsa", { +const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 2048, publicKeyEncoding: { type: "spki", @@ -25,7 +25,7 @@ const { privateKey } = crypto.generateKeyPairSync("rsa", { }, }); -const url = "http://localhost:4000"; +const url = "/"; beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); @@ -131,16 +131,13 @@ describe("User tests", () => { }, ]; for (const testCase of testCases) { - const price = calculateTaskPrice(context as unknown as Context, testCase.timeValue, testCase.priorityValue); + const price = calculateTaskPrice(context as unknown as ContextPlugin, testCase.timeValue, testCase.priorityValue); expect(price).toEqual(testCase.expectedPrice); } }); it("Should handle the comment", async () => { - const data = { - ...issueCommented, - authToken: process.env.GITHUB_TOKEN, - }; + const data = issueCommented; const sign = crypto.createSign("SHA256"); sign.update(JSON.stringify(data)); sign.end(); @@ -159,8 +156,9 @@ describe("User tests", () => { url, } as unknown as Request, { - SUPABASE_URL: "url", + SUPABASE_URL: "http://localhost:65432", SUPABASE_KEY: "key", + KERNEL_PUBLIC_KEY: publicKey, } ); expect(result.ok).toEqual(true); @@ -175,32 +173,38 @@ describe("User tests", () => { { SUPABASE_URL: "url", SUPABASE_KEY: "key", + KERNEL_PUBLIC_KEY: "key", } ); expect(result.ok).toEqual(false); - expect(result.status).toEqual(405); + expect(result.status).toEqual(404); }); it("Should reject an invalid environment", async () => { - const errorSpy = jest.spyOn(console, "error").mockImplementation(() => {}); + const data = issueCommented; + const sign = crypto.createSign("SHA256"); + sign.update(JSON.stringify(data)); + sign.end(); + const signature = sign.sign(privateKey, "base64"); const result = await workerFetch.fetch( { method: "POST", headers: { get: () => "application/json", }, - url: undefined, - json() { - return { settings: {} }; - }, + url, + json: () => ({ + ...data, + signature, + }), } as unknown as Request, { - SUPABASE_URL: "url", - SUPABASE_KEY: "key", + SUPABASE_URL: "http://localhost:65432", + KERNEL_PUBLIC_KEY: publicKey, } as unknown as Env ); expect(result.ok).toEqual(false); expect(result.status).toEqual(500); - expect(errorSpy).toHaveBeenCalled(); + expect(await result.text()).toEqual("Internal Server Error"); }); }); diff --git a/tsconfig.json b/tsconfig.json index c6d3097..8c39d70 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,7 @@ /* Modules */ "module": "commonjs" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ diff --git a/wrangler.toml b/wrangler.toml index 96f8ec8..469c0ed 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -4,3 +4,6 @@ compatibility_date = "2024-09-23" compatibility_flags = [ "nodejs_compat" ] [env.dev] [env.prod] + +[observability] +enabled = true \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3f0d6ed..7e27ce2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@actions/core@1.10.1", "@actions/core@^1.10.1": +"@actions/core@1.10.1": version "1.10.1" resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.10.1.tgz#61108e7ac40acae95ee36da074fa5850ca4ced8a" integrity sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g== @@ -15,7 +15,7 @@ "@actions/http-client" "^2.0.1" uuid "^8.3.2" -"@actions/github@6.0.0", "@actions/github@^6.0.0": +"@actions/github@6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@actions/github/-/github-6.0.0.tgz#65883433f9d81521b782a64cc1fd45eef2191ea7" integrity sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g== @@ -361,35 +361,35 @@ dependencies: mime "^3.0.0" -"@cloudflare/workerd-darwin-64@1.20240925.0": - version "1.20240925.0" - resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240925.0.tgz#f78fe394f73540594609d8e05a2da7feb46c76c0" - integrity sha512-KdLnSXuzB65CbqZPm+qYzk+zkQ1tUNPaaRGYVd/jPYAxwwtfTUQdQ+ahDPwVVs2tmQELKy7ZjQjf2apqSWUfjw== +"@cloudflare/workerd-darwin-64@1.20241022.0": + version "1.20241022.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20241022.0.tgz#1d22149152ad32672971e3be48ab1491ff236eb3" + integrity sha512-1NNYun37myMTgCUiPQEJ0cMal4mKZVTpkD0b2tx9hV70xji+frVJcSK8YVLeUm1P+Rw1d/ct8DMgQuCpsz3Fsw== -"@cloudflare/workerd-darwin-arm64@1.20240925.0": - version "1.20240925.0" - resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240925.0.tgz#f03b17177744ad898bb12610d15cc0a9744abfe6" - integrity sha512-MiQ6uUmCXjsXgWNV+Ock2tp2/tYqNJGzjuaH6jFioeRF+//mz7Tv7J7EczOL4zq+TH8QFOh0/PUsLyazIWVGng== +"@cloudflare/workerd-darwin-arm64@1.20241022.0": + version "1.20241022.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20241022.0.tgz#6ec4e4fd1427ac09d9cfea38db849799755029e5" + integrity sha512-FOO/0P0U82EsTLTdweNVgw+4VOk5nghExLPLSppdOziq6IR5HVgP44Kmq5LdsUeHUhwUmfOh9hzaTpkNzUqKvw== -"@cloudflare/workerd-linux-64@1.20240925.0": - version "1.20240925.0" - resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240925.0.tgz#fe0366b804b957acf5012d889e94163bab806a57" - integrity sha512-Rjix8jsJMfsInmq3Hm3fmiRQ+rwzuWRPV1pg/OWhMSfNP7Qp2RCU+RGkhgeR9Z5eNAje0Sn2BMrFq4RvF9/yRA== +"@cloudflare/workerd-linux-64@1.20241022.0": + version "1.20241022.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20241022.0.tgz#0297222e46ce8b2c55b591ae87ee2bbb85830e20" + integrity sha512-RsNc19BQJG9yd+ngnjuDeG9ywZG+7t1L4JeglgceyY5ViMNMKVO7Zpbsu69kXslU9h6xyQG+lrmclg3cBpnhYA== -"@cloudflare/workerd-linux-arm64@1.20240925.0": - version "1.20240925.0" - resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240925.0.tgz#fcf82de06def420972c661a6021c87683cd8fbdc" - integrity sha512-VYIPeMHQRtbwQoIjUwS/zULlywPxyDvo46XkTpIW5MScEChfqHvAYviQ7TzYGx6Q+gmZmN+DUB2KOMx+MEpCxA== +"@cloudflare/workerd-linux-arm64@1.20241022.0": + version "1.20241022.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20241022.0.tgz#7637092b14129c94ddb87990221d38623e4fd498" + integrity sha512-x5mUXpKxfsosxcFmcq5DaqLs37PejHYVRsNz1cWI59ma7aC4y4Qn6Tf3i0r9MwQTF/MccP4SjVslMU6m4W7IaA== -"@cloudflare/workerd-windows-64@1.20240925.0": - version "1.20240925.0" - resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240925.0.tgz#0a5c82b95b03a94591cc8a1830f28d2e41ff7685" - integrity sha512-C8peGvaU5R51bIySi1VbyfRgwNSSRknqoFSnSbSBI3uTN3THTB3UnmRKy7GXJDmyjgXuT9Pcs1IgaWNubLtNtw== +"@cloudflare/workerd-windows-64@1.20241022.0": + version "1.20241022.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20241022.0.tgz#64f05a2bc475e1450154942c060555184e6350de" + integrity sha512-eBCClx4szCOgKqOlxxbdNszMqQf3MRG1B9BRIqEM/diDfdR9IrZ8l3FaEm+l9gXgPmS6m1NBn40aWuGBl8UTSw== -"@cloudflare/workers-shared@0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@cloudflare/workers-shared/-/workers-shared-0.5.4.tgz#bbf8f03b79a6bc0169ad66a6015ebe579d36753a" - integrity sha512-PNL/0TjKRdUHa1kwgVdqUNJVZ9ez4kacsi8omz+gv859EvJmsVuGiMAClY2YfJnC9LVKhKCcjqmFgKNXG9/IXA== +"@cloudflare/workers-shared@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workers-shared/-/workers-shared-0.7.0.tgz#ecb6a3a1f483989c1ca10d5ed0592d01c7eff0c0" + integrity sha512-LLQRTqx7lKC7o2eCYMpyc5FXV8d0pUX6r3A+agzhqS9aoR5A6zCPefwQGcvbKx83ozX22ATZcemwxQXn12UofQ== dependencies: mime "^3.0.0" zod "^3.22.3" @@ -2158,10 +2158,10 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@sinclair/typebox@^0.32.15": - version "0.32.29" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.32.29.tgz#35a31aac1078f14b722bd08086cea44fa73b206a" - integrity sha512-GWKskKPGQV0vVYizqCu0E1YLwGthvlkDqpRxB3iBuqxJ8dN/9n1cnDRSQHF59GMoxDJwzSgmxpU617SidtUnMw== +"@sinclair/typebox@^0.33.17": + version "0.33.17" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.33.17.tgz#603475bb1fb343deb82a4ce85e6adf177a65739b" + integrity sha512-75232GRx3wp3P7NP+yc4nRK3XUAnaQShxTAzapgmQrgs0QvSq0/mOJGoZXRpH15cFCKyys+4laCPbBselqJ5Ag== "@sinonjs/commons@^3.0.0": version "3.0.1" @@ -2533,10 +2533,10 @@ "@typescript-eslint/types" "7.0.1" eslint-visitor-keys "^3.4.1" -"@ubiquity-os/ubiquity-os-kernel@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@ubiquity-os/ubiquity-os-kernel/-/ubiquity-os-kernel-2.4.0.tgz#1bb74d4b02ef5ba6b0c1c01d509e3e0f58609904" - integrity sha512-KT8AwtMOHA99GoVUs43eAR2PZii9AHmY9NjOlBtvotB5tXbeEIyhjgHr0kgRncgiLJU1UFIe0QYMmpOvmXiQpg== +"@ubiquity-os/ubiquity-os-kernel@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@ubiquity-os/ubiquity-os-kernel/-/ubiquity-os-kernel-2.5.1.tgz#13cc77146837deeb7a6e6346511a686be78a832b" + integrity sha512-G+gL/NmZTP7QiijB1CcHXBj78iKW7RYdRC80ETWxzojIR3fZYPxTSb3rWEJOim9iAfQ7qGawJQJGL1yrPuMRBQ== dependencies: "@actions/core" "1.10.1" "@actions/github" "6.0.0" @@ -3505,6 +3505,11 @@ date-fns@^2.21.1: dependencies: "@babel/runtime" "^7.21.0" +date-fns@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== + debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -4515,10 +4520,10 @@ hono@4.4.13: resolved "https://registry.yarnpkg.com/hono/-/hono-4.4.13.tgz#954e8f6e4bab14f3f9d7bac4eef4c56d23e7f900" integrity sha512-c6qqenclmQ6wpXzqiElMa2jt423PVCmgBreDfC5s2lPPpGk7d0lOymd8QTzFZyYC5mSSs6imiTMPip+gLwuW/g== -hono@^4.6.5: - version "4.6.5" - resolved "https://registry.yarnpkg.com/hono/-/hono-4.6.5.tgz#9d5a1ada5b40dc865e2d28c0bcc5d24cc755b9f1" - integrity sha512-qsmN3V5fgtwdKARGLgwwHvcdLKursMd+YOt69eGpl1dUCJb8mCd7hZfyZnBYjxCegBG7qkJRQRUy2oO25yHcyQ== +hono@^4.6.7: + version "4.6.7" + resolved "https://registry.yarnpkg.com/hono/-/hono-4.6.7.tgz#5389be797be4e049991d29ad7aea499e70d28085" + integrity sha512-wX4ZbOnzfNO61hUjuQbJ7OPGs1fWXXVVJ8VTPDb2Ls/x9HjCbVTm80Je6VTHMz5n5RGDtBgV9d9ZFZxBqx56ng== hosted-git-info@^2.1.4: version "2.8.9" @@ -4945,6 +4950,11 @@ iterable-lookahead@^1.0.0: resolved "https://registry.yarnpkg.com/iterable-lookahead/-/iterable-lookahead-1.0.0.tgz#896dfcb78680bdb50036e97edb034c8b68a9737f" integrity sha512-hJnEP2Xk4+44DDwJqUQGdXal5VbyeWLaPyDl2AQc242Zr7iqz4DgpQOrEzglWVMGHMDCkguLHEKxd1+rOsmgSQ== +itty-time@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/itty-time/-/itty-time-1.0.6.tgz#a6eeda619f19d2f4c480ceddd013b93acb05714d" + integrity sha512-+P8IZaLLBtFv8hCkIjcymZOp4UJ+xW6bSlQsXGqrkmJh7vSiMFSlNne0mCYagEE0N7HDNR5jJBRxwN0oYv61Rw== + jackspeak@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" @@ -5804,10 +5814,10 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -miniflare@3.20240925.0: - version "3.20240925.0" - resolved "https://registry.yarnpkg.com/miniflare/-/miniflare-3.20240925.0.tgz#a291998dedf90bfb4bcfdad033ba030851ff9171" - integrity sha512-2LmQbKHf0n6ertUKhT+Iltixi53giqDH7P71+wCir3OnGyXIODqYwOECx1mSDNhYThpxM2dav8UdPn6SQiMoXw== +miniflare@3.20241022.0: + version "3.20241022.0" + resolved "https://registry.yarnpkg.com/miniflare/-/miniflare-3.20241022.0.tgz#31b8a2bc53b411ac814b55db9c31aebfe475f344" + integrity sha512-x9Fbq1Hmz1f0osIT9Qmj78iX4UpCP2EqlZnA/tzj/3+I49vc3Kq0fNqSSKplcdf6HlCHdL3fOBicmreQF4BUUQ== dependencies: "@cspotcode/source-map-support" "0.8.1" acorn "^8.8.0" @@ -5817,7 +5827,7 @@ miniflare@3.20240925.0: glob-to-regexp "^0.4.1" stoppable "^1.1.0" undici "^5.28.4" - workerd "1.20240925.0" + workerd "1.20241022.0" ws "^8.17.1" youch "^3.2.2" zod "^3.22.3" @@ -7458,10 +7468,10 @@ undici@^5.25.4, undici@^5.28.4: dependencies: "@fastify/busboy" "^2.0.0" -"unenv@npm:unenv-nightly@2.0.0-20240919-125358-9a64854": - version "2.0.0-20240919-125358-9a64854" - resolved "https://registry.yarnpkg.com/unenv-nightly/-/unenv-nightly-2.0.0-20240919-125358-9a64854.tgz#13f6812c7b12b9521ea05c6d49259d136e093acd" - integrity sha512-XjsgUTrTHR7iw+k/SRTNjh6EQgwpC9voygnoCJo5kh4hKqsSDHUW84MhL9EsHTNfLctvVBHaSw8e2k3R2fKXsQ== +"unenv@npm:unenv-nightly@2.0.0-20241018-011344-e666fcf": + version "2.0.0-20241018-011344-e666fcf" + resolved "https://registry.yarnpkg.com/unenv-nightly/-/unenv-nightly-2.0.0-20241018-011344-e666fcf.tgz#75b6d1ab37e6b5edeec80fe8f403e3de8e9bc50e" + integrity sha512-D00bYn8rzkCBOlLx+k1iHQlc69jvtJRT7Eek4yIGQ6461a2tUBjngGZdRpqsoXAJCz/qBW0NgPting7Zvg+ysg== dependencies: defu "^6.1.4" ohash "^1.1.4" @@ -7672,38 +7682,40 @@ which@^4.0.0: dependencies: isexe "^3.1.1" -workerd@1.20240925.0: - version "1.20240925.0" - resolved "https://registry.yarnpkg.com/workerd/-/workerd-1.20240925.0.tgz#0a2602eabfa7e1d01d89ff2b600ed359be9b515d" - integrity sha512-/Jj6+yLwfieZGEt3Kx4+5MoufuC3g/8iFaIh4MPBNGJOGYmdSKXvgCqz09m2+tVCYnysRfbq2zcbVxJRBfOCqQ== +workerd@1.20241022.0: + version "1.20241022.0" + resolved "https://registry.yarnpkg.com/workerd/-/workerd-1.20241022.0.tgz#45ab10642009b4573ae78df784e0a8c5b338914e" + integrity sha512-jyGXsgO9DRcJyx6Ovv7gUyDPc3UYC2i/E0p9GFUg6GUzpldw4Y93y9kOmdfsOnKZ3+lY53veSiUniiBPE6Q2NQ== optionalDependencies: - "@cloudflare/workerd-darwin-64" "1.20240925.0" - "@cloudflare/workerd-darwin-arm64" "1.20240925.0" - "@cloudflare/workerd-linux-64" "1.20240925.0" - "@cloudflare/workerd-linux-arm64" "1.20240925.0" - "@cloudflare/workerd-windows-64" "1.20240925.0" + "@cloudflare/workerd-darwin-64" "1.20241022.0" + "@cloudflare/workerd-darwin-arm64" "1.20241022.0" + "@cloudflare/workerd-linux-64" "1.20241022.0" + "@cloudflare/workerd-linux-arm64" "1.20241022.0" + "@cloudflare/workerd-windows-64" "1.20241022.0" -wrangler@3.79.0: - version "3.79.0" - resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-3.79.0.tgz#c23b08ba06bff752b650a567a7fcbe03e4036914" - integrity sha512-29wzQWc5qNKtD3bSyAEX11j/U5IIk8xy2/ZJ4wljJlS5ppff8qGqI+LtlLmqjqTSeQqFLb87xRkms0YFUgNLXg== +wrangler@^3.83.0: + version "3.83.0" + resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-3.83.0.tgz#bebe3d0067f3430cf6f0b2e8e4ab648c208e002b" + integrity sha512-qDzdUuTngKqmm2OJUZm7Gk4+Hv37F2nNNAHuhIgItEIhxBdOVDsgKmvpd+f41MFxyuGg3fbGWYANHI+0V2Z5yw== dependencies: "@cloudflare/kv-asset-handler" "0.3.4" - "@cloudflare/workers-shared" "0.5.4" + "@cloudflare/workers-shared" "0.7.0" "@esbuild-plugins/node-globals-polyfill" "^0.2.3" "@esbuild-plugins/node-modules-polyfill" "^0.2.2" blake3-wasm "^2.1.5" chokidar "^3.5.3" + date-fns "^4.1.0" esbuild "0.17.19" - miniflare "3.20240925.0" + itty-time "^1.0.6" + miniflare "3.20241022.0" nanoid "^3.3.3" path-to-regexp "^6.3.0" resolve "^1.22.8" resolve.exports "^2.0.2" selfsigned "^2.0.1" source-map "^0.6.1" - unenv "npm:unenv-nightly@2.0.0-20240919-125358-9a64854" - workerd "1.20240925.0" + unenv "npm:unenv-nightly@2.0.0-20241018-011344-e666fcf" + workerd "1.20241022.0" xxhash-wasm "^1.0.1" optionalDependencies: fsevents "~2.3.2"