From edbd38443293d0a9d066294234795a88ebe34ab0 Mon Sep 17 00:00:00 2001 From: Umar Adilov <99314948+adilovcode@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:22:34 +0500 Subject: [PATCH] Implemented opencode plugins --- .opencode/plugin/dev-server-hmr.ts | 70 +++++ .opencode/plugin/file-protection.ts | 26 ++ .opencode/types/taylordb.types.ts | 410 ++++++++++++++++++++++++++++ index.html | 2 +- 4 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 .opencode/plugin/dev-server-hmr.ts create mode 100644 .opencode/plugin/file-protection.ts create mode 100644 .opencode/types/taylordb.types.ts diff --git a/.opencode/plugin/dev-server-hmr.ts b/.opencode/plugin/dev-server-hmr.ts new file mode 100644 index 0000000..9b0933b --- /dev/null +++ b/.opencode/plugin/dev-server-hmr.ts @@ -0,0 +1,70 @@ +import type { Plugin } from "@opencode-ai/plugin"; +import { Axios } from "axios"; +import { z } from "zod"; + +const { vmOrchestrationStatusUpdateUrl } = z + .object({ + vmOrchestrationStatusUpdateUrl: z.string(), + }) + .parse({ + vmOrchestrationStatusUpdateUrl: + process.env.TAYLORDB_VM_ORCHESTRATION_STATUS_UPDATE_URL, + }); + +const axios = new Axios({ + baseURL: vmOrchestrationStatusUpdateUrl, +}); + +const updateAppStatus = async (status: "Errored" | "Active" | "Pending") => { + await axios.put("/", { + status, + }); +}; + +export const DevServerHMRPlugin: Plugin = async ({ client, $ }) => { + return { + event: async ({ event }) => { + if (event.type !== "session.idle") return; + + const result = await $`pnpm build`.catch((error) => error); + + if (result.exitCode !== 0) { + if (!client.session["tries"]) { + client.session["tries"] = 0; + } else { + client.session["tries"]++; + } + + if (client.session["tries"] > 3) { + await updateAppStatus("Errored"); + + console.log("CHANGE STATUS TO ERRORED"); + + return; + } + + await client.session.prompt({ + path: { id: event.properties.sessionID }, + body: { + parts: [ + { + type: "text", + text: `While building the project, the following error occurred:\n\n${result.stderr.toString()}\n\nPlease fix the error and try again.`, + }, + ], + }, + }); + } + + await updateAppStatus("Active"); + + console.log("CHANGE STATUS TO ACTIVE"); + }, + + "chat.message": async (input, output) => { + await updateAppStatus("Pending"); + + console.log("CHANGE STATUS TO PENDING"); + }, + }; +}; diff --git a/.opencode/plugin/file-protection.ts b/.opencode/plugin/file-protection.ts new file mode 100644 index 0000000..86cccba --- /dev/null +++ b/.opencode/plugin/file-protection.ts @@ -0,0 +1,26 @@ +import type { Plugin } from "@opencode-ai/plugin"; +import micromatch from "micromatch"; + +const uneditableFiles = [ + ".env", + ".env.local", + ".env.development", + ".env.production", + "src/lib/*.ts", + "opencode.json", +]; + +export const FileProtectionPlugin: Plugin = async ({ client, $ }) => { + return { + "tool.execute.before": async (input, output) => { + if ( + input.tool === "edit" && + uneditableFiles.some((pattern) => + micromatch.isMatch(output.args.filePath, pattern) + ) + ) { + throw new Error(`Do not edit ${output.args.filePath} files`); + } + }, + }; +}; diff --git a/.opencode/types/taylordb.types.ts b/.opencode/types/taylordb.types.ts new file mode 100644 index 0000000..bab5246 --- /dev/null +++ b/.opencode/types/taylordb.types.ts @@ -0,0 +1,410 @@ +/** + * Copyright (c) 2025 TaylorDB + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +interface FileInformation { + fieldname: string; + originalname: string; + encoding: string; + mimetype: string; + destination: string; + filename: string; + path: string; + size: number; + format: string; + width: number; + height: number; +} + +interface UploadResponse { + collectionName: string; + fileInformation: FileInformation; + metadata: { + thumbnails: any[]; + clips: any[]; + }; + baseId: string; + storageAdaptor: string; + _id: string; + __v: number; +} + +export interface AttachmentColumnValue { + url: string; + fileType: string; + size: number; +} + +export class Attachment { + public readonly collectionName: string; + public readonly fileInformation: FileInformation; + public readonly metadata: { thumbnails: any[]; clips: any[] }; + public readonly baseId: string; + public readonly storageAdaptor: string; + public readonly _id: string; + + constructor(data: UploadResponse) { + this.collectionName = data.collectionName; + this.fileInformation = data.fileInformation; + this.metadata = data.metadata; + this.baseId = data.baseId; + this.storageAdaptor = data.storageAdaptor; + this._id = data._id; + } + + toColumnValue(): AttachmentColumnValue { + return { + url: this.fileInformation.path, + fileType: this.fileInformation.mimetype, + size: this.fileInformation.size, + }; + } +} + +type IsWithinOperatorValue = + | "pastWeek" + | "pastMonth" + | "pastYear" + | "nextWeek" + | "nextMonth" + | "nextYear" + | "daysFromNow" + | "daysAgo" + | "currentWeek" + | "currentMonth" + | "currentYear"; + +type DefaultDateFilterValue = + | ( + | "today" + | "tomorrow" + | "yesterday" + | "oneWeekAgo" + | "oneWeekFromNow" + | "oneMonthAgo" + | "oneMonthFromNow" + ) + | ["exactDay" | "exactTimestamp", string] + | ["daysAgo" | "daysFromNow", number]; + +type DateFilters = { + "=": DefaultDateFilterValue; + "!=": DefaultDateFilterValue; + "<": DefaultDateFilterValue; + ">": DefaultDateFilterValue; + "<=": DefaultDateFilterValue; + ">=": DefaultDateFilterValue; + isWithIn: + | IsWithinOperatorValue + | { value: "daysAgo" | "daysFromNow"; date: number }; + isEmpty: boolean; + isNotEmpty: boolean; +}; + +type DateAggregations = { + empty: number; + filled: number; + unique: number; + percentEmpty: number; + percentFilled: number; + percentUnique: number; + min: number | null; + max: number | null; + daysRange: number | null; + monthRange: number | null; +}; + +type TextFilters = { + "=": string; + "!=": string; + caseEqual: string; + hasAnyOf: string[]; + contains: string; + startsWith: string; + endsWith: string; + doesNotContain: string; + isEmpty: never; + isNotEmpty: never; +}; + +type LinkFilters = { + hasAnyOf: number[]; + hasAllOf: number[]; + isExactly: number[]; + "=": number; + hasNoneOf: number[]; + contains: string; + doesNotContain: string; + isEmpty: never; + isNotEmpty: never; +}; + +type SelectFilters = { + hasAnyOf: O[number][]; + hasAllOf: O[number][]; + isExactly: O[number][]; + "=": O[number]; + hasNoneOf: O[number][]; + contains: string; + doesNotContain: string; + isEmpty: never; + isNotEmpty: never; +}; + +type LinkAggregations = { + empty: number; + filled: number; + percentEmpty: number; + percentFilled: number; +}; + +type NumberFilters = { + "=": number; + "!=": number; + ">": number; + ">=": number; + "<": number; + "<=": number; + hasAnyOf: number[]; + hasNoneOf: number[]; + isEmpty: never; + isNotEmpty: never; +}; + +type NumberAggregations = { + sum: number; + average: number; + median: number; + min: number | null; + max: number | null; + range: number; + standardDeviation: number; + histogram: Record; + empty: number; + filled: number; + unique: number; + percentEmpty: number; + percentFilled: number; + percentUnique: number; +}; + +type CheckboxFilters = { + "=": number; +}; + +/** + * + * Column types + * + */ +export type ColumnType< + S, + U, + I, + R extends boolean, + F extends { [key: string]: any } = object, + A extends { [key: string]: any } = object +> = { + raw: S; + insert: I; + update: U; + filters: F; + aggregations: A; + isRequired: R; +}; + +export type DateColumnType = ColumnType< + string, + string, + string, + R, + DateFilters, + DateAggregations +>; + +export type TextColumnType = ColumnType< + string, + string, + string, + R, + TextFilters +>; + +export type ALinkColumnType< + T extends string, + S, + U, + I, + R extends boolean, + F extends { [key: string]: any } = LinkFilters, + A extends LinkAggregations = LinkAggregations +> = ColumnType & { + linkedTo: T; +}; + +export type LinkColumnType< + T extends string, + R extends boolean +> = ALinkColumnType< + T, + object, + number | number[] | { newIds: number[]; deletedIds: number[] }, + number | number[], + R +>; + +export type AttachmentColumnType = ALinkColumnType< + "attachmentTable", + Attachment[], + Attachment[] | { newIds: number[]; deletedIds: number[] } | number[], + Attachment[] | number[], + R +>; + +export type NumberColumnType = ColumnType< + number, + number, + number, + R, + NumberFilters, + NumberAggregations +>; + +export type CheckboxColumnType = ColumnType< + boolean, + boolean, + boolean, + R, + CheckboxFilters +>; + +export type AutoGeneratedNumberColumnType = ColumnType< + number, + never, + never, + false, + NumberFilters, + NumberAggregations +>; + +export type AutoGeneratedDateColumnType = ColumnType< + string, + never, + never, + false, + DateFilters, + DateAggregations +>; + +export type SingleSelectColumnType< + O extends readonly string[], + R extends boolean +> = ALinkColumnType< + "selectTable", + O[number], + O[number] | O[number][], + O[number] | O[number][], + R, + SelectFilters +>; + +export type TableRaws = { + [K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType< + infer S, + any, + any, + infer R, + any, + any + > + ? R extends true + ? S + : S | undefined + : never; +}; + +export type TableInserts = { + [K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType< + any, + infer I, + any, + infer R, + any, + any + > + ? R extends true + ? I + : I | undefined + : never; +}; + +export type TableUpdates = { + [K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType< + any, + any, + infer U, + any, + any, + any + > + ? U + : never; +}; + +export type SelectTable = { + id: AutoGeneratedNumberColumnType; + name: TextColumnType; + color: TextColumnType; +}; + +export type AttachmentTable = { + id: AutoGeneratedNumberColumnType; + name: TextColumnType; + metadata: TextColumnType; + size: NumberColumnType; + fileType: TextColumnType; + url: TextColumnType; +}; + +export type CollaboratorsTable = { + id: AutoGeneratedNumberColumnType; + name: TextColumnType; + emailAddress: TextColumnType; + avatar: TextColumnType; +}; + +export type TaylorDatabase = { + /** + * + * + * Internal tables, these tables can not be queried directly. + * + */ + selectTable: SelectTable; + attachmentTable: AttachmentTable; + collaboratorsTable: CollaboratorsTable; + apps: AppsTable; +}; + +export type VmStatusOptions = ["Active", "Inactive", "Pending", "Errored"]; +export type AppStatusOptions = ["Active", "Inactive", "Pending", "Errored"]; + +export type AppsTable = { + name: TextColumnType; + slug: TextColumnType; + description: TextColumnType; + templateRepoUrl: TextColumnType; + devUrl: TextColumnType; + opencodeUrl: TextColumnType; + isPublic: CheckboxColumnType; + isPublished: CheckboxColumnType; + status: SingleSelectColumnType; + appStatus: SingleSelectColumnType; + icon: TextColumnType; + color: TextColumnType; + healthcheck: TextColumnType; + publishUrl: TextColumnType; +}; diff --git a/index.html b/index.html index ec8fa89..1d24401 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - task tracker + blank