Skip to content

Commit

Permalink
Merge pull request #25 from gentlementlegen/feat/schema-validation
Browse files Browse the repository at this point in the history
feat: schema validation
  • Loading branch information
gentlementlegen authored Oct 4, 2024
2 parents 3684bec + 3182d29 commit 4622aff
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 26 deletions.
59 changes: 59 additions & 0 deletions .github/workflows/update-configuration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: "Update Configuration"

on:
workflow_dispatch:
push:

jobs:
update:
name: "Update Configuration in manifest.json"
runs-on: ubuntu-latest
permissions: write-all

steps:
- uses: actions/checkout@v4

- name: Setup node
uses: actions/setup-node@v4
with:
node-version: "20.10.0"

- name: Install deps and run configuration update
run: |
yarn install --immutable --immutable-cache --check-cache
yarn tsc --noCheck --project tsconfig.json
- name: Update manifest configuration using GitHub Script
uses: actions/github-script@v7
with:
script: |
(async () => {
const fs = await import('fs/promises');
const path = await import('path');
const { pluginSettingsSchema } = await import("${{ github.workspace }}/src/types/plugin-inputs.js");
const manifestPath = path.resolve("${{ github.workspace }}", './manifest.json');
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
const configuration = JSON.stringify(pluginSettingsSchema);
manifest["configuration"] = JSON.parse(configuration);
const updatedManifest = JSON.stringify(manifest, null, 2);
console.log('Updated manifest:', updatedManifest);
await fs.writeFile(manifestPath, updatedManifest);
})();
- name: Commit and Push generated types
run: |
git config --global user.name 'ubiquity-os[bot]'
git config --global user.email 'ubiquity-os[bot]@users.noreply.github.com'
git add ./manifest.json
if [ -n "$(git diff-index --cached --name-only HEAD)" ]; then
git commit -m "chore: updated generated configuration" || echo "Lint-staged check failed"
git push origin HEAD:${{ github.ref_name }}
else
echo "No changes to commit"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
File renamed without changes.
40 changes: 38 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
{
"name": "User activity watcher",
"description": "Watches user activity on issues, sends reminders on deadlines, and unassign inactive users.",
"ubiquity:listeners": ["pull_request_review_comment.created", "issue_comment.created", "push"]
}
"ubiquity:listeners": [
"pull_request_review_comment.created",
"issue_comment.created",
"push"
],
"configuration": {
"type": "object",
"properties": {
"warning": {
"default": "3.5 days",
"type": "string"
},
"watch": {
"type": "object",
"properties": {
"optOut": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"optOut"
]
},
"disqualification": {
"default": "7 days",
"type": "string"
}
},
"required": [
"warning",
"watch",
"disqualification"
]
}
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "user-activity-watcher",
"version": "1.0.0",
"description": "Watches user activity on issues, sends reminders on deadlines, and unassign inactive users.",
"main": "src/worker.ts",
"main": "src/index.ts",
"author": "Ubiquity DAO",
"license": "MIT",
"type": "module",
Expand Down Expand Up @@ -40,7 +40,8 @@
"dotenv": "16.4.5",
"luxon": "3.4.4",
"ms": "2.1.3",
"tsx": "4.11.2"
"tsx": "4.11.2",
"typebox-validators": "0.3.5"
},
"devDependencies": {
"@commitlint/cli": "19.3.0",
Expand Down Expand Up @@ -72,7 +73,7 @@
"prettier": "3.3.0",
"supabase": "1.176.9",
"ts-jest": "29.1.4",
"typescript": "^5.4.5"
"typescript": "5.6.2"
},
"lint-staged": {
"*.ts": [
Expand Down
50 changes: 50 additions & 0 deletions src/helpers/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as github from "@actions/github";
import { Octokit } from "@octokit/rest";
import { TransformDecodeCheckError, TransformDecodeError, Value, ValueError } from "@sinclair/typebox/value";
import { Env, envSchema, envValidator, pluginSettingsValidator, PluginSettings, pluginSettingsSchema } from "../types/plugin-inputs";

export function validateAndDecodeSchemas(rawEnv: object, rawSettings: object) {
const errors: ValueError[] = [];

const env = Value.Default(envSchema, rawEnv) as Env;
if (!envValidator.test(env)) {
for (const error of envValidator.errors(env)) {
errors.push(error);
}
}

const settings = Value.Default(pluginSettingsSchema, rawSettings) as PluginSettings;
if (!pluginSettingsValidator.test(settings)) {
for (const error of pluginSettingsValidator.errors(settings)) {
errors.push(error);
}
}

if (errors.length) {
throw { errors };
}

try {
const decodedSettings = Value.Decode(pluginSettingsSchema, settings);
const decodedEnv = Value.Decode(envSchema, rawEnv || {});
return { decodedEnv, decodedSettings };
} catch (e) {
if (e instanceof TransformDecodeCheckError || e instanceof TransformDecodeError) {
throw { errors: [e.error] };
}
throw e;
}
}

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),
},
});
}
8 changes: 4 additions & 4 deletions src/parser/payload.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import * as github from "@actions/github";
import { Value } from "@sinclair/typebox/value";
import { config } from "dotenv";
import { PluginInputs, userActivityWatcherSettingsSchema } from "../types/plugin-inputs";
import { validateAndDecodeSchemas } from "../helpers/validator";
import { PluginInputs } from "../types/plugin-inputs";

config();

const webhookPayload = github.context.payload.inputs;
const settings = Value.Decode(userActivityWatcherSettingsSchema, Value.Default(userActivityWatcherSettingsSchema, JSON.parse(webhookPayload.settings)));
const { decodedSettings } = validateAndDecodeSchemas(JSON.parse(webhookPayload.settings), process.env);

const program: PluginInputs = {
stateId: webhookPayload.stateId,
eventName: webhookPayload.eventName,
authToken: webhookPayload.authToken,
ref: webhookPayload.ref,
eventPayload: JSON.parse(webhookPayload.eventPayload),
settings,
settings: decodedSettings,
};

export default program;
3 changes: 2 additions & 1 deletion src/run.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Octokit } from "@octokit/rest";
import { returnDataToKernel } from "./helpers/validator";
import { Context } from "./types/context";
import { PluginInputs } from "./types/plugin-inputs";
import { Logs } from "@ubiquity-dao/ubiquibot-logger";
Expand All @@ -14,7 +15,7 @@ export async function run(inputs: PluginInputs) {
logger: new Logs("verbose"),
};
await runPlugin(context);
return JSON.stringify({ status: 200 });
return returnDataToKernel(process.env.GITHUB_TOKEN, inputs.stateId, {});
}

export async function runPlugin(context: Context) {
Expand Down
4 changes: 2 additions & 2 deletions src/types/context.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { EmitterWebhookEvent as WebhookEvent, EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks";
import { Octokit } from "@octokit/rest";
import { SupportedEvents, UserActivityWatcherSettings } from "./plugin-inputs";
import { SupportedEvents, PluginSettings } from "./plugin-inputs";
import { Logs } from "@ubiquity-dao/ubiquibot-logger";

export interface Context<T extends SupportedEvents = SupportedEvents> {
eventName: T;
payload: WebhookEvent<T>["payload"];
octokit: InstanceType<typeof Octokit>;
config: UserActivityWatcherSettings;
config: PluginSettings;
logger: Logs;
}
15 changes: 12 additions & 3 deletions src/types/plugin-inputs.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { EmitterWebhookEvent as WebhookEvent, EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks";
import { StaticDecode, StringOptions, Type as T, TypeBoxError } from "@sinclair/typebox";
import ms from "ms";
import { StandardValidator } from "typebox-validators";

export type SupportedEvents = "pull_request_review_comment.created" | "issue_comment.created" | "push";

export interface PluginInputs<T extends WebhookEventName = SupportedEvents> {
stateId: string;
eventName: T;
eventPayload: WebhookEvent<T>["payload"];
settings: UserActivityWatcherSettings;
settings: PluginSettings;
authToken: string;
ref: string;
}
Expand All @@ -31,7 +32,7 @@ function thresholdType(options?: StringOptions) {
});
}

export const userActivityWatcherSettingsSchema = T.Object({
export const pluginSettingsSchema = T.Object({
/**
* Delay to send reminders. 0 means disabled. Any other value is counted in days, e.g. 1,5 days
*/
Expand All @@ -51,4 +52,12 @@ export const userActivityWatcherSettingsSchema = T.Object({
}),
});

export type UserActivityWatcherSettings = StaticDecode<typeof userActivityWatcherSettingsSchema>;
export const pluginSettingsValidator = new StandardValidator(pluginSettingsSchema);

export type PluginSettings = StaticDecode<typeof pluginSettingsSchema>;

export const envSchema = T.Object({});

export const envValidator = new StandardValidator(envSchema);

export type Env = StaticDecode<typeof envSchema>;
9 changes: 9 additions & 0 deletions src/types/process-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
GITHUB_TOKEN: string;
}
}
}

export {};
8 changes: 4 additions & 4 deletions tests/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { drop } from "@mswjs/data";
import { TransformDecodeError, Value } from "@sinclair/typebox/value";
import { runPlugin } from "../src/run";
import { userActivityWatcherSettingsSchema } from "../src/types/plugin-inputs";
import { pluginSettingsSchema } from "../src/types/plugin-inputs";
import { db } from "./__mocks__/db";
import { server } from "./__mocks__/node";
import cfg from "./__mocks__/results/valid-configuration.json";
Expand Down Expand Up @@ -32,12 +32,12 @@ describe("User start/stop", () => {
});

it("Should parse thresholds", async () => {
const settings = Value.Decode(userActivityWatcherSettingsSchema, Value.Default(userActivityWatcherSettingsSchema, cfg));
const settings = Value.Decode(pluginSettingsSchema, Value.Default(pluginSettingsSchema, cfg));
expect(settings).toEqual({ warning: 302400000, disqualification: 604800000, watch: { optOut: [STRINGS.PRIVATE_REPO_NAME] } });
expect(() =>
Value.Decode(
userActivityWatcherSettingsSchema,
Value.Default(userActivityWatcherSettingsSchema, {
pluginSettingsSchema,
Value.Default(pluginSettingsSchema, {
warning: "12 foobars",
disqualification: "2 days",
watch: { optOut: [STRINGS.PRIVATE_REPO_NAME] },
Expand Down
6 changes: 3 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */

/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
Expand All @@ -25,9 +25,9 @@
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */

/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
"module": "ESNext" /* 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. */
Expand Down
13 changes: 9 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5189,10 +5189,15 @@ type-fest@^4.9.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.18.3.tgz#5249f96e7c2c3f0f1561625f54050e343f1c8f68"
integrity sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ==

typescript@^5.4.5:
version "5.5.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
[email protected]:
version "0.3.5"
resolved "https://registry.yarnpkg.com/typebox-validators/-/typebox-validators-0.3.5.tgz#b913bad0a87571ffe0edd01d2b6090a268e1ecc9"
integrity sha512-FXrmSUAN6bSGxDANResNCZQ8VRRLr5bSyy73/HyqSXGdiVuogppGAoRocy7NTVZY4Wc2sWUofmWwwIXE6OxS6Q==

[email protected]:
version "5.6.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0"
integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==

undici-types@~5.26.4:
version "5.26.5"
Expand Down

0 comments on commit 4622aff

Please sign in to comment.