summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-06-20 14:53:38 -0700
committerElizabeth Hunt <me@liz.coffee>2025-06-20 14:53:38 -0700
commitd4791f3d357634daf506fb8f91cc5332a794c421 (patch)
tree1bb01d2d4d8fa74d83bb6f99f2c8aa4146ca2d11
parentd7e8d31c94cd713a2f4cf799e20e993acc69e361 (diff)
downloadci-d4791f3d357634daf506fb8f91cc5332a794c421.tar.gz
ci-d4791f3d357634daf506fb8f91cc5332a794c421.zip
Move to nodejs
-rw-r--r--.ci/ci.json2
-rw-r--r--.ci/ci.ts139
-rw-r--r--.eslintrc.js35
-rw-r--r--.gitignore6
-rw-r--r--.prettierignore72
-rw-r--r--.prettierrc29
-rw-r--r--.zed/settings.json36
-rw-r--r--Dockerfile6
-rw-r--r--deno.json4
-rwxr-xr-xindex.ts37
-rw-r--r--mise.toml2
-rwxr-xr-xmod.ts35
-rw-r--r--model/deno.json5
-rw-r--r--model/index.ts2
-rw-r--r--model/job.ts67
-rw-r--r--model/job/index.ts11
-rw-r--r--model/job/jobs.ts52
-rw-r--r--model/mod.ts2
-rw-r--r--model/package.json27
-rw-r--r--model/pipeline.ts89
-rw-r--r--model/pipeline/builder.ts53
-rw-r--r--model/pipeline/impl.ts20
-rw-r--r--model/pipeline/index.ts19
-rw-r--r--model/tsconfig.json15
-rw-r--r--package-lock.json1966
-rw-r--r--package.json38
-rw-r--r--server/Dockerfile2
-rw-r--r--server/ci.ts89
-rw-r--r--server/deno.json4
-rw-r--r--server/health.ts48
-rw-r--r--server/index.ts31
-rw-r--r--server/job.ts185
-rw-r--r--server/job/index.ts2
-rw-r--r--server/job/queue.ts74
-rw-r--r--server/job/run_activity.ts94
-rw-r--r--server/mod.ts22
-rw-r--r--server/package.json33
-rw-r--r--server/tsconfig.json15
-rw-r--r--tsconfig.json29
-rw-r--r--u/deno.json5
-rw-r--r--u/fn/callable.ts8
-rw-r--r--u/fn/either.ts180
-rw-r--r--u/fn/index.ts2
-rw-r--r--u/fn/mod.ts2
-rw-r--r--u/index.ts6
-rw-r--r--u/leftpadesque/debug.ts15
-rw-r--r--u/leftpadesque/index.ts4
-rw-r--r--u/leftpadesque/memoize.ts22
-rw-r--r--u/leftpadesque/mod.ts4
-rw-r--r--u/leftpadesque/object.ts3
-rw-r--r--u/leftpadesque/prepend.ts8
-rw-r--r--u/mod.ts6
-rw-r--r--u/package.json47
-rw-r--r--u/process/argv.ts77
-rw-r--r--u/process/env.ts53
-rw-r--r--u/process/index.ts4
-rw-r--r--u/process/mod.ts4
-rw-r--r--u/process/run.ts102
-rw-r--r--u/process/validate_identifier.ts23
-rw-r--r--u/server/activity/fourohfour.ts37
-rw-r--r--u/server/activity/health.ts108
-rw-r--r--u/server/activity/index.ts8
-rw-r--r--u/server/activity/mod.ts13
-rw-r--r--u/server/filter/index.ts34
-rw-r--r--u/server/filter/json.ts92
-rw-r--r--u/server/filter/method.ts67
-rw-r--r--u/server/filter/mod.ts35
-rw-r--r--u/server/index.ts7
-rw-r--r--u/server/mod.ts7
-rw-r--r--u/server/request.ts74
-rw-r--r--u/server/response.ts139
-rw-r--r--u/trace/index.ts5
-rw-r--r--u/trace/itrace.ts120
-rw-r--r--u/trace/logger.ts169
-rw-r--r--u/trace/metrics.ts229
-rw-r--r--u/trace/mod.ts5
-rw-r--r--u/trace/trace.ts117
-rw-r--r--u/trace/util.ts76
-rw-r--r--u/tsconfig.json15
-rw-r--r--worker/deno.json4
-rw-r--r--worker/executor.ts161
-rw-r--r--worker/index.ts2
-rw-r--r--worker/mod.ts2
-rw-r--r--worker/package.json28
-rwxr-xr-xworker/scripts/ansible_playbook.ts193
-rwxr-xr-xworker/scripts/build_docker_image.ts259
-rwxr-xr-xworker/scripts/checkout_ci.ts294
-rw-r--r--worker/secret.ts286
-rw-r--r--worker/tsconfig.json15
89 files changed, 4309 insertions, 2264 deletions
diff --git a/.ci/ci.json b/.ci/ci.json
index dc29f8d..e10df80 100644
--- a/.ci/ci.json
+++ b/.ci/ci.json
@@ -1,3 +1,3 @@
{
- "pipeline": ".ci/ci.ts"
+ "workflow": ".ci/ci.js"
}
diff --git a/.ci/ci.ts b/.ci/ci.ts
index 89bad35..7f06b26 100644
--- a/.ci/ci.ts
+++ b/.ci/ci.ts
@@ -1,84 +1,83 @@
-#!/usr/bin/env -S deno run --allow-env
+#!/usr/bin/env ts-node
import {
- AnsiblePlaybookJob,
- BuildDockerImageJob,
- DefaultGitHookPipelineBuilder,
- FetchCodeJob,
-} from "@emprespresso/ci_model";
+ AnsiblePlaybookJob,
+ BuildDockerImageJob,
+ DefaultGitHookPipelineBuilder,
+ FetchCodeJob,
+} from '../model/index.js';
-const REGISTRY = "oci.liz.coffee";
-const NAMESPACE = "emprespresso";
-const IMG = "ci";
-const REMOTE = "ssh://src.liz.coffee:2222";
+const REGISTRY = 'oci.liz.coffee';
+const NAMESPACE = 'emprespresso';
+const IMG = 'ci';
+const REMOTE = 'ssh://src.liz.coffee:2222';
const getPipeline = () => {
- const gitHookPipeline = new DefaultGitHookPipelineBuilder();
- const branch = gitHookPipeline.getBranch();
- if (!branch) return gitHookPipeline.build();
+ const gitHookPipeline = new DefaultGitHookPipelineBuilder();
+ const branch = gitHookPipeline.getBranch();
+ if (!branch) return gitHookPipeline.build();
- const commonBuildArgs = {
- registry: REGISTRY,
- namespace: NAMESPACE,
- imageTag: branch,
- };
+ const commonBuildArgs = {
+ registry: REGISTRY,
+ namespace: NAMESPACE,
+ imageTag: branch,
+ };
- const baseCiPackageBuild: BuildDockerImageJob = {
- type: "build_docker_image.ts",
- arguments: {
- ...commonBuildArgs,
- context: gitHookPipeline.getSourceDestination(),
- repository: IMG + "_base",
- buildTarget: IMG + "_base",
- dockerfile: "Dockerfile",
- },
- };
- gitHookPipeline.addStage({
- parallelJobs: [baseCiPackageBuild],
- });
+ const baseCiPackageBuild: BuildDockerImageJob = {
+ type: 'build_docker_image.ts',
+ arguments: {
+ ...commonBuildArgs,
+ context: gitHookPipeline.getSourceDestination(),
+ repository: IMG + '_base',
+ buildTarget: IMG + '_base',
+ dockerfile: 'Dockerfile',
+ },
+ };
+ gitHookPipeline.addStage({
+ parallelJobs: [baseCiPackageBuild],
+ });
- const subPackages = ["worker", "hooks"].map((_package) => ({
- type: "build_docker_image.ts",
- arguments: {
- ...commonBuildArgs,
- repository: `${IMG}_${_package}`,
- buildTarget: _package,
- dockerfile: `${_package}/Dockerfile`,
- },
- }));
- gitHookPipeline.addStage({
- parallelJobs: subPackages,
- });
+ const subPackages = ['worker', 'hooks'].map((_package) => ({
+ type: 'build_docker_image.ts',
+ arguments: {
+ ...commonBuildArgs,
+ repository: `${IMG}_${_package}`,
+ buildTarget: _package,
+ dockerfile: `${_package}/Dockerfile`,
+ },
+ }));
+ gitHookPipeline.addStage({
+ parallelJobs: subPackages,
+ });
- const isRelease = branch === "release";
- if (!isRelease) {
- return gitHookPipeline.build();
- }
+ const isRelease = branch === 'release';
+ if (!isRelease) {
+ return gitHookPipeline.build();
+ }
- const fetchAnsibleCode: FetchCodeJob = {
- type: "fetch_code.ts",
- arguments: {
- remoteUrl: `${REMOTE}/infra`,
- checkout: "main",
- path: "infra",
- },
- };
- const thenDeploy: AnsiblePlaybookJob = {
- type: "ansible_playbook.ts",
- arguments: {
- path: "infra",
- playbooks: "playbooks/ci.yml",
- },
- };
- [fetchAnsibleCode, thenDeploy].forEach((deploymentStage) =>
- gitHookPipeline.addStage({ parallelJobs: [deploymentStage] }),
- );
+ const fetchAnsibleCode: FetchCodeJob = {
+ type: 'fetch_code.ts',
+ arguments: {
+ remoteUrl: `${REMOTE}/infra`,
+ checkout: 'main',
+ path: 'infra',
+ },
+ };
+ const thenDeploy: AnsiblePlaybookJob = {
+ type: 'ansible_playbook.ts',
+ arguments: {
+ path: 'infra',
+ playbooks: 'playbooks/ci.yml',
+ },
+ };
+ [fetchAnsibleCode, thenDeploy].forEach((deploymentStage) =>
+ gitHookPipeline.addStage({ parallelJobs: [deploymentStage] }),
+ );
- return gitHookPipeline.build();
+ return gitHookPipeline.build();
};
-if (import.meta.main) {
- const encoder = new TextEncoder();
- const data = encoder.encode(getPipeline().serialize());
- await Deno.stdout.write(data);
+if (import.meta.url === `file://${process.argv[1]}`) {
+ const data = getPipeline().serialize();
+ process.stdout.write(data);
}
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..a4878e3
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,35 @@
+module.exports = {
+ root: true,
+ parser: '@typescript-eslint/parser',
+ plugins: ['@typescript-eslint', 'prettier'],
+ extends: [
+ 'eslint:recommended',
+ '@typescript-eslint/recommended',
+ '@typescript-eslint/recommended-requiring-type-checking',
+ 'prettier',
+ 'plugin:prettier/recommended',
+ ],
+ parserOptions: {
+ ecmaVersion: 2022,
+ sourceType: 'module',
+ project: './tsconfig.json',
+ },
+ env: {
+ node: true,
+ es2022: true,
+ },
+ rules: {
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
+ '@typescript-eslint/no-explicit-any': 'warn',
+ '@typescript-eslint/explicit-function-return-type': 'off',
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
+ '@typescript-eslint/no-non-null-assertion': 'warn',
+ '@typescript-eslint/prefer-nullish-coalescing': 'error',
+ '@typescript-eslint/prefer-optional-chain': 'error',
+ '@typescript-eslint/no-floating-promises': 'error',
+ 'no-console': 'warn',
+ 'prefer-const': 'error',
+ 'prettier/prettier': 'error',
+ },
+ ignorePatterns: ['dist/', 'node_modules/', '*.js', '*.d.ts'],
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c4d9f6d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+**/.env
+**/node_modules
+**/dist
+
+.DS_Store
+Thumbs.db
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..23da47f
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,72 @@
+# Dependencies
+node_modules/
+package-lock.json
+yarn.lock
+pnpm-lock.yaml
+
+# Build outputs
+dist/
+build/
+.next/
+.nuxt/
+.vuepress/dist/
+.serverless/
+.vercel/
+
+# Logs
+*.log
+logs/
+
+# Runtime data
+pids/
+*.pid
+*.seed
+*.pid.lock
+
+# Coverage directory used by tools like istanbul
+coverage/
+*.lcov
+
+# Generated files
+*.d.ts
+*.tsbuildinfo
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# Temporary files
+*.tmp
+*.temp
+
+# Docker
+Dockerfile*
+.dockerignore
+
+# Git
+.git/
+.gitignore
+
+# CI/CD
+.github/
+.gitlab-ci.yml
+
+# Config files that might have specific formatting
+.env*
+*.conf
+*.config.js
+*.config.ts
+
+# Markdown files that might be auto-generated
+CHANGELOG.md \ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..70bc078
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,29 @@
+{
+ "semi": true,
+ "trailingComma": "all",
+ "singleQuote": true,
+ "printWidth": 120,
+ "tabWidth": 4,
+ "useTabs": false,
+ "bracketSpacing": true,
+ "bracketSameLine": false,
+ "arrowParens": "always",
+ "endOfLine": "lf",
+ "quoteProps": "as-needed",
+ "jsxSingleQuote": true,
+ "overrides": [
+ {
+ "files": "*.json",
+ "options": {
+ "printWidth": 120
+ }
+ },
+ {
+ "files": "*.md",
+ "options": {
+ "printWidth": 100,
+ "proseWrap": "preserve"
+ }
+ }
+ ]
+}
diff --git a/.zed/settings.json b/.zed/settings.json
index 78b2d5d..77c7e9a 100644
--- a/.zed/settings.json
+++ b/.zed/settings.json
@@ -1,22 +1,24 @@
{
- "formatter": "prettier",
- "lsp": {
- "deno": {
- "settings": {
- "deno": {
- "enable": true
+ "formatter": "prettier",
+ "lsp": {
+ "typescript-language-server": {
+ "settings": {
+ "typescript": {
+ "preferences": {
+ "includePackageJsonAutoImports": "on"
+ }
+ }
+ }
}
- }
- }
- },
- "languages": {
- "TypeScript": {
- "language_servers": ["deno", "!typescript-language-server"],
- "formatter": "prettier"
},
- "TSX": {
- "language_servers": ["deno", "!typescript-language-server"],
- "formatter": "prettier"
+ "languages": {
+ "TypeScript": {
+ "language_servers": ["typescript-language-server"],
+ "formatter": "prettier"
+ },
+ "TSX": {
+ "language_servers": ["typescript-language-server"],
+ "formatter": "prettier"
+ }
}
- }
}
diff --git a/Dockerfile b/Dockerfile
index 33c6062..5c87c65 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,7 +25,7 @@ RUN cmake -B /opt/laminar/build -S /opt/laminar/src -G Ninja \
# -- </laminar_bin> --
# -- <ci_base> --
-FROM denoland/deno:debian AS ci_base
+FROM node:22-slim AS ci_base
RUN apt-get update -yqq && apt-get install -yqq libcapnp-0.9.2 \
libsqlite3-0 zlib1g curl bash
@@ -38,7 +38,11 @@ COPY --from=laminar_bin /usr/share/bash-completion/completions/laminarc /usr/sha
COPY --from=laminar_bin /usr/share/zsh/site-functions/_laminarc /usr/share/zsh/site-functions/_laminarc
WORKDIR /app
+COPY package*.json ./
+RUN npm ci --only=production
+
COPY . /app
+RUN npm run build
ENTRYPOINT [ "/bin/bash", "-c" ]
# -- </ci_base> --
diff --git a/deno.json b/deno.json
deleted file mode 100644
index adeae7b..0000000
--- a/deno.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "package": "@emprespresso/ci",
- "workspace": ["./*"]
-}
diff --git a/index.ts b/index.ts
new file mode 100755
index 0000000..a9defca
--- /dev/null
+++ b/index.ts
@@ -0,0 +1,37 @@
+#!/usr/bin/env node
+
+import { argv, IEither, Either } from '@emprespresso/pengueno';
+import { runServer } from '@emprespresso/ci_server';
+
+const main = (_argv = process.argv.slice(2)): Promise<IEither<Error, 0>> =>
+ argv(
+ ['--run-server', '--port', '--host'],
+ {
+ '--run-server': { absent: false, unspecified: true, present: () => true },
+ '--port': { absent: 9000, present: (port) => parseInt(port) },
+ '--host': { absent: '0.0.0.0', present: (host) => host },
+ },
+ _argv,
+ )
+ .mapRight((args) => ({
+ server_mode: args['--run-server'],
+ port: args['--port'],
+ host: args['--host'],
+ }))
+ .flatMapAsync((runConfig) => {
+ if (runConfig.server_mode) {
+ return runServer(runConfig.port, runConfig.host);
+ }
+ return Promise.resolve(Either.right(0));
+ });
+
+if (process.argv[1] === import.meta.filename) {
+ await main().then((eitherDone) =>
+ eitherDone.fold(({ isLeft, value }) => {
+ if (!isLeft) return;
+
+ console.error(`failed to start`, value);
+ process.exit(1);
+ }),
+ );
+}
diff --git a/mise.toml b/mise.toml
index ac7a209..24429f1 100644
--- a/mise.toml
+++ b/mise.toml
@@ -1,2 +1,2 @@
[tools]
-deno = "latest"
+node = "22.16.0"
diff --git a/mod.ts b/mod.ts
deleted file mode 100755
index b43fff3..0000000
--- a/mod.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run
-
-import { argv, IEither, Either } from "@emprespresso/pengueno";
-import { runServer } from "@emprespresso/ci_server";
-
-const main = (
- _argv = Deno.args,
-): Promise<IEither<Error, 0>> =>
- argv(["--run-server", "--port", "--host"], {
- "--run-server": { absent: false, unspecified: true, present: () => true },
- "--port": { absent: 9000, present: (port) => parseInt(port) },
- "--host": { absent: "0.0.0.0", present: (host) => host },
- }, _argv)
- .mapRight((args) => ({
- server_mode: args["--run-server"],
- port: args["--port"],
- host: args["--host"],
- }))
- .flatMapAsync((runConfig) => {
- if (runConfig.server_mode) {
- return runServer(runConfig.port, runConfig.host);
- }
- return Promise.resolve(Either.right(0));
- });
-
-if (import.meta.main) {
- await main().then((eitherDone) =>
- eitherDone.fold(({ isLeft, value }) => {
- if (!isLeft) return;
-
- console.error(`Failed to start`, value);
- Deno.exit(1);
- }),
- );
-}
diff --git a/model/deno.json b/model/deno.json
deleted file mode 100644
index 5f5dacf..0000000
--- a/model/deno.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "@emprespresso/ci_model",
- "version": "0.1.0",
- "exports": "./mod.ts"
-}
diff --git a/model/index.ts b/model/index.ts
new file mode 100644
index 0000000..094c693
--- /dev/null
+++ b/model/index.ts
@@ -0,0 +1,2 @@
+export * from './job';
+export * from './pipeline';
diff --git a/model/job.ts b/model/job.ts
deleted file mode 100644
index 187ed56..0000000
--- a/model/job.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { isObject } from "@emprespresso/pengueno";
-
-export type JobArgT = Record<string, string>;
-export interface Job {
- readonly type: string;
- readonly arguments: JobArgT;
-}
-export const isJob = (j: unknown): j is Job =>
- !!(
- isObject(j) &&
- "arguments" in j &&
- isObject(j.arguments) &&
- "type" in j &&
- typeof j.type === "string" &&
- j
- );
-
-export interface FetchCodeJobProps extends JobArgT {
- readonly remoteUrl: string;
- readonly checkout: string;
- readonly path: string;
-}
-
-export interface FetchCodeJob {
- readonly type: "fetch_code.ts";
- readonly arguments: FetchCodeJobProps;
-}
-
-export interface BuildDockerImageJobProps extends JobArgT {
- readonly registry: string;
- readonly namespace: string;
- readonly repository: string;
- readonly imageTag: string;
-
- readonly context: string;
- readonly dockerfile: string;
- readonly buildTarget: string;
-}
-
-export interface BuildDockerImageJob extends Job {
- readonly type: "build_docker_image.ts";
- readonly arguments: BuildDockerImageJobProps;
-}
-
-export interface AnsiblePlaybookJobProps extends JobArgT {
- readonly path: string;
- readonly playbooks: string;
-}
-
-export interface AnsiblePlaybookJob extends Job {
- readonly type: "ansible_playbook.ts";
- readonly arguments: AnsiblePlaybookJobProps;
-}
-
-export interface CheckoutCiJobProps extends JobArgT {
- readonly remote: string;
- readonly refname: string;
- readonly rev: string;
-
- readonly run: string;
- readonly returnPath: string;
-}
-
-export interface CheckoutCiJob extends Job {
- readonly type: "checkout_ci.ts";
- readonly arguments: CheckoutCiJobProps;
-}
diff --git a/model/job/index.ts b/model/job/index.ts
new file mode 100644
index 0000000..78f69d6
--- /dev/null
+++ b/model/job/index.ts
@@ -0,0 +1,11 @@
+import { isObject } from '@emprespresso/pengueno';
+
+export type JobArgT = Record<string, string>;
+export interface Job {
+ readonly type: string;
+ readonly arguments: JobArgT;
+}
+export const isJob = (j: unknown): j is Job =>
+ !!(isObject(j) && 'arguments' in j && isObject(j.arguments) && 'type' in j && typeof j.type === 'string' && j);
+
+export * from './jobs';
diff --git a/model/job/jobs.ts b/model/job/jobs.ts
new file mode 100644
index 0000000..e201c4d
--- /dev/null
+++ b/model/job/jobs.ts
@@ -0,0 +1,52 @@
+import { Job, JobArgT } from '.';
+
+export interface FetchCodeJobProps extends JobArgT {
+ readonly remoteUrl: string;
+ readonly checkout: string;
+ readonly path: string;
+}
+
+export interface FetchCodeJob {
+ readonly type: 'fetch_code.ts';
+ readonly arguments: FetchCodeJobProps;
+}
+
+export interface BuildDockerImageJobProps extends JobArgT {
+ readonly registry: string;
+ readonly namespace: string;
+ readonly repository: string;
+ readonly imageTag: string;
+
+ readonly context: string;
+ readonly dockerfile: string;
+ readonly buildTarget: string;
+}
+
+export interface BuildDockerImageJob extends Job {
+ readonly type: 'build_docker_image.ts';
+ readonly arguments: BuildDockerImageJobProps;
+}
+
+export interface AnsiblePlaybookJobProps extends JobArgT {
+ readonly path: string;
+ readonly playbooks: string;
+}
+
+export interface AnsiblePlaybookJob extends Job {
+ readonly type: 'ansible_playbook.ts';
+ readonly arguments: AnsiblePlaybookJobProps;
+}
+
+export interface CheckoutCiJobProps extends JobArgT {
+ readonly remote: string;
+ readonly refname: string;
+ readonly rev: string;
+
+ readonly run: string;
+ readonly returnPath: string;
+}
+
+export interface CheckoutCiJob extends Job {
+ readonly type: 'checkout_ci.ts';
+ readonly arguments: CheckoutCiJobProps;
+}
diff --git a/model/mod.ts b/model/mod.ts
deleted file mode 100644
index 944ab7d..0000000
--- a/model/mod.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./job.ts";
-export * from "./pipeline.ts";
diff --git a/model/package.json b/model/package.json
new file mode 100644
index 0000000..23cfd52
--- /dev/null
+++ b/model/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "@emprespresso/ci_model",
+ "version": "0.1.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ },
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "clean": "rm -rf dist",
+ "type-check": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@emprespresso/pengueno": "*"
+ },
+ "files": [
+ "dist/**/*",
+ "package.json",
+ "README.md"
+ ]
+}
diff --git a/model/pipeline.ts b/model/pipeline.ts
deleted file mode 100644
index edc8337..0000000
--- a/model/pipeline.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { Either, type IEither, isObject } from "@emprespresso/pengueno";
-import { type FetchCodeJob, isJob, type Job } from "@emprespresso/ci_model";
-
-export interface PipelineStage {
- readonly parallelJobs: Array<Job>;
-}
-export const isPipelineStage = (t: unknown): t is PipelineStage =>
- isObject(t) &&
- "parallelJobs" in t &&
- Array.isArray(t.parallelJobs) &&
- t.parallelJobs.every((j) => isJob(j));
-
-export interface Pipeline {
- readonly serialJobs: Array<PipelineStage>;
- serialize(): string;
-}
-export const isPipeline = (t: unknown): t is Pipeline =>
- isObject(t) &&
- "serialJobs" in t &&
- Array.isArray(t.serialJobs) &&
- t.serialJobs.every((p) => isPipelineStage(p));
-
-export interface PipelineBuilder {
- addStage(stage: PipelineStage): PipelineBuilder;
- build(): Pipeline;
-}
-
-export class PipelineImpl implements Pipeline {
- constructor(public readonly serialJobs: Array<PipelineStage>) {}
-
- public serialize() {
- return JSON.stringify(this.serialJobs);
- }
-
- public static from(s: string): IEither<Error, Pipeline> {
- return Either.fromFailable<Error, unknown>(() => JSON.parse(s))
- .flatMap<Pipeline>((eitherPipelineJson) =>
- isPipeline(eitherPipelineJson)
- ? Either.right(eitherPipelineJson)
- : Either.left(new Error("oh noes D: its a bad pipewine :((")),
- )
- .mapRight((pipeline) => new PipelineImpl(pipeline.serialJobs));
- }
-}
-
-abstract class BasePipelineBuilder implements PipelineBuilder {
- protected readonly stages: Array<PipelineStage> = [];
-
- public addStage(stage: PipelineStage): PipelineBuilder {
- this.stages.push(stage);
- return this;
- }
-
- public build() {
- return new PipelineImpl(this.stages);
- }
-}
-
-export class DefaultGitHookPipelineBuilder extends BasePipelineBuilder {
- constructor(
- private readonly remoteUrl = Deno.env.get("remote")!,
- rev = Deno.env.get("rev")!,
- private readonly ref = Deno.env.get("ref")!,
- ) {
- super();
-
- this.addStage({
- parallelJobs: [
- <FetchCodeJob>{
- type: "fetch_code",
- arguments: {
- remoteUrl,
- checkout: rev,
- path: this.getSourceDestination(),
- },
- },
- ],
- });
- }
-
- public getSourceDestination() {
- return this.remoteUrl.split("/").at(-1) ?? "src";
- }
-
- public getBranch(): string | undefined {
- const branchRefPrefix = "refs/heads/";
- return this.ref.split(branchRefPrefix).at(1);
- }
-}
diff --git a/model/pipeline/builder.ts b/model/pipeline/builder.ts
new file mode 100644
index 0000000..e95e89c
--- /dev/null
+++ b/model/pipeline/builder.ts
@@ -0,0 +1,53 @@
+import { Pipeline, PipelineStage } from '.';
+import { FetchCodeJob } from '../job';
+import { PipelineImpl } from './impl';
+
+export interface PipelineBuilder {
+ addStage(stage: PipelineStage): PipelineBuilder;
+ build(): Pipeline;
+}
+
+export abstract class BasePipelineBuilder implements PipelineBuilder {
+ protected readonly stages: Array<PipelineStage> = [];
+
+ public addStage(stage: PipelineStage): PipelineBuilder {
+ this.stages.push(stage);
+ return this;
+ }
+
+ public build() {
+ return new PipelineImpl(this.stages);
+ }
+}
+
+export class DefaultGitHookPipelineBuilder extends BasePipelineBuilder {
+ constructor(
+ private readonly remoteUrl = process.env.remote!,
+ rev = process.env.rev!,
+ private readonly ref = process.env.ref!,
+ ) {
+ super();
+
+ this.addStage({
+ parallelJobs: [
+ <FetchCodeJob>{
+ type: 'fetch_code.ts',
+ arguments: {
+ remoteUrl,
+ checkout: rev,
+ path: this.getSourceDestination(),
+ },
+ },
+ ],
+ });
+ }
+
+ public getSourceDestination() {
+ return this.remoteUrl.split('/').at(-1) ?? 'src';
+ }
+
+ public getBranch(): string | undefined {
+ const branchRefPrefix = 'refs/heads/';
+ return this.ref.split(branchRefPrefix).at(1);
+ }
+}
diff --git a/model/pipeline/impl.ts b/model/pipeline/impl.ts
new file mode 100644
index 0000000..2e08d6e
--- /dev/null
+++ b/model/pipeline/impl.ts
@@ -0,0 +1,20 @@
+import { Either, IEither } from '@emprespresso/pengueno';
+import { isPipeline, Pipeline, PipelineStage } from '.';
+
+export class PipelineImpl implements Pipeline {
+ constructor(public readonly serialJobs: Array<PipelineStage>) {}
+
+ public serialize() {
+ return JSON.stringify(this.serialJobs);
+ }
+
+ public static from(s: string): IEither<Error, Pipeline> {
+ return Either.fromFailable<Error, unknown>(() => JSON.parse(s))
+ .flatMap<Pipeline>((eitherPipelineJson) =>
+ isPipeline(eitherPipelineJson)
+ ? Either.right(eitherPipelineJson)
+ : Either.left(new Error('oh noes D: its a bad pipewine :((')),
+ )
+ .mapRight((pipeline) => new PipelineImpl(pipeline.serialJobs));
+ }
+}
diff --git a/model/pipeline/index.ts b/model/pipeline/index.ts
new file mode 100644
index 0000000..adf902b
--- /dev/null
+++ b/model/pipeline/index.ts
@@ -0,0 +1,19 @@
+import { isObject } from '@emprespresso/pengueno';
+import { isJob, Job } from '../job';
+
+export interface PipelineStage {
+ readonly parallelJobs: Array<Job>;
+}
+export const isPipelineStage = (t: unknown): t is PipelineStage =>
+ isObject(t) && 'parallelJobs' in t && Array.isArray(t.parallelJobs) && t.parallelJobs.every((j) => isJob(j));
+
+export const isPipeline = (t: unknown): t is Pipeline =>
+ isObject(t) && 'serialJobs' in t && Array.isArray(t.serialJobs) && t.serialJobs.every((p) => isPipelineStage(p));
+
+export interface Pipeline {
+ readonly serialJobs: Array<PipelineStage>;
+ serialize(): string;
+}
+
+export * from './builder';
+export * from './impl';
diff --git a/model/tsconfig.json b/model/tsconfig.json
new file mode 100644
index 0000000..7ad21ad
--- /dev/null
+++ b/model/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./",
+ "composite": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "noEmit": false
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
+ "references": [{ "path": "../u" }]
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..d6b31c9
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1966 @@
+{
+ "name": "@emprespresso/ci",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@emprespresso/ci",
+ "version": "0.1.0",
+ "workspaces": [
+ "u",
+ "model",
+ "server",
+ "worker",
+ ".ci"
+ ],
+ "devDependencies": {
+ "@types/node": "^24.0.3",
+ "@typescript-eslint/eslint-plugin": "^8.34.1",
+ "@typescript-eslint/parser": "^8.34.1",
+ "eslint": "^8.34.1",
+ "eslint-config-prettier": "^10.1.5",
+ "eslint-plugin-prettier": "^5.5.0",
+ "prettier": "^3.5.3",
+ "typescript": "^5.8.3"
+ },
+ "engines": {
+ "node": ">=22.16.0",
+ "npm": ">=10.0.0"
+ }
+ },
+ "model": {
+ "name": "@emprespresso/ci_model",
+ "version": "0.1.0",
+ "dependencies": {
+ "@emprespresso/pengueno": "*"
+ }
+ },
+ "node_modules/@emprespresso/ci_model": {
+ "resolved": "model",
+ "link": true
+ },
+ "node_modules/@emprespresso/ci_server": {
+ "resolved": "server",
+ "link": true
+ },
+ "node_modules/@emprespresso/ci_worker": {
+ "resolved": "worker",
+ "link": true
+ },
+ "node_modules/@emprespresso/pengueno": {
+ "resolved": "u",
+ "link": true
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.14.4",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.14.4.tgz",
+ "integrity": "sha512-DnxpshhYewr2q9ZN8ez/M5mmc3sucr8CT1sIgIy1bkeUXut9XWDkqHoFHRhWIQgkYnKpVRxunyhK7WzpJeJ6qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgr/core": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
+ "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "24.0.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz",
+ "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
+ "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.34.1",
+ "@typescript-eslint/type-utils": "8.34.1",
+ "@typescript-eslint/utils": "8.34.1",
+ "@typescript-eslint/visitor-keys": "8.34.1",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.34.1",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
+ "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.34.1",
+ "@typescript-eslint/types": "8.34.1",
+ "@typescript-eslint/typescript-estree": "8.34.1",
+ "@typescript-eslint/visitor-keys": "8.34.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
+ "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.34.1",
+ "@typescript-eslint/types": "^8.34.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
+ "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.34.1",
+ "@typescript-eslint/visitor-keys": "8.34.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
+ "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
+ "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "8.34.1",
+ "@typescript-eslint/utils": "8.34.1",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
+ "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
+ "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.34.1",
+ "@typescript-eslint/tsconfig-utils": "8.34.1",
+ "@typescript-eslint/types": "8.34.1",
+ "@typescript-eslint/visitor-keys": "8.34.1",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
+ "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.34.1",
+ "@typescript-eslint/types": "8.34.1",
+ "@typescript-eslint/typescript-estree": "8.34.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.34.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
+ "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.34.1",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.5",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
+ "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.0.tgz",
+ "integrity": "sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.11.7"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.8.1.tgz",
+ "integrity": "sha512-ErA2ifywnSmcnB5XDuFqGDfXJ9xuAJR2C/8cZAk6vDaOCzofB8eNlha/wZWIiamREzWk94S9Z7wHsnKQHn7Niw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
+ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/synckit": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
+ "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.4"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
+ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "server": {
+ "name": "@emprespresso/ci_server",
+ "version": "0.1.0",
+ "dependencies": {
+ "@emprespresso/ci_model": "*",
+ "@emprespresso/pengueno": "*",
+ "@hono/node-server": "^1.14.0",
+ "hono": "^4.8.0"
+ }
+ },
+ "u": {
+ "name": "@emprespresso/pengueno",
+ "version": "0.1.0",
+ "dependencies": {
+ "hono": "^4.8.0"
+ }
+ },
+ "worker": {
+ "name": "@emprespresso/ci_worker",
+ "version": "0.1.0",
+ "dependencies": {
+ "@emprespresso/ci_model": "*",
+ "@emprespresso/pengueno": "*"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8107bdf
--- /dev/null
+++ b/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "@emprespresso/ci",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "workspaces": [
+ "u",
+ "model",
+ "server",
+ "worker",
+ ".ci"
+ ],
+ "scripts": {
+ "build": "npm run build --workspaces --if-present && tsc",
+ "dev": "npm run dev --workspaces --if-present",
+ "lint": "eslint . --ext .ts,.js",
+ "lint:fix": "eslint . --ext .ts,.js --fix",
+ "format": "prettier --write .",
+ "format:check": "prettier --check .",
+ "type-check": "tsc --noEmit",
+ "clean": "npm run clean --workspaces --if-present && rm -rf dist",
+ "start": "node dist/index.js"
+ },
+ "devDependencies": {
+ "@types/node": "^24.0.3",
+ "@typescript-eslint/eslint-plugin": "^8.34.1",
+ "@typescript-eslint/parser": "^8.34.1",
+ "eslint": "^8.34.1",
+ "eslint-config-prettier": "^10.1.5",
+ "eslint-plugin-prettier": "^5.5.0",
+ "prettier": "^3.5.3",
+ "typescript": "^5.8.3"
+ },
+ "engines": {
+ "node": ">=22.16.0",
+ "npm": ">=10.0.0"
+ }
+}
diff --git a/server/Dockerfile b/server/Dockerfile
index b610907..1b9b0ed 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -1,5 +1,5 @@
# -- <ci_server> --
FROM oci.liz.coffee/emprespresso/ci_base:release AS server
-CMD [ "/app/mod.ts --run-server --port=9000" ]
+CMD [ "node", "/app/dist/index.js", "--run-server", "--port=9000" ]
# -- </ci_server> --
diff --git a/server/ci.ts b/server/ci.ts
index f8d4a17..f57c426 100644
--- a/server/ci.ts
+++ b/server/ci.ts
@@ -1,56 +1,47 @@
import {
- FourOhFourActivityImpl,
- getRequiredEnv,
- HealthCheckActivityImpl,
- type HealthChecker,
- type IFourOhFourActivity,
- type IHealthCheckActivity,
- type ITraceable,
- PenguenoRequest,
- type ServerTrace,
- TraceUtil,
-} from "@emprespresso/pengueno";
-import type { Job } from "@emprespresso/ci_model";
-import {
- healthCheck as _healthCheck,
- type IJobHookActivity,
- type IJobQueuer,
- JobHookActivityImpl,
- LaminarJobQueuer,
-} from "@emprespresso/ci_server";
+ FourOhFourActivityImpl,
+ getRequiredEnv,
+ HealthCheckActivityImpl,
+ type HealthChecker,
+ type IFourOhFourActivity,
+ type IHealthCheckActivity,
+ type ITraceable,
+ PenguenoRequest,
+ type ServerTrace,
+ TraceUtil,
+} from '@emprespresso/pengueno';
+import type { Job } from '@emprespresso/ci_model';
+import { type IJobHookActivity, type IJobQueuer, JobHookActivityImpl, LaminarJobQueuer } from './job';
+import { healthCheck as _healthCheck } from '.';
+
+export const DEFAULT_CI_SERVER = 'https://ci.liz.coffee';
export class CiHookServer {
- constructor(
- healthCheck: HealthChecker = _healthCheck,
- jobQueuer: IJobQueuer<ITraceable<Job, ServerTrace>> = new LaminarJobQueuer(
- getRequiredEnv("LAMINAR_URL").fold(({ isLeft, value }) =>
- isLeft ? "https://ci.liz.coffee" : value,
- ),
- ),
- private readonly healthCheckActivity: IHealthCheckActivity = new HealthCheckActivityImpl(
- healthCheck,
- ),
- private readonly jobHookActivity: IJobHookActivity = new JobHookActivityImpl(
- jobQueuer,
- ),
- private readonly fourOhFourActivity: IFourOhFourActivity = new FourOhFourActivityImpl(),
- ) {}
+ constructor(
+ healthCheck: HealthChecker = _healthCheck,
+ jobQueuer: IJobQueuer<ITraceable<Job, ServerTrace>> = new LaminarJobQueuer(
+ getRequiredEnv('LAMINAR_URL').fold(({ isLeft, value }) => (isLeft ? DEFAULT_CI_SERVER : value)),
+ ),
+ private readonly healthCheckActivity: IHealthCheckActivity = new HealthCheckActivityImpl(healthCheck),
+ private readonly jobHookActivity: IJobHookActivity = new JobHookActivityImpl(jobQueuer),
+ private readonly fourOhFourActivity: IFourOhFourActivity = new FourOhFourActivityImpl(),
+ ) {}
- private route(req: ITraceable<PenguenoRequest, ServerTrace>) {
- const url = new URL(req.get().url);
- if (url.pathname === "/health") {
- return this.healthCheckActivity.checkHealth(req);
+ private route(req: ITraceable<PenguenoRequest, ServerTrace>) {
+ const url = new URL(req.get().url);
+ if (url.pathname === '/health') {
+ return this.healthCheckActivity.checkHealth(req);
+ }
+ if (url.pathname === '/job') {
+ return this.jobHookActivity.processHook(req);
+ }
+ return this.fourOhFourActivity.fourOhFour(req);
}
- if (url.pathname === "/job") {
- return this.jobHookActivity.processHook(req);
- }
- return this.fourOhFourActivity.fourOhFour(req);
- }
- public serve(req: Request): Promise<Response> {
- return PenguenoRequest.from(req)
- .bimap(TraceUtil.withClassTrace(this))
- .map((req) => this.route(req))
- .get();
- }
+ public serve(req: Request): Promise<Response> {
+ return PenguenoRequest.from(req)
+ .bimap(TraceUtil.withClassTrace(this))
+ .map((req) => this.route(req))
+ .get();
+ }
}
diff --git a/server/deno.json b/server/deno.json
deleted file mode 100644
index c86c9a7..0000000
--- a/server/deno.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "@emprespresso/ci_server",
- "exports": "./mod.ts"
-}
diff --git a/server/health.ts b/server/health.ts
index e69077b..8435865 100644
--- a/server/health.ts
+++ b/server/health.ts
@@ -1,32 +1,24 @@
import {
- getRequiredEnv,
- getStdout,
- type HealthChecker,
- type HealthCheckInput,
- HealthCheckOutput,
- type IEither,
- type ITraceable,
- type ServerTrace,
- TraceUtil,
-} from "@emprespresso/pengueno";
+ getRequiredEnv,
+ getStdout,
+ type HealthChecker,
+ type HealthCheckInput,
+ HealthCheckOutput,
+ type IEither,
+ type ITraceable,
+ type ServerTrace,
+ TraceUtil,
+} from '@emprespresso/pengueno';
export const healthCheck: HealthChecker = (
- input: ITraceable<HealthCheckInput, ServerTrace>,
+ input: ITraceable<HealthCheckInput, ServerTrace>,
): Promise<IEither<Error, HealthCheckOutput>> =>
- input
- .bimap(TraceUtil.withFunctionTrace(healthCheck))
- .move(getRequiredEnv("LAMINAR_HOST"))
- // ensure LAMINAR_HOST is propagated to getStdout for other procedures
- .map((tEitherEnv) =>
- tEitherEnv
- .get()
- .flatMapAsync((_hasEnv) =>
- getStdout(tEitherEnv.move(["laminarc", "show-jobs"])),
- ),
- )
- .map(
- TraceUtil.promiseify((stdout) =>
- stdout.get().moveRight(HealthCheckOutput.YAASSSLAYQUEEN),
- ),
- )
- .get();
+ input
+ .bimap(TraceUtil.withFunctionTrace(healthCheck))
+ .move(getRequiredEnv('LAMINAR_HOST'))
+ // ensure LAMINAR_HOST is propagated to getStdout for other procedures
+ .map((tEitherEnv) =>
+ tEitherEnv.get().flatMapAsync((_hasEnv) => getStdout(tEitherEnv.move(['laminarc', 'show-jobs']))),
+ )
+ .map(TraceUtil.promiseify((stdout) => stdout.get().moveRight(HealthCheckOutput.YAASSSLAYQUEEN)))
+ .get();
diff --git a/server/index.ts b/server/index.ts
new file mode 100644
index 0000000..c33b43e
--- /dev/null
+++ b/server/index.ts
@@ -0,0 +1,31 @@
+#!/usr/bin/env node
+
+export * from './job';
+export * from './ci';
+export * from './health';
+
+import { CiHookServer } from '.';
+import { Either, type IEither } from '@emprespresso/pengueno';
+import { serve } from '@hono/node-server';
+import { Hono } from 'hono';
+
+const server = new CiHookServer();
+
+const neverEndingPromise = new Promise<IEither<Error, 0>>(() => {});
+export const runServer = (port: number, host: string): Promise<IEither<Error, 0>> =>
+ Either.fromFailable<Error, void>(() => {
+ const app = new Hono();
+
+ app.all('*', async (c) => {
+ const response = await server.serve(c.req.raw);
+ return response;
+ });
+
+ serve({
+ fetch: app.fetch,
+ port,
+ hostname: host,
+ });
+
+ console.log(`server running on http://${host}:${port} :D`);
+ }).flatMapAsync(() => neverEndingPromise);
diff --git a/server/job.ts b/server/job.ts
deleted file mode 100644
index 620a083..0000000
--- a/server/job.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import {
- getStdout,
- type Mapper,
- memoize,
- Either,
- ErrorSource,
- type IActivity,
- type IEither,
- type ITraceable,
- jsonModel,
- JsonResponse,
- LogLevel,
- Metric,
- PenguenoError,
- type PenguenoRequest,
- type ServerTrace,
- TraceUtil,
- validateExecutionEntries,
-} from "@emprespresso/pengueno";
-import { isJob, type Job } from "@emprespresso/ci_model";
-
-// -- <job.hook> --
-const wellFormedJobMetric = Metric.fromName("Job.WellFormed");
-
-const jobJsonTransformer = (
- j: ITraceable<unknown, ServerTrace>,
-): IEither<PenguenoError, Job> =>
- j
- .bimap(TraceUtil.withMetricTrace(wellFormedJobMetric))
- .map((tJson): IEither<PenguenoError, Job> => {
- const tJob = tJson.get();
- if (!isJob(tJob) || !validateExecutionEntries(tJob)) {
- const err = "seems like a pwetty mawfomed job (-.-)";
- tJson.trace.addTrace(LogLevel.WARN).trace(err);
- return Either.left(new PenguenoError(err, 400));
- }
- return Either.right(tJob);
- })
- .peek((tJob) =>
- tJob.trace.trace(
- tJob
- .get()
- .fold(({ isLeft }) =>
- isLeft ? wellFormedJobMetric.failure : wellFormedJobMetric.success,
- ),
- ),
- )
- .get();
-
-export interface IJobHookActivity {
- processHook: IActivity;
-}
-
-const jobHookRequestMetric = Metric.fromName("JobHook.process");
-export class JobHookActivityImpl implements IJobHookActivity {
- constructor(
- private readonly queuer: IJobQueuer<ITraceable<Job, ServerTrace>>,
- ) {}
-
- private trace(r: ITraceable<PenguenoRequest, ServerTrace>) {
- return r
- .bimap(TraceUtil.withClassTrace(this))
- .bimap(TraceUtil.withMetricTrace(jobHookRequestMetric));
- }
-
- public processHook(r: ITraceable<PenguenoRequest, ServerTrace>) {
- return this.trace(r)
- .map(jsonModel(jobJsonTransformer))
- .map(async (tEitherJobJson) => {
- const eitherJob = await tEitherJobJson.get();
- return eitherJob.flatMapAsync(async (job) => {
- const eitherQueued = await tEitherJobJson
- .move(job)
- .map((job) => this.queuer.queue(job))
- .get();
- return eitherQueued.mapLeft((e) => new PenguenoError(e.message, 500));
- });
- })
- .peek(
- TraceUtil.promiseify((tJob) =>
- tJob.get().fold(({ isRight, value }) => {
- if (isRight) {
- tJob.trace.trace(jobHookRequestMetric.success);
- tJob.trace.trace(`all queued up and weady to go :D !! ${value}`);
- return;
- }
-
- tJob.trace.trace(
- value.source === ErrorSource.SYSTEM
- ? jobHookRequestMetric.failure
- : jobHookRequestMetric.warn,
- );
- tJob.trace.addTrace(value.source).trace(`${value}`);
- }),
- ),
- )
- .map(
- TraceUtil.promiseify(
- (tEitherQueuedJob) =>
- new JsonResponse(r, tEitherQueuedJob.get(), {
- status: tEitherQueuedJob
- .get()
- .fold(({ isRight, value }) => (isRight ? 200 : value.status)),
- }),
- ),
- )
- .get();
- }
-}
-
-// -- </job.hook> --
-
-// -- <job.queuer> --
-type QueuePosition = string;
-export class QueueError extends Error {}
-export interface IJobQueuer<TJob> {
- queue: Mapper<TJob, Promise<IEither<QueueError, QueuePosition>>>;
-}
-
-export class LaminarJobQueuer
- implements IJobQueuer<ITraceable<Job, ServerTrace>>
-{
- constructor(private readonly queuePositionPrefix: string) {}
-
- private static GetJobTypeTrace = (jobType: string) =>
- `LaminarJobQueue.Queue.${jobType}`;
- private static JobTypeMetrics = memoize((jobType: string) =>
- Metric.fromName(LaminarJobQueuer.GetJobTypeTrace(jobType)),
- );
-
- public queue(j: ITraceable<Job, ServerTrace>) {
- const { type: jobType } = j.get();
- const trace = LaminarJobQueuer.GetJobTypeTrace(jobType);
- const metric = LaminarJobQueuer.JobTypeMetrics(jobType);
-
- return j
- .bimap(TraceUtil.withTrace(trace))
- .bimap(TraceUtil.withMetricTrace(metric))
- .map((j) => {
- const { type: jobType, arguments: args } = j.get();
- const laminarCommand = [
- "laminarc",
- "queue",
- jobType,
- ...Object.entries(args).map(([key, val]) => `"${key}"="${val}"`),
- ];
- return laminarCommand;
- })
- .peek((c) =>
- c.trace.trace(
- `im so excited to see how this queue job will end!! (>ᴗ<): ${c
- .get()
- .toString()}`,
- ),
- )
- .map(getStdout)
- .peek(
- TraceUtil.promiseify((q) =>
- q.trace.trace(
- q
- .get()
- .fold(({ isLeft }) => (isLeft ? metric.failure : metric.success)),
- ),
- ),
- )
- .map(
- TraceUtil.promiseify((q) =>
- q.get().fold(({ isLeft, value }) => {
- if (isLeft) {
- q.trace.addTrace(LogLevel.ERROR).trace(value.toString());
- return Either.left<Error, string>(value);
- }
- q.trace.addTrace(LogLevel.DEBUG).trace(`stdout ${value}`);
- const [jobName, jobId] = value.split(":");
- const jobUrl = `${this.queuePositionPrefix}/jobs/${jobName}/${jobId}`;
-
- q.trace.trace(`all queued up and weady to go~ (˘ω˘) => ${jobUrl}`);
- return Either.right<Error, string>(jobUrl);
- }),
- ),
- )
- .get();
- }
-}
-// -- </job.queuer> --
diff --git a/server/job/index.ts b/server/job/index.ts
new file mode 100644
index 0000000..ecf0984
--- /dev/null
+++ b/server/job/index.ts
@@ -0,0 +1,2 @@
+export * from './queue';
+export * from './run_activity';
diff --git a/server/job/queue.ts b/server/job/queue.ts
new file mode 100644
index 0000000..2392222
--- /dev/null
+++ b/server/job/queue.ts
@@ -0,0 +1,74 @@
+import {
+ getStdout,
+ type Mapper,
+ memoize,
+ Either,
+ type IEither,
+ type ITraceable,
+ LogLevel,
+ Metric,
+ type ServerTrace,
+ TraceUtil,
+} from '@emprespresso/pengueno';
+import { type Job } from '@emprespresso/ci_model';
+
+type QueuePosition = string;
+export class QueueError extends Error {}
+export interface IJobQueuer<TJob> {
+ queue: Mapper<TJob, Promise<IEither<QueueError, QueuePosition>>>;
+}
+
+export class LaminarJobQueuer implements IJobQueuer<ITraceable<Job, ServerTrace>> {
+ constructor(private readonly queuePositionPrefix: string) {}
+
+ private static GetJobTypeTrace = (jobType: string) => `LaminarJobQueue.Queue.${jobType}`;
+ private static JobTypeMetrics = memoize((jobType: string) =>
+ Metric.fromName(LaminarJobQueuer.GetJobTypeTrace(jobType)),
+ );
+
+ public queue(j: ITraceable<Job, ServerTrace>) {
+ const { type: jobType } = j.get();
+ const trace = LaminarJobQueuer.GetJobTypeTrace(jobType);
+ const metric = LaminarJobQueuer.JobTypeMetrics(jobType);
+
+ return j
+ .bimap(TraceUtil.withTrace(trace))
+ .bimap(TraceUtil.withMetricTrace(metric))
+ .map((j) => {
+ const { type: jobType, arguments: args } = j.get();
+ const laminarCommand = [
+ 'laminarc',
+ 'queue',
+ jobType,
+ ...Object.entries(args).map(([key, val]) => `"${key}"="${val}"`),
+ ];
+ return laminarCommand;
+ })
+ .peek((c) =>
+ c.trace.trace(`im so excited to see how this queue job will end!! (>ᴗ<): ${c.get().toString()}`),
+ )
+ .map(getStdout)
+ .peek(
+ TraceUtil.promiseify((q) =>
+ q.trace.trace(q.get().fold(({ isLeft }) => (isLeft ? metric.failure : metric.success))),
+ ),
+ )
+ .map(
+ TraceUtil.promiseify((q) =>
+ q.get().fold(({ isLeft, value }) => {
+ if (isLeft) {
+ q.trace.addTrace(LogLevel.ERROR).trace(value.toString());
+ return Either.left<Error, string>(value);
+ }
+ q.trace.addTrace(LogLevel.DEBUG).trace(`stdout ${value}`);
+ const [jobName, jobId] = value.split(':');
+ const jobUrl = `${this.queuePositionPrefix}/jobs/${jobName}/${jobId}`;
+
+ q.trace.trace(`all queued up and weady to go~ (˘ω˘) => ${jobUrl}`);
+ return Either.right<Error, string>(jobUrl);
+ }),
+ ),
+ )
+ .get();
+ }
+}
diff --git a/server/job/run_activity.ts b/server/job/run_activity.ts
new file mode 100644
index 0000000..9f25cf8
--- /dev/null
+++ b/server/job/run_activity.ts
@@ -0,0 +1,94 @@
+import {
+ Either,
+ ErrorSource,
+ type IActivity,
+ type IEither,
+ type ITraceable,
+ jsonModel,
+ JsonResponse,
+ LogLevel,
+ Metric,
+ PenguenoError,
+ type PenguenoRequest,
+ type ServerTrace,
+ TraceUtil,
+ validateExecutionEntries,
+} from '@emprespresso/pengueno';
+import { isJob, type Job } from '@emprespresso/ci_model';
+import { IJobQueuer } from './queue';
+
+const wellFormedJobMetric = Metric.fromName('Job.WellFormed');
+
+const jobJsonTransformer = (j: ITraceable<unknown, ServerTrace>): IEither<PenguenoError, Job> =>
+ j
+ .bimap(TraceUtil.withMetricTrace(wellFormedJobMetric))
+ .map((tJson): IEither<PenguenoError, Job> => {
+ const tJob = tJson.get();
+ if (!isJob(tJob) || !validateExecutionEntries(tJob)) {
+ const err = 'seems like a pwetty mawfomed job (-.-)';
+ tJson.trace.addTrace(LogLevel.WARN).trace(err);
+ return Either.left(new PenguenoError(err, 400));
+ }
+ return Either.right(tJob);
+ })
+ .peek((tJob) =>
+ tJob.trace.trace(
+ tJob.get().fold(({ isLeft }) => (isLeft ? wellFormedJobMetric.failure : wellFormedJobMetric.success)),
+ ),
+ )
+ .get();
+
+export interface IJobHookActivity {
+ processHook: IActivity;
+}
+
+const jobHookRequestMetric = Metric.fromName('JobHook.process');
+export class JobHookActivityImpl implements IJobHookActivity {
+ constructor(private readonly queuer: IJobQueuer<ITraceable<Job, ServerTrace>>) {}
+
+ private trace(r: ITraceable<PenguenoRequest, ServerTrace>) {
+ return r.bimap(TraceUtil.withClassTrace(this)).bimap(TraceUtil.withMetricTrace(jobHookRequestMetric));
+ }
+
+ public processHook(r: ITraceable<PenguenoRequest, ServerTrace>) {
+ return this.trace(r)
+ .map(jsonModel(jobJsonTransformer))
+ .map(async (tEitherJobJson) => {
+ const eitherJob = await tEitherJobJson.get();
+ return eitherJob.flatMapAsync(async (job) => {
+ const eitherQueued = await tEitherJobJson
+ .move(job)
+ .map((job) => this.queuer.queue(job))
+ .get();
+ return eitherQueued.mapLeft((e) => new PenguenoError(e.message, 500));
+ });
+ })
+ .peek(
+ TraceUtil.promiseify((tJob) =>
+ tJob.get().fold(({ isRight, value }) => {
+ if (isRight) {
+ tJob.trace.trace(jobHookRequestMetric.success);
+ tJob.trace.trace(`all queued up and weady to go :D !! ${value}`);
+ return;
+ }
+
+ tJob.trace.trace(
+ value.source === ErrorSource.SYSTEM
+ ? jobHookRequestMetric.failure
+ : jobHookRequestMetric.warn,
+ );
+ tJob.trace.addTrace(value.source).trace(`${value}`);
+ }),
+ ),
+ )
+ .map(
+ TraceUtil.promiseify(
+ (tEitherQueuedJob) =>
+ new JsonResponse(r, tEitherQueuedJob.get(), {
+ status: tEitherQueuedJob.get().fold(({ isRight, value }) => (isRight ? 200 : value.status)),
+ }),
+ ),
+ )
+ .get();
+ }
+}
diff --git a/server/mod.ts b/server/mod.ts
deleted file mode 100644
index 1d168d2..0000000
--- a/server/mod.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run
-
-export * from "./ci.ts";
-export * from "./health.ts";
-export * from "./job.ts";
-
-import { CiHookServer } from "./mod.ts";
-import { Either, type IEither } from "@emprespresso/pengueno";
-const server = new CiHookServer();
-
-export const runServer = (
- port: number,
- host: string,
-): Promise<IEither<Error, 0>> => {
- const serverConfig = {
- host,
- port,
- };
- return Either.fromFailable<Error, Deno.HttpServer>(() =>
- Deno.serve(serverConfig, (req) => server.serve(req)),
- ).flatMapAsync((server) => Either.fromFailableAsync(() => server.finished.then(() => 0)));
-};
diff --git a/server/package.json b/server/package.json
new file mode 100644
index 0000000..644f267
--- /dev/null
+++ b/server/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@emprespresso/ci_server",
+ "version": "0.1.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ },
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "clean": "rm -rf dist",
+ "type-check": "tsc --noEmit",
+ "start": "node dist/index.js",
+ "format": "prettier --write .",
+ "format:check": "prettier --check ."
+ },
+ "dependencies": {
+ "@emprespresso/pengueno": "*",
+ "@emprespresso/ci_model": "*",
+ "hono": "^4.8.0",
+ "@hono/node-server": "^1.14.0"
+ },
+ "files": [
+ "dist/**/*",
+ "package.json",
+ "README.md"
+ ]
+}
diff --git a/server/tsconfig.json b/server/tsconfig.json
new file mode 100644
index 0000000..58e9147
--- /dev/null
+++ b/server/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./",
+ "composite": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "noEmit": false
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
+ "references": [{ "path": "../u" }, { "path": "../model" }]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..105c510
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "outDir": "./dist",
+ "moduleResolution": "node",
+ "strict": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "lib": ["ES2022"],
+ "paths": {
+ "@emprespresso/pengueno": ["./u/index.ts"],
+ "@emprespresso/ci_model": ["./model/index.ts"],
+ "@emprespresso/ci_server": ["./server/index.ts"],
+ "@emprespresso/ci_worker": ["./worker/index.ts"],
+ }
+ },
+ "include": ["**/*.ts", "**/*.js"],
+ "exclude": ["node_modules", "dist", "**/*.d.ts"],
+ "references": [{ "path": "./u" }, { "path": "./model" }, { "path": "./server" }, { "path": "./worker" }]
+}
diff --git a/u/deno.json b/u/deno.json
deleted file mode 100644
index b277873..0000000
--- a/u/deno.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "@emprespresso/pengueno",
- "version": "0.1.0",
- "exports": "./mod.ts"
-}
diff --git a/u/fn/callable.ts b/u/fn/callable.ts
index cfb7d00..8a61057 100644
--- a/u/fn/callable.ts
+++ b/u/fn/callable.ts
@@ -1,18 +1,18 @@
// deno-lint-ignore no-explicit-any
export interface Callable<T = any, ArgT = any> {
- (...args: Array<ArgT>): T;
+ (...args: Array<ArgT>): T;
}
export interface Supplier<T> extends Callable<T, undefined> {
- (): T;
+ (): T;
}
export interface Mapper<T, U> extends Callable<U, T> {
- (t: T): U;
+ (t: T): U;
}
export interface BiMapper<T, U, R> extends Callable {
- (t: T, u: U): R;
+ (t: T, u: U): R;
}
export interface SideEffect<T> extends Mapper<T, void> {}
diff --git a/u/fn/either.ts b/u/fn/either.ts
index ffe8033..8c47b64 100644
--- a/u/fn/either.ts
+++ b/u/fn/either.ts
@@ -1,114 +1,100 @@
-import { type Mapper, type Supplier, isObject } from "@emprespresso/pengueno";
+import { type Mapper, type Supplier, isObject } from '@emprespresso/pengueno';
-type IEitherTag = "IEither";
-const iEitherTag: IEitherTag = "IEither";
+type IEitherTag = 'IEither';
+const iEitherTag: IEitherTag = 'IEither';
export interface _Either<LeftT, RightT, T> {
- readonly isLeft: LeftT;
- readonly isRight: RightT;
- readonly value: T;
+ readonly isLeft: LeftT;
+ readonly isRight: RightT;
+ readonly value: T;
}
export type Left<E> = _Either<true, false, E>;
export type Right<T> = _Either<false, true, T>;
export interface IEither<E, T> {
- readonly _tag: IEitherTag;
-
- mapBoth: <_E, _T>(
- errBranch: Mapper<E, _E>,
- okBranch: Mapper<T, _T>,
- ) => IEither<_E, _T>;
- fold: <_T>(folder: Mapper<Left<E> | Right<T>, _T>) => _T;
- moveRight: <_T>(t: _T) => IEither<E, _T>;
- mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>;
- mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>;
- flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>;
- flatMapAsync: <_T>(
- mapper: Mapper<T, Promise<IEither<E, _T>>>,
- ) => Promise<IEither<E, _T>>;
+ readonly _tag: IEitherTag;
+
+ mapBoth: <_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>) => IEither<_E, _T>;
+ fold: <_T>(folder: Mapper<Left<E> | Right<T>, _T>) => _T;
+ moveRight: <_T>(t: _T) => IEither<E, _T>;
+ mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>;
+ mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>;
+ flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>;
+ flatMapAsync: <_T>(mapper: Mapper<T, Promise<IEither<E, _T>>>) => Promise<IEither<E, _T>>;
}
export class Either<E, T> implements IEither<E, T> {
- private readonly self: Left<E> | Right<T>;
-
- private constructor(
- init: { err?: E; ok?: T },
- public readonly _tag: IEitherTag = iEitherTag,
- ) {
- this.self = <Left<E> | Right<T>>{
- isLeft: "err" in init,
- isRight: "ok" in init,
- value: init.err ?? init.ok!,
- };
- }
-
- public moveRight<_T>(t: _T) {
- return this.mapRight(() => t);
- }
-
- public fold<_T>(folder: Mapper<Left<E> | Right<T>, _T>): _T {
- return folder(this.self);
- }
-
- public mapBoth<_E, _T>(
- errBranch: Mapper<E, _E>,
- okBranch: Mapper<T, _T>,
- ): IEither<_E, _T> {
- if (this.self.isLeft) return Either.left(errBranch(this.self.value));
- return Either.right(okBranch(this.self.value));
- }
-
- public flatMap<_T>(mapper: Mapper<T, IEither<E, _T>>): IEither<E, _T> {
- if (this.self.isRight) return mapper(this.self.value);
- return Either.left<E, _T>(this.self.value);
- }
-
- public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> {
- if (this.self.isRight) return Either.right<E, _T>(mapper(this.self.value));
- return Either.left<E, _T>(this.self.value);
- }
-
- public mapLeft<_E>(mapper: Mapper<E, _E>): IEither<_E, T> {
- if (this.self.isLeft) return Either.left<_E, T>(mapper(this.self.value));
- return Either.right<_E, T>(this.self.value);
- }
-
- public async flatMapAsync<_T>(
- mapper: Mapper<T, Promise<IEither<E, _T>>>,
- ): Promise<IEither<E, _T>> {
- if (this.self.isLeft) {
- return Promise.resolve(Either.left<E, _T>(this.self.value));
+ private readonly self: Left<E> | Right<T>;
+
+ private constructor(
+ init: { err?: E; ok?: T },
+ public readonly _tag: IEitherTag = iEitherTag,
+ ) {
+ this.self = <Left<E> | Right<T>>{
+ isLeft: 'err' in init,
+ isRight: 'ok' in init,
+ value: init.err ?? init.ok!,
+ };
}
- return await mapper(this.self.value).catch((err) =>
- Either.left<E, _T>(err),
- );
- }
-
- static left<E, T>(e: E): IEither<E, T> {
- return new Either<E, T>({ err: e });
- }
-
- static right<E, T>(t: T): IEither<E, T> {
- return new Either<E, T>({ ok: t });
- }
-
- static fromFailable<E, T>(s: Supplier<T>): IEither<E, T> {
- try {
- return Either.right<E, T>(s());
- } catch (e) {
- return Either.left<E, T>(e as E);
+
+ public moveRight<_T>(t: _T) {
+ return this.mapRight(() => t);
+ }
+
+ public fold<_T>(folder: Mapper<Left<E> | Right<T>, _T>): _T {
+ return folder(this.self);
+ }
+
+ public mapBoth<_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>): IEither<_E, _T> {
+ if (this.self.isLeft) return Either.left(errBranch(this.self.value));
+ return Either.right(okBranch(this.self.value));
+ }
+
+ public flatMap<_T>(mapper: Mapper<T, IEither<E, _T>>): IEither<E, _T> {
+ if (this.self.isRight) return mapper(this.self.value);
+ return Either.left<E, _T>(this.self.value);
+ }
+
+ public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> {
+ if (this.self.isRight) return Either.right<E, _T>(mapper(this.self.value));
+ return Either.left<E, _T>(this.self.value);
+ }
+
+ public mapLeft<_E>(mapper: Mapper<E, _E>): IEither<_E, T> {
+ if (this.self.isLeft) return Either.left<_E, T>(mapper(this.self.value));
+ return Either.right<_E, T>(this.self.value);
+ }
+
+ public async flatMapAsync<_T>(mapper: Mapper<T, Promise<IEither<E, _T>>>): Promise<IEither<E, _T>> {
+ if (this.self.isLeft) {
+ return Promise.resolve(Either.left<E, _T>(this.self.value));
+ }
+ return await mapper(this.self.value).catch((err) => Either.left<E, _T>(err));
+ }
+
+ static left<E, T>(e: E): IEither<E, T> {
+ return new Either<E, T>({ err: e });
+ }
+
+ static right<E, T>(t: T): IEither<E, T> {
+ return new Either<E, T>({ ok: t });
+ }
+
+ static fromFailable<E, T>(s: Supplier<T>): IEither<E, T> {
+ try {
+ return Either.right<E, T>(s());
+ } catch (e) {
+ return Either.left<E, T>(e as E);
+ }
+ }
+
+ static async fromFailableAsync<E, T>(s: Supplier<Promise<T>> | Promise<T>): Promise<IEither<E, T>> {
+ return await (typeof s === 'function' ? s() : s)
+ .then((t: T) => Either.right<E, T>(t))
+ .catch((e: E) => Either.left<E, T>(e));
}
- }
-
- static async fromFailableAsync<E, T>(
- s: Supplier<Promise<T>>,
- ): Promise<IEither<E, T>> {
- return await s()
- .then((t: T) => Either.right<E, T>(t))
- .catch((e: E) => Either.left<E, T>(e));
- }
}
export const isEither = <E, T>(o: unknown): o is IEither<E, T> => {
- return isObject(o) && "_tag" in o && o._tag === "IEither";
+ return isObject(o) && '_tag' in o && o._tag === 'IEither';
};
diff --git a/u/fn/index.ts b/u/fn/index.ts
new file mode 100644
index 0000000..1ec71aa
--- /dev/null
+++ b/u/fn/index.ts
@@ -0,0 +1,2 @@
+export * from './callable.js';
+export * from './either.js';
diff --git a/u/fn/mod.ts b/u/fn/mod.ts
deleted file mode 100644
index f0fbe88..0000000
--- a/u/fn/mod.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./callable.ts";
-export * from "./either.ts";
diff --git a/u/index.ts b/u/index.ts
new file mode 100644
index 0000000..0c8c760
--- /dev/null
+++ b/u/index.ts
@@ -0,0 +1,6 @@
+export * from './fn/index.js';
+export * from './leftpadesque/index.js';
+export * from './process/index.js';
+export * from './trace/index.js';
+export * from './server/index.js';
+export * from './history.js';
diff --git a/u/leftpadesque/debug.ts b/u/leftpadesque/debug.ts
index e50b2e0..074e567 100644
--- a/u/leftpadesque/debug.ts
+++ b/u/leftpadesque/debug.ts
@@ -1,13 +1,8 @@
-const _hasEnv = !Deno.permissions.querySync({ name: "env" });
+const _hasEnv = true; // Node.js always has access to environment variables
-const _env: "development" | "production" =
- _hasEnv && (Deno.env.get("ENVIRONMENT") ?? "").toLowerCase().includes("prod")
- ? "production"
- : "development";
-export const isProd = () => _env === "production";
+const _env: 'development' | 'production' =
+ _hasEnv && (process.env.ENVIRONMENT ?? '').toLowerCase().includes('prod') ? 'production' : 'development';
+export const isProd = () => _env === 'production';
-const _debug =
- !isProd() ||
- (_hasEnv &&
- ["y", "t"].some((Deno.env.get("DEBUG") ?? "").toLowerCase().startsWith));
+const _debug = !isProd() || (_hasEnv && ['y', 't'].some((process.env.DEBUG ?? '').toLowerCase().startsWith));
export const isDebug = () => _debug;
diff --git a/u/leftpadesque/index.ts b/u/leftpadesque/index.ts
new file mode 100644
index 0000000..6403e4a
--- /dev/null
+++ b/u/leftpadesque/index.ts
@@ -0,0 +1,4 @@
+export * from './object.js';
+export * from './prepend.js';
+export * from './debug.js';
+export * from './memoize.js';
diff --git a/u/leftpadesque/memoize.ts b/u/leftpadesque/memoize.ts
index 95e6019..541bd20 100644
--- a/u/leftpadesque/memoize.ts
+++ b/u/leftpadesque/memoize.ts
@@ -1,14 +1,14 @@
-import type { Callable } from "@emprespresso/pengueno";
+import type { Callable } from '@emprespresso/pengueno';
export const memoize = <R, F extends Callable<R>>(fn: F): F => {
- const cache = new Map<string, R>();
- return ((...args: unknown[]): R => {
- const key = JSON.stringify(args);
- if (cache.has(key)) {
- return cache.get(key)!;
- }
- const res = fn.apply(args);
- cache.set(key, res);
- return res;
- }) as F;
+ const cache = new Map<string, R>();
+ return ((...args: unknown[]): R => {
+ const key = JSON.stringify(args);
+ if (cache.has(key)) {
+ return cache.get(key)!;
+ }
+ const res = fn.apply(args);
+ cache.set(key, res);
+ return res;
+ }) as F;
};
diff --git a/u/leftpadesque/mod.ts b/u/leftpadesque/mod.ts
deleted file mode 100644
index 63d8d7a..0000000
--- a/u/leftpadesque/mod.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from "./object.ts";
-export * from "./prepend.ts";
-export * from "./debug.ts";
-export * from "./memoize.ts";
diff --git a/u/leftpadesque/object.ts b/u/leftpadesque/object.ts
index 73f7f80..fe97999 100644
--- a/u/leftpadesque/object.ts
+++ b/u/leftpadesque/object.ts
@@ -1,2 +1 @@
-export const isObject = (o: unknown): o is object =>
- typeof o === "object" && !Array.isArray(o) && !!o;
+export const isObject = (o: unknown): o is object => typeof o === 'object' && !Array.isArray(o) && !!o;
diff --git a/u/leftpadesque/prepend.ts b/u/leftpadesque/prepend.ts
index 0f1ce30..d80f6b6 100644
--- a/u/leftpadesque/prepend.ts
+++ b/u/leftpadesque/prepend.ts
@@ -1,5 +1,5 @@
export const prependWith = (arr: string[], prep: string) =>
- Array(arr.length * 2)
- .fill(0)
- .map((_, i) => i % 2 === 0)
- .map((isPrep, i) => (isPrep ? prep : arr[i]));
+ Array(arr.length * 2)
+ .fill(0)
+ .map((_, i) => i % 2 === 0)
+ .map((isPrep, i) => (isPrep ? prep : arr[i]!));
diff --git a/u/mod.ts b/u/mod.ts
deleted file mode 100644
index 2ab8f68..0000000
--- a/u/mod.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export * from "./fn/mod.ts";
-export * from "./leftpadesque/mod.ts";
-export * from "./process/mod.ts";
-export * from "./trace/mod.ts";
-export * from "./server/mod.ts";
-export * from "./history.ts";
diff --git a/u/package.json b/u/package.json
new file mode 100644
index 0000000..d38ac4c
--- /dev/null
+++ b/u/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "@emprespresso/pengueno",
+ "version": "0.1.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ },
+ "./fn": {
+ "types": "./dist/fn/index.d.ts",
+ "import": "./dist/fn/index.js"
+ },
+ "./leftpadesque": {
+ "types": "./dist/leftpadesque/index.d.ts",
+ "import": "./dist/leftpadesque/index.js"
+ },
+ "./process": {
+ "types": "./dist/process/index.d.ts",
+ "import": "./dist/process/index.js"
+ },
+ "./trace": {
+ "types": "./dist/trace/index.d.ts",
+ "import": "./dist/trace/index.js"
+ },
+ "./server": {
+ "types": "./dist/server/index.d.ts",
+ "import": "./dist/server/index.js"
+ }
+ },
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "clean": "rm -rf dist",
+ "type-check": "tsc --noEmit"
+ },
+ "dependencies": {
+ "hono": "^4.8.0"
+ },
+ "files": [
+ "dist/**/*",
+ "package.json",
+ "README.md"
+ ]
+}
diff --git a/u/process/argv.ts b/u/process/argv.ts
index 45f8ff0..dcdba85 100644
--- a/u/process/argv.ts
+++ b/u/process/argv.ts
@@ -1,7 +1,6 @@
-import { Either, type Mapper, type IEither } from "@emprespresso/pengueno";
+import { Either, type Mapper, type IEither } from '@emprespresso/pengueno';
-export const isArgKey = <K extends string>(k: string): k is K =>
- k.startsWith("--");
+export const isArgKey = <K extends string>(k: string): k is K => k.startsWith('--');
interface ArgHandler<V> {
absent?: V;
@@ -10,42 +9,42 @@ interface ArgHandler<V> {
}
export const getArg = <K extends string, V>(
- arg: K,
- argv: Array<string>,
- whenValue: ArgHandler<V>,
+ arg: K,
+ argv: Array<string>,
+ whenValue: ArgHandler<V>,
): IEither<Error, V> => {
- const value = argv.filter((_argv) => isArgKey(_argv) && _argv.split("=")[0] === arg).map((_argv, i) => {
- const next = _argv.includes("=") ? _argv.split("=")[1] : argv.at(i + 1);
- if (next) {
- if (isArgKey(next)) return whenValue.unspecified;
- return whenValue.present(next);
- }
- return whenValue.unspecified;
- }).find(x => x) ?? whenValue.absent;
+ const value =
+ argv
+ .filter((_argv) => isArgKey(_argv) && _argv.split('=')[0] === arg)
+ .map((_argv, i) => {
+ const next = _argv.includes('=') ? _argv.split('=')[1] : argv.at(i + 1);
+ if (next) {
+ if (isArgKey(next)) return whenValue.unspecified;
+ return whenValue.present(next);
+ }
+ return whenValue.unspecified;
+ })
+ .find((x) => x) ?? whenValue.absent;
if (value === undefined) {
- return Either.left(new Error("no value specified for " + arg));
+ return Either.left(new Error('no value specified for ' + arg));
}
return Either.right(value);
};
type MappedArgs<
- Args extends ReadonlyArray<string>,
- Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>
+ Args extends ReadonlyArray<string>,
+ Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>,
> = {
- [K in Args[number]]: K extends keyof Handlers
- ? Handlers[K] extends ArgHandler<infer T>
- ? T
- : string
- : string;
+ [K in Args[number]]: K extends keyof Handlers ? (Handlers[K] extends ArgHandler<infer T> ? T : string) : string;
};
export const argv = <
- const Args extends ReadonlyArray<string>,
- const Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>
+ const Args extends ReadonlyArray<string>,
+ const Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>,
>(
args: Args,
handlers?: Handlers,
- argv = Deno.args,
+ argv = process.argv.slice(2),
): IEither<Error, MappedArgs<Args, Handlers>> => {
type Result = MappedArgs<Args, Handlers>;
@@ -53,20 +52,20 @@ export const argv = <
const processArg = (arg: Args[number]): IEither<Error, [Args[number], unknown]> => {
const handler = handlers?.[arg] ?? defaultHandler;
- return getArg(arg, argv, handler).mapRight(value => [arg, value] as const);
+ return getArg(arg, argv, handler).mapRight((value) => [arg, value] as const);
};
- const argResults = args.map(processArg);
-
- return argResults.reduce(
- (acc: IEither<Error, Partial<Result>>, current: IEither<Error, readonly [Args[number], unknown]>) => {
- return acc.flatMap(accValue =>
- current.mapRight(([key, value]) => ({
- ...accValue,
- [key]: value
- }))
- );
- },
- Either.right({} as Partial<Result>)
- ).mapRight(result => result as Result);
+ return args
+ .map(processArg)
+ .reduce(
+ (acc: IEither<Error, Partial<Result>>, current: IEither<Error, readonly [Args[number], unknown]>) =>
+ acc.flatMap((accValue) =>
+ current.mapRight(([key, value]) => ({
+ ...accValue,
+ [key]: value,
+ })),
+ ),
+ Either.right(<Partial<Result>>{}),
+ )
+ .mapRight((result) => <Result>result);
};
diff --git a/u/process/env.ts b/u/process/env.ts
index 5ba4189..1e4fd32 100644
--- a/u/process/env.ts
+++ b/u/process/env.ts
@@ -1,37 +1,30 @@
-import { Either, type IEither } from "@emprespresso/pengueno";
+import { Either, type IEither } from '@emprespresso/pengueno';
export const getRequiredEnv = <V extends string>(name: V): IEither<Error, V> =>
- Either.fromFailable<Error, V | undefined>(
- () => Deno.env.get(name) as V | undefined,
- ) // could throw when no permission.
- .flatMap(
- (v) =>
- (v && Either.right(v)) ||
- Either.left(new Error(`environment variable "${name}" is required D:`)),
- );
+ Either.fromFailable<Error, V | undefined>(() => process.env[name] as V | undefined) // could throw when no permission.
+ .flatMap(
+ (v) => (v && Either.right(v)) || Either.left(new Error(`environment variable "${name}" is required D:`)),
+ );
type ObjectFromList<T extends ReadonlyArray<string>, V = string> = {
- [K in T extends ReadonlyArray<infer U> ? U : never]: V;
+ [K in T extends ReadonlyArray<infer U> ? U : never]: V;
};
export const getRequiredEnvVars = <V extends string>(vars: ReadonlyArray<V>) =>
- vars
- .map((envVar) => [envVar, getRequiredEnv(envVar)] as [V, IEither<Error, V>])
- .reduce(
- (
- acc: IEither<Error, ObjectFromList<typeof vars>>,
- x: [V, IEither<Error, V>],
- ) => {
- const [envVar, eitherVal] = x;
- return acc.flatMap((args) => {
- return eitherVal.mapRight(
- (envValue) =>
- ({
- ...args,
- [envVar]: envValue,
- }) as ObjectFromList<typeof vars>,
- );
- });
- },
- Either.right({} as ObjectFromList<typeof vars>),
- );
+ vars
+ .map((envVar) => [envVar, getRequiredEnv(envVar)] as [V, IEither<Error, V>])
+ .reduce(
+ (acc: IEither<Error, ObjectFromList<typeof vars>>, x: [V, IEither<Error, V>]) => {
+ const [envVar, eitherVal] = x;
+ return acc.flatMap((args) => {
+ return eitherVal.mapRight(
+ (envValue) =>
+ ({
+ ...args,
+ [envVar]: envValue,
+ }) as ObjectFromList<typeof vars>,
+ );
+ });
+ },
+ Either.right({} as ObjectFromList<typeof vars>),
+ );
diff --git a/u/process/index.ts b/u/process/index.ts
new file mode 100644
index 0000000..4ffbf2a
--- /dev/null
+++ b/u/process/index.ts
@@ -0,0 +1,4 @@
+export * from './env.js';
+export * from './run.js';
+export * from './validate_identifier.js';
+export * from './argv.js';
diff --git a/u/process/mod.ts b/u/process/mod.ts
deleted file mode 100644
index 211e9a7..0000000
--- a/u/process/mod.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from "./env.ts";
-export * from "./run.ts";
-export * from "./validate_identifier.ts";
-export * from "./argv.ts";
diff --git a/u/process/run.ts b/u/process/run.ts
index abe143c..e3c4c3d 100644
--- a/u/process/run.ts
+++ b/u/process/run.ts
@@ -1,68 +1,46 @@
import {
- Either,
- type IEither,
- type ITraceable,
- LogLevel,
- type LogTraceSupplier,
- TraceUtil,
-} from "@emprespresso/pengueno";
+ Either,
+ type IEither,
+ type ITraceable,
+ LogLevel,
+ type LogTraceSupplier,
+ TraceUtil,
+} from '@emprespresso/pengueno';
+import { promisify } from 'node:util';
+import { exec as execCallback } from 'node:child_process';
+const exec = promisify(execCallback);
export type Command = string[] | string;
-type CommandOutputDecoded = {
- code: number;
- stdoutText: string;
- stderrText: string;
-};
+export type StdStreams = { stdout: string; stderr: string };
export const getStdout = (
- c: ITraceable<Command, LogTraceSupplier>,
- options: Deno.CommandOptions = {},
+ c: ITraceable<Command, LogTraceSupplier>,
+ options: { env?: Record<string, string>; clearEnv?: boolean } = {},
): Promise<IEither<Error, string>> =>
- c
- .bimap(TraceUtil.withFunctionTrace(getStdout))
- .map((tCmd) => {
- const cmd = tCmd.get();
- tCmd.trace.trace(`Command = ${cmd} :> im gonna run this command! `);
- const [exec, ...args] = typeof cmd === "string" ? cmd.split(" ") : cmd;
- return new Deno.Command(exec, {
- args,
- stdout: "piped",
- stderr: "piped",
- ...options,
- });
- })
- .map((tCmd) =>
- Either.fromFailableAsync<Error, Deno.CommandOutput>(() =>
- tCmd.get().output(),
- ),
- )
- .map(
- TraceUtil.promiseify((tEitherOut) =>
- tEitherOut.get().flatMap(({ code, stderr, stdout }) =>
- Either.fromFailable<Error, CommandOutputDecoded>(() => {
- const stdoutText = new TextDecoder().decode(stdout);
- const stderrText = new TextDecoder().decode(stderr);
- return { code, stdoutText, stderrText };
- })
- .mapLeft((e) => {
- tEitherOut.trace.addTrace(LogLevel.ERROR).trace(e);
- return e;
- })
- .flatMap((decodedOutput): IEither<Error, string> => {
- const { code, stdoutText, stderrText } = decodedOutput;
- if (stderrText) {
- tEitherOut.trace
- .addTrace(LogLevel.DEBUG)
- .trace(`stderr: ${stderrText}`);
- }
- if (code !== 0) {
- const msg = `i weceived an exit code of ${code} i wanna zewoooo :<`;
- tEitherOut.trace.addTrace(LogLevel.ERROR).trace(msg);
- return Either.left(new Error(msg));
- }
- return Either.right(stdoutText);
- }),
- ),
- ),
- )
- .get();
+ c
+ .bimap(TraceUtil.withFunctionTrace(getStdout))
+ .bimap((tCmd) => {
+ const cmd = tCmd.get();
+ tCmd.trace.trace(`Command = ${cmd} :> im gonna run this command! `);
+
+ const _exec = typeof cmd === 'string' ? cmd : cmd.join(' ');
+ const env = options.clearEnv ? options.env : { ...process.env, ...options.env };
+
+ const p: Promise<IEither<Error, StdStreams>> = Either.fromFailableAsync(exec(_exec, { env }));
+ return [p, `Command = ${_exec}`];
+ })
+ .map(
+ TraceUtil.promiseify(
+ (tEitherProcess): IEither<Error, string> =>
+ tEitherProcess.get().fold(({ isLeft, value }) => {
+ if (isLeft) {
+ return Either.left(value);
+ }
+ if (value.stderr) {
+ tEitherProcess.trace.addTrace(LogLevel.DEBUG).trace(`StdErr = ${value.stderr}`);
+ }
+ return Either.right(value.stdout);
+ }),
+ ),
+ )
+ .get();
diff --git a/u/process/validate_identifier.ts b/u/process/validate_identifier.ts
index 4e93728..1ff3791 100644
--- a/u/process/validate_identifier.ts
+++ b/u/process/validate_identifier.ts
@@ -1,23 +1,18 @@
-import { Either, type IEither } from "@emprespresso/pengueno";
+import { Either, type IEither } from '@emprespresso/pengueno';
export const validateIdentifier = (token: string) => {
- return /^[a-zA-Z0-9_\-:. \/]+$/.test(token) && !token.includes("..");
+ return /^[a-zA-Z0-9_\-:. \/]+$/.test(token) && !token.includes('..');
};
// ensure {@param obj} is a Record<string, string> with stuff that won't
// have the potential for shell injection, just to be super safe.
type InvalidEntry<K, T> = [K, T];
-export const validateExecutionEntries = <
- T,
- K extends symbol | number | string = symbol | number | string,
->(
- obj: Record<K, T>,
+export const validateExecutionEntries = <T, K extends symbol | number | string = symbol | number | string>(
+ obj: Record<K, T>,
): IEither<Array<InvalidEntry<K, T>>, Record<string, string>> => {
- const invalidEntries = <Array<InvalidEntry<K, T>>>(
- Object.entries(obj).filter(
- (e) => !e.every((x) => typeof x === "string" && validateIdentifier(x)),
- )
- );
- if (invalidEntries.length > 0) return Either.left(invalidEntries);
- return Either.right(<Record<string, string>>obj);
+ const invalidEntries = <Array<InvalidEntry<K, T>>>(
+ Object.entries(obj).filter((e) => !e.every((x) => typeof x === 'string' && validateIdentifier(x)))
+ );
+ if (invalidEntries.length > 0) return Either.left(invalidEntries);
+ return Either.right(<Record<string, string>>obj);
};
diff --git a/u/server/activity/fourohfour.ts b/u/server/activity/fourohfour.ts
index 33cfe5f..cd90ba0 100644
--- a/u/server/activity/fourohfour.ts
+++ b/u/server/activity/fourohfour.ts
@@ -1,29 +1,28 @@
import {
- type IActivity,
- type ITraceable,
- JsonResponse,
- type PenguenoRequest,
- type ServerTrace,
-} from "@emprespresso/pengueno";
+ type IActivity,
+ type ITraceable,
+ JsonResponse,
+ type PenguenoRequest,
+ type ServerTrace,
+} from '@emprespresso/pengueno';
const messages = [
- "D: meow-t found! your api call ran away!",
- "404-bidden! but like...in a cute way >:3 !",
- ":< your data went on a paw-sible vacation!",
- "uwu~ not found, but found our hearts instead!",
+ 'D: meow-t found! your api call ran away!',
+ '404-bidden! but like...in a cute way >:3 !',
+ ':< your data went on a paw-sible vacation!',
+ 'uwu~ not found, but found our hearts instead!',
];
-const randomFourOhFour = () =>
- messages[Math.floor(Math.random() * messages.length)];
+const randomFourOhFour = () => messages[Math.floor(Math.random() * messages.length)]!;
export interface IFourOhFourActivity {
- fourOhFour: IActivity;
+ fourOhFour: IActivity;
}
export class FourOhFourActivityImpl implements IFourOhFourActivity {
- public fourOhFour(req: ITraceable<PenguenoRequest, ServerTrace>) {
- return req
- .move(new JsonResponse(req, randomFourOhFour(), { status: 404 }))
- .map((resp) => Promise.resolve(resp.get()))
- .get();
- }
+ public fourOhFour(req: ITraceable<PenguenoRequest, ServerTrace>) {
+ return req
+ .move(new JsonResponse(req, randomFourOhFour(), { status: 404 }))
+ .map((resp) => Promise.resolve(resp.get()))
+ .get();
+ }
}
diff --git a/u/server/activity/health.ts b/u/server/activity/health.ts
index 95dfa97..b3ae559 100644
--- a/u/server/activity/health.ts
+++ b/u/server/activity/health.ts
@@ -1,71 +1,67 @@
import {
- type IActivity,
- type IEither,
- type ITraceable,
- JsonResponse,
- LogLevel,
- type Mapper,
- Metric,
- type PenguenoRequest,
- type ServerTrace,
- TraceUtil,
-} from "@emprespresso/pengueno";
+ type IActivity,
+ type IEither,
+ IMetric,
+ type ITraceable,
+ JsonResponse,
+ LogLevel,
+ type Mapper,
+ Metric,
+ type PenguenoRequest,
+ type ServerTrace,
+ TraceUtil,
+} from '@emprespresso/pengueno';
export enum HealthCheckInput {
- CHECK,
+ CHECK,
}
export enum HealthCheckOutput {
- YAASSSLAYQUEEN,
+ YAASSSLAYQUEEN,
}
export interface IHealthCheckActivity {
- checkHealth: IActivity;
+ checkHealth: IActivity;
}
-const healthCheckMetric = Metric.fromName("Health");
+const healthCheckMetric: IMetric = Metric.fromName('Health');
export interface HealthChecker
- extends Mapper<
- ITraceable<HealthCheckInput, ServerTrace>,
- Promise<IEither<Error, HealthCheckOutput>>
- > {}
+ extends Mapper<ITraceable<HealthCheckInput, ServerTrace>, Promise<IEither<Error, HealthCheckOutput>>> {}
export class HealthCheckActivityImpl implements IHealthCheckActivity {
- constructor(private readonly check: HealthChecker) {}
+ constructor(private readonly check: HealthChecker) {}
- public checkHealth(req: ITraceable<PenguenoRequest, ServerTrace>) {
- return req
- .bimap(TraceUtil.withFunctionTrace(this.checkHealth))
- .bimap(TraceUtil.withMetricTrace(healthCheckMetric))
- .flatMap((r) =>
- r.move(HealthCheckInput.CHECK).map((input) => this.check(input)),
- )
- .peek(
- TraceUtil.promiseify((h) =>
- h.get().fold(({ isLeft, value }) => {
- if (!isLeft) {
- h.trace.trace(healthCheckMetric.success);
- return;
- }
- h.trace.trace(healthCheckMetric.failure);
- h.trace.addTrace(LogLevel.ERROR).trace(value);
- }),
- ),
- )
- .map(
- TraceUtil.promiseify((h) =>
- h
- .get()
- .mapBoth(
- () => "oh no, i need to eat more vegetables (。•́︿•̀。)...",
- () => "think im healthy!! (✿˘◡˘) ready to do work~",
+ public checkHealth(req: ITraceable<PenguenoRequest, ServerTrace>) {
+ return req
+ .bimap(TraceUtil.withFunctionTrace(this.checkHealth))
+ .bimap(TraceUtil.withMetricTrace(healthCheckMetric))
+ .flatMap((r) => r.move(HealthCheckInput.CHECK).map((input) => this.check(input)))
+ .peek(
+ TraceUtil.promiseify((h) =>
+ h.get().fold(({ isLeft, value }) => {
+ if (!isLeft) {
+ h.trace.trace(healthCheckMetric.success);
+ return;
+ }
+ h.trace.trace(healthCheckMetric.failure);
+ h.trace.addTrace(LogLevel.ERROR).trace(value);
+ }),
+ ),
)
- .fold(
- ({ isLeft, value: message }) =>
- new JsonResponse(req, message, {
- status: isLeft ? 500 : 200,
- }),
- ),
- ),
- )
- .get();
- }
+ .map(
+ TraceUtil.promiseify((h) =>
+ h
+ .get()
+ .mapBoth(
+ () => 'oh no, i need to eat more vegetables (。•́︿•̀。)...',
+ () => 'think im healthy!! (✿˘◡˘) ready to do work~',
+ )
+ .fold(
+ ({ isLeft, value: message }) =>
+ new JsonResponse(req, message, {
+ status: isLeft ? 500 : 200,
+ }),
+ ),
+ ),
+ )
+ .get();
+ }
}
diff --git a/u/server/activity/index.ts b/u/server/activity/index.ts
new file mode 100644
index 0000000..fa0a6b2
--- /dev/null
+++ b/u/server/activity/index.ts
@@ -0,0 +1,8 @@
+import type { ITraceable, PenguenoRequest, PenguenoResponse, ServerTrace } from '@emprespresso/pengueno';
+
+export interface IActivity {
+ (req: ITraceable<PenguenoRequest, ServerTrace>): Promise<PenguenoResponse>;
+}
+
+export * from './health.js';
+export * from './fourohfour.js';
diff --git a/u/server/activity/mod.ts b/u/server/activity/mod.ts
deleted file mode 100644
index 82d8ec4..0000000
--- a/u/server/activity/mod.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type {
- ITraceable,
- PenguenoRequest,
- PenguenoResponse,
- ServerTrace,
-} from "@emprespresso/pengueno";
-
-export interface IActivity {
- (req: ITraceable<PenguenoRequest, ServerTrace>): Promise<PenguenoResponse>;
-}
-
-export * from "./health.ts";
-export * from "./fourohfour.ts";
diff --git a/u/server/filter/index.ts b/u/server/filter/index.ts
new file mode 100644
index 0000000..62a584d
--- /dev/null
+++ b/u/server/filter/index.ts
@@ -0,0 +1,34 @@
+import {
+ type IEither,
+ type ITraceable,
+ LogLevel,
+ type PenguenoRequest,
+ type ServerTrace,
+} from '@emprespresso/pengueno';
+
+export enum ErrorSource {
+ USER = LogLevel.WARN,
+ SYSTEM = LogLevel.ERROR,
+}
+
+export class PenguenoError extends Error {
+ public readonly source: ErrorSource;
+ constructor(
+ override readonly message: string,
+ public readonly status: number,
+ ) {
+ super(message);
+ this.source = Math.floor(status / 100) === 4 ? ErrorSource.USER : ErrorSource.SYSTEM;
+ }
+}
+
+export interface RequestFilter<
+ T,
+ Err extends PenguenoError = PenguenoError,
+ RIn = ITraceable<PenguenoRequest, ServerTrace>,
+> {
+ (req: RIn): Promise<IEither<Err, T>>;
+}
+
+export * from './method.js';
+export * from './json.js';
diff --git a/u/server/filter/json.ts b/u/server/filter/json.ts
index 145d1be..527d483 100644
--- a/u/server/filter/json.ts
+++ b/u/server/filter/json.ts
@@ -1,54 +1,50 @@
import {
- Either,
- type IEither,
- type ITraceable,
- LogLevel,
- Metric,
- PenguenoError,
- type PenguenoRequest,
- type RequestFilter,
- type ServerTrace,
- TraceUtil,
-} from "@emprespresso/pengueno";
+ Either,
+ type IEither,
+ type ITraceable,
+ LogLevel,
+ Metric,
+ PenguenoError,
+ type PenguenoRequest,
+ type RequestFilter,
+ type ServerTrace,
+ TraceUtil,
+} from '@emprespresso/pengueno';
export interface JsonTransformer<R, ParsedJson = unknown> {
- (json: ITraceable<ParsedJson, ServerTrace>): IEither<PenguenoError, R>;
+ (json: ITraceable<ParsedJson, ServerTrace>): IEither<PenguenoError, R>;
}
-const ParseJsonMetric = Metric.fromName("JsonParse");
+const ParseJsonMetric = Metric.fromName('JsonParse');
export const jsonModel =
- <MessageT>(
- jsonTransformer: JsonTransformer<MessageT>,
- ): RequestFilter<MessageT> =>
- (r: ITraceable<PenguenoRequest, ServerTrace>) =>
- r
- .bimap(TraceUtil.withFunctionTrace(jsonModel))
- .bimap(TraceUtil.withMetricTrace(ParseJsonMetric))
- .map((j) =>
- Either.fromFailableAsync<Error, MessageT>(() => j.get().json()).then(
- (either) =>
- either.mapLeft((errReason) => {
- j.trace.addTrace(LogLevel.WARN).trace(errReason);
- return new PenguenoError(
- "seems to be invalid JSON (>//<) can you fix?",
- 400,
- );
- }),
- ),
- )
- .peek(
- TraceUtil.promiseify((traceableEither) =>
- traceableEither.get().fold(({ isLeft }) =>
- traceableEither.trace.trace(ParseJsonMetric[isLeft ? "failure" : "success"])
- ),
- ),
- )
- .map(
- TraceUtil.promiseify((traceableEitherJson) =>
- traceableEitherJson
- .get()
- .mapRight((j) => traceableEitherJson.move(j))
- .flatMap(jsonTransformer),
- ),
- )
- .get();
+ <MessageT>(jsonTransformer: JsonTransformer<MessageT>): RequestFilter<MessageT> =>
+ (r: ITraceable<PenguenoRequest, ServerTrace>) =>
+ r
+ .bimap(TraceUtil.withFunctionTrace(jsonModel))
+ .bimap(TraceUtil.withMetricTrace(ParseJsonMetric))
+ .map((j) =>
+ Either.fromFailableAsync<Error, MessageT>(<Promise<MessageT>>j.get().json()).then((either) =>
+ either.mapLeft((errReason) => {
+ j.trace.addTrace(LogLevel.WARN).trace(errReason);
+ return new PenguenoError('seems to be invalid JSON (>//<) can you fix?', 400);
+ }),
+ ),
+ )
+ .peek(
+ TraceUtil.promiseify((traceableEither) =>
+ traceableEither
+ .get()
+ .fold(({ isLeft }) =>
+ traceableEither.trace.trace(ParseJsonMetric[isLeft ? 'failure' : 'success']),
+ ),
+ ),
+ )
+ .map(
+ TraceUtil.promiseify((traceableEitherJson) =>
+ traceableEitherJson
+ .get()
+ .mapRight((j) => traceableEitherJson.move(j))
+ .flatMap(jsonTransformer),
+ ),
+ )
+ .get();
diff --git a/u/server/filter/method.ts b/u/server/filter/method.ts
index 9901c6f..5ca5716 100644
--- a/u/server/filter/method.ts
+++ b/u/server/filter/method.ts
@@ -1,43 +1,32 @@
import {
- Either,
- type ITraceable,
- LogLevel,
- PenguenoError,
- type PenguenoRequest,
- type RequestFilter,
- type ServerTrace,
- TraceUtil,
-} from "@emprespresso/pengueno";
+ Either,
+ type ITraceable,
+ LogLevel,
+ PenguenoError,
+ type PenguenoRequest,
+ type RequestFilter,
+ type ServerTrace,
+ TraceUtil,
+} from '@emprespresso/pengueno';
-type HttpMethod =
- | "POST"
- | "GET"
- | "HEAD"
- | "PUT"
- | "DELETE"
- | "CONNECT"
- | "OPTIONS"
- | "TRACE"
- | "PATCH";
+type HttpMethod = 'POST' | 'GET' | 'HEAD' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';
export const requireMethod =
- (methods: Array<HttpMethod>): RequestFilter<HttpMethod> =>
- (req: ITraceable<PenguenoRequest, ServerTrace>) =>
- req
- .bimap(TraceUtil.withFunctionTrace(requireMethod))
- .move(Promise.resolve(req.get()))
- .map(
- TraceUtil.promiseify((t) => {
- const { method: _method } = t.get();
- const method = <HttpMethod>_method;
- if (!methods.includes(method)) {
- const msg = "that's not how you pet me (⋟﹏⋞)~";
- t.trace.addTrace(LogLevel.WARN).trace(msg);
- return Either.left<PenguenoError, HttpMethod>(
- new PenguenoError(msg, 405),
- );
- }
- return Either.right<PenguenoError, HttpMethod>(method);
- }),
- )
- .get();
+ (methods: Array<HttpMethod>): RequestFilter<HttpMethod> =>
+ (req: ITraceable<PenguenoRequest, ServerTrace>) =>
+ req
+ .bimap(TraceUtil.withFunctionTrace(requireMethod))
+ .move(Promise.resolve(req.get()))
+ .map(
+ TraceUtil.promiseify((t) => {
+ const { method: _method } = t.get();
+ const method = <HttpMethod>_method;
+ if (!methods.includes(method)) {
+ const msg = "that's not how you pet me (⋟﹏⋞)~";
+ t.trace.addTrace(LogLevel.WARN).trace(msg);
+ return Either.left<PenguenoError, HttpMethod>(new PenguenoError(msg, 405));
+ }
+ return Either.right<PenguenoError, HttpMethod>(method);
+ }),
+ )
+ .get();
diff --git a/u/server/filter/mod.ts b/u/server/filter/mod.ts
deleted file mode 100644
index 0e0a4cb..0000000
--- a/u/server/filter/mod.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
- type IEither,
- type ITraceable,
- LogLevel,
- type PenguenoRequest,
- type ServerTrace,
-} from "@emprespresso/pengueno";
-
-export enum ErrorSource {
- USER = LogLevel.WARN,
- SYSTEM = LogLevel.ERROR,
-}
-
-export class PenguenoError extends Error {
- public readonly source: ErrorSource;
- constructor(
- override readonly message: string,
- public readonly status: number,
- ) {
- super(message);
- this.source =
- Math.floor(status / 100) === 4 ? ErrorSource.USER : ErrorSource.SYSTEM;
- }
-}
-
-export interface RequestFilter<
- T,
- Err extends PenguenoError = PenguenoError,
- RIn = ITraceable<PenguenoRequest, ServerTrace>,
-> {
- (req: RIn): Promise<IEither<Err, T>>;
-}
-
-export * from "./method.ts";
-export * from "./json.ts";
diff --git a/u/server/index.ts b/u/server/index.ts
new file mode 100644
index 0000000..17cbbdf
--- /dev/null
+++ b/u/server/index.ts
@@ -0,0 +1,7 @@
+import type { LogMetricTraceSupplier } from '@emprespresso/pengueno';
+export type ServerTrace = LogMetricTraceSupplier;
+
+export * from './activity/index.js';
+export * from './filter/index.js';
+export * from './response.js';
+export * from './request.js';
diff --git a/u/server/mod.ts b/u/server/mod.ts
deleted file mode 100644
index 866b5f9..0000000
--- a/u/server/mod.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { LogMetricTraceSupplier } from "@emprespresso/pengueno";
-export type ServerTrace = LogMetricTraceSupplier;
-
-export * from "./activity/mod.ts";
-export * from "./filter/mod.ts";
-export * from "./response.ts";
-export * from "./request.ts";
diff --git a/u/server/request.ts b/u/server/request.ts
index 72e812a..10610f1 100644
--- a/u/server/request.ts
+++ b/u/server/request.ts
@@ -1,49 +1,39 @@
-import { LogMetricTraceable, LogTraceable } from "@emprespresso/pengueno";
-import { TraceUtil } from "../trace/util.ts";
+import { TraceUtil, LogMetricTraceable, LogTraceable } from '@emprespresso/pengueno';
-const greetings = [
- "hewwo :D",
- "hiya cutie",
- "boop!",
- "sending virtual hugs!",
- "stay pawsitive",
-];
-const penguenoGreeting = () =>
- greetings[Math.floor(Math.random() * greetings.length)];
+const greetings = ['hewwo :D', 'hiya cutie', 'boop!', 'sending virtual hugs!', 'stay pawsitive'];
+const penguenoGreeting = () => greetings[Math.floor(Math.random() * greetings.length)];
export class PenguenoRequest extends Request {
- private constructor(
- _input: Request,
- public readonly id: string,
- public readonly at: Date,
- ) {
- super(_input);
- }
+ private constructor(
+ _input: Request,
+ public readonly id: string,
+ public readonly at: Date,
+ ) {
+ super(_input);
+ }
- public baseResponseHeaders(): Record<string, string> {
- const ServerRequestTime = this.at.getTime();
- const ServerResponseTime = Date.now();
- const DeltaTime = ServerResponseTime - ServerRequestTime;
- const RequestId = this.id;
+ public baseResponseHeaders(): Record<string, string> {
+ const ServerRequestTime = this.at.getTime();
+ const ServerResponseTime = Date.now();
+ const DeltaTime = ServerResponseTime - ServerRequestTime;
+ const RequestId = this.id;
- return Object.entries({
- RequestId,
- ServerRequestTime,
- ServerResponseTime,
- DeltaTime,
- Hai: penguenoGreeting(),
- }).reduce((acc, [key, val]) => ({ ...acc, [key]: val.toString() }), {});
- }
+ return Object.entries({
+ RequestId,
+ ServerRequestTime,
+ ServerResponseTime,
+ DeltaTime,
+ Hai: penguenoGreeting(),
+ }).reduce((acc, [key, val]) => ({ ...acc, [key]: val!.toString() }), {});
+ }
- public static from(request: Request): LogMetricTraceable<PenguenoRequest> {
- const id = crypto.randomUUID();
- const url = new URL(request.url);
- const { pathname } = url;
- const logTraceable = LogTraceable.of(
- new PenguenoRequest(request, id, new Date()),
- ).bimap(
- TraceUtil.withTrace(`RequestId = ${id}, Method = ${request.method}, Path = ${pathname}`),
- );
- return LogMetricTraceable.ofLogTraceable(logTraceable);
- }
+ public static from(request: Request): LogMetricTraceable<PenguenoRequest> {
+ const id = crypto.randomUUID();
+ const url = new URL(request.url);
+ const { pathname } = url;
+ const logTraceable = LogTraceable.of(new PenguenoRequest(request, id, new Date())).bimap(
+ TraceUtil.withTrace(`RequestId = ${id}, Method = ${request.method}, Path = ${pathname}`),
+ );
+ return LogMetricTraceable.ofLogTraceable(logTraceable);
+ }
}
diff --git a/u/server/response.ts b/u/server/response.ts
index 629dbb5..18d70b5 100644
--- a/u/server/response.ts
+++ b/u/server/response.ts
@@ -1,86 +1,83 @@
import {
- type IEither,
- isEither,
- type ITraceable,
- Metric,
- type PenguenoRequest,
- type ServerTrace,
-} from "@emprespresso/pengueno";
+ type IEither,
+ isEither,
+ type ITraceable,
+ Metric,
+ type PenguenoRequest,
+ type ServerTrace,
+} from '@emprespresso/pengueno';
+export type BodyInit =
+ | ArrayBuffer
+ | AsyncIterable<Uint8Array>
+ | Blob
+ | FormData
+ | Iterable<Uint8Array>
+ | NodeJS.ArrayBufferView
+ | URLSearchParams
+ | null
+ | string;
export type ResponseBody = object | string;
-export type TResponseInit = ResponseInit & {
- status: number;
- headers?: Record<string, string>;
+export type TResponseInit = Omit<ResponseInit, 'headers'> & {
+ status: number;
+ headers?: Record<string, string>;
};
-const getResponse = (
- req: PenguenoRequest,
- opts: TResponseInit,
-): TResponseInit => {
- return {
- ...opts,
- headers: {
- ...req.baseResponseHeaders(),
- ...opts?.headers,
- "Content-Type":
- (opts?.headers?.["Content-Type"] ?? "text/plain") + "; charset=utf-8",
- },
- };
+const getResponse = (req: PenguenoRequest, opts: TResponseInit): ResponseInit => {
+ const baseHeaders = req.baseResponseHeaders();
+ const optHeaders = opts.headers || {};
+
+ return {
+ ...opts,
+ headers: {
+ ...baseHeaders,
+ ...optHeaders,
+ 'Content-Type': (optHeaders['Content-Type'] ?? 'text/plain') + '; charset=utf-8',
+ } as Record<string, string>,
+ };
};
-const ResponseCodeMetrics = [0, 1, 2, 3, 4, 5].map((x) =>
- Metric.fromName(`response.${x}xx`),
-);
-export const getResponseMetric = (status: number) => {
- const index = Math.floor(status / 100);
- return ResponseCodeMetrics[index] ?? ResponseCodeMetrics[5];
+const ResponseCodeMetrics = [0, 1, 2, 3, 4, 5].map((x) => Metric.fromName(`response.${x}xx`));
+export const getResponseMetrics = (status: number) => {
+ const index = Math.floor(status / 100);
+ return ResponseCodeMetrics.map((metric, i) => metric.count.withValue(i === index ? 1.0 : 0.0));
};
export class PenguenoResponse extends Response {
- constructor(
- req: ITraceable<PenguenoRequest, ServerTrace>,
- msg: BodyInit,
- opts: TResponseInit,
- ) {
- const responseOpts = getResponse(req.get(), opts);
- const resMetric = getResponseMetric(opts.status);
- req.trace.trace(resMetric.count.withValue(1.0));
- responseOpts.headers;
- super(msg, responseOpts);
- }
+ constructor(req: ITraceable<PenguenoRequest, ServerTrace>, msg: BodyInit, opts: TResponseInit) {
+ const responseOpts = getResponse(req.get(), opts);
+ for (const metric of getResponseMetrics(opts.status)) {
+ req.trace.trace(metric);
+ }
+ super(msg, responseOpts);
+ }
}
export class JsonResponse extends PenguenoResponse {
- constructor(
- req: ITraceable<PenguenoRequest, ServerTrace>,
- e: BodyInit | IEither<ResponseBody, ResponseBody>,
- opts: TResponseInit,
- ) {
- const optsWithJsonContentType = {
- ...opts,
- headers: {
- ...opts?.headers,
- "Content-Type": "application/json",
- },
- };
- if (isEither<ResponseBody, ResponseBody>(e)) {
- super(
- req,
- JSON.stringify(
- e.fold(({ isLeft, value }) =>
- isLeft ? { error: value } : { ok: value },
- ),
- ),
- optsWithJsonContentType,
- );
- return;
+ constructor(
+ req: ITraceable<PenguenoRequest, ServerTrace>,
+ e: BodyInit | IEither<ResponseBody, ResponseBody>,
+ opts: TResponseInit,
+ ) {
+ const optsWithJsonContentType: TResponseInit = {
+ ...opts,
+ headers: {
+ ...opts.headers,
+ 'Content-Type': 'application/json',
+ },
+ };
+ if (isEither<ResponseBody, ResponseBody>(e)) {
+ super(
+ req,
+ JSON.stringify(e.fold(({ isLeft, value }) => (isLeft ? { error: value } : { ok: value }))),
+ optsWithJsonContentType,
+ );
+ return;
+ }
+ super(
+ req,
+ JSON.stringify(Math.floor(opts.status / 100) > 4 ? { error: e } : { ok: e }),
+ optsWithJsonContentType,
+ );
}
- super(
- req,
- JSON.stringify(
- Math.floor(opts.status / 100) > 4 ? { error: e } : { ok: e },
- ),
- optsWithJsonContentType,
- );
- }
}
diff --git a/u/trace/index.ts b/u/trace/index.ts
new file mode 100644
index 0000000..18da87a
--- /dev/null
+++ b/u/trace/index.ts
@@ -0,0 +1,5 @@
+export * from './itrace.js';
+export * from './util.js';
+export * from './logger.js';
+export * from './metrics.js';
+export * from './trace.js';
diff --git a/u/trace/itrace.ts b/u/trace/itrace.ts
index fcfbe32..8cf123a 100644
--- a/u/trace/itrace.ts
+++ b/u/trace/itrace.ts
@@ -1,90 +1,72 @@
-import type { Mapper, SideEffect, Supplier } from "@emprespresso/pengueno";
+import type { Mapper, SideEffect, Supplier } from '@emprespresso/pengueno';
// the "thing" every Trace writer must "trace()"
type BaseTraceWith = string;
export type ITraceWith<T> = BaseTraceWith | T;
export interface ITrace<TraceWith> {
- addTrace: Mapper<ITraceWith<TraceWith>, ITrace<TraceWith>>;
- trace: SideEffect<ITraceWith<TraceWith>>;
+ addTrace: Mapper<ITraceWith<TraceWith>, ITrace<TraceWith>>;
+ trace: SideEffect<ITraceWith<TraceWith>>;
}
export type ITraceableTuple<T, TraceWith> = [T, BaseTraceWith | TraceWith];
-export type ITraceableMapper<T, _T, TraceWith, W = ITraceable<T, TraceWith>> = (
- w: W,
-) => _T;
+export type ITraceableMapper<T, _T, TraceWith, W = ITraceable<T, TraceWith>> = (w: W) => _T;
export interface ITraceable<T, Trace = BaseTraceWith> {
- readonly trace: ITrace<Trace>;
- get: Supplier<T>;
- move: <_T>(t: _T) => ITraceable<_T, Trace>;
- map: <_T>(mapper: ITraceableMapper<T, _T, Trace>) => ITraceable<_T, Trace>;
- bimap: <_T>(
- mapper: ITraceableMapper<
- T,
- ITraceableTuple<_T, Array<Trace> | Trace>,
- Trace
- >,
- ) => ITraceable<_T, Trace>;
- peek: (peek: ITraceableMapper<T, void, Trace>) => ITraceable<T, Trace>;
- flatMap: <_T>(
- mapper: ITraceableMapper<T, ITraceable<_T, Trace>, Trace>,
- ) => ITraceable<_T, Trace>;
- flatMapAsync<_T>(
- mapper: ITraceableMapper<T, Promise<ITraceable<_T, Trace>>, Trace>,
- ): ITraceable<Promise<_T>, Trace>;
+ readonly trace: ITrace<Trace>;
+ get: Supplier<T>;
+ move: <_T>(t: _T) => ITraceable<_T, Trace>;
+ map: <_T>(mapper: ITraceableMapper<T, _T, Trace>) => ITraceable<_T, Trace>;
+ bimap: <_T>(mapper: ITraceableMapper<T, ITraceableTuple<_T, Array<Trace> | Trace>, Trace>) => ITraceable<_T, Trace>;
+ peek: (peek: ITraceableMapper<T, void, Trace>) => ITraceable<T, Trace>;
+ flatMap: <_T>(mapper: ITraceableMapper<T, ITraceable<_T, Trace>, Trace>) => ITraceable<_T, Trace>;
+ flatMapAsync<_T>(
+ mapper: ITraceableMapper<T, Promise<ITraceable<_T, Trace>>, Trace>,
+ ): ITraceable<Promise<_T>, Trace>;
}
export class TraceableImpl<T, TraceWith> implements ITraceable<T, TraceWith> {
- protected constructor(
- private readonly item: T,
- public readonly trace: ITrace<TraceWith>,
- ) {}
+ protected constructor(
+ private readonly item: T,
+ public readonly trace: ITrace<TraceWith>,
+ ) {}
- public map<_T>(mapper: ITraceableMapper<T, _T, TraceWith>) {
- const result = mapper(this);
- return new TraceableImpl(result, this.trace);
- }
+ public map<_T>(mapper: ITraceableMapper<T, _T, TraceWith>) {
+ const result = mapper(this);
+ return new TraceableImpl(result, this.trace);
+ }
- public flatMap<_T>(
- mapper: ITraceableMapper<T, ITraceable<_T, TraceWith>, TraceWith>,
- ): ITraceable<_T, TraceWith> {
- return mapper(this);
- }
+ public flatMap<_T>(mapper: ITraceableMapper<T, ITraceable<_T, TraceWith>, TraceWith>): ITraceable<_T, TraceWith> {
+ return mapper(this);
+ }
- public flatMapAsync<_T>(
- mapper: ITraceableMapper<T, Promise<ITraceable<_T, TraceWith>>, TraceWith>,
- ): ITraceable<Promise<_T>, TraceWith> {
- return new TraceableImpl(
- mapper(this).then((t) => t.get()),
- this.trace,
- );
- }
+ public flatMapAsync<_T>(
+ mapper: ITraceableMapper<T, Promise<ITraceable<_T, TraceWith>>, TraceWith>,
+ ): ITraceable<Promise<_T>, TraceWith> {
+ return new TraceableImpl(
+ mapper(this).then((t) => t.get()),
+ this.trace,
+ );
+ }
- public peek(peek: ITraceableMapper<T, void, TraceWith>) {
- peek(this);
- return this;
- }
+ public peek(peek: ITraceableMapper<T, void, TraceWith>) {
+ peek(this);
+ return this;
+ }
- public move<_T>(t: _T): ITraceable<_T, TraceWith> {
- return this.map(() => t);
- }
+ public move<_T>(t: _T): ITraceable<_T, TraceWith> {
+ return this.map(() => t);
+ }
- public bimap<_T>(
- mapper: ITraceableMapper<
- T,
- ITraceableTuple<_T, Array<TraceWith> | TraceWith>,
- TraceWith
- >,
- ) {
- const [item, trace] = mapper(this);
- const traces = Array.isArray(trace) ? trace : [trace];
- return new TraceableImpl(
- item,
- traces.reduce((trace, _trace) => trace.addTrace(_trace), this.trace),
- );
- }
+ public bimap<_T>(mapper: ITraceableMapper<T, ITraceableTuple<_T, Array<TraceWith> | TraceWith>, TraceWith>) {
+ const [item, trace] = mapper(this);
+ const traces = Array.isArray(trace) ? trace : [trace];
+ return new TraceableImpl(
+ item,
+ traces.reduce((trace, _trace) => trace.addTrace(_trace), this.trace),
+ );
+ }
- public get() {
- return this.item;
- }
+ public get() {
+ return this.item;
+ }
}
diff --git a/u/trace/logger.ts b/u/trace/logger.ts
index d8392eb..91432fe 100644
--- a/u/trace/logger.ts
+++ b/u/trace/logger.ts
@@ -1,112 +1,95 @@
-import {
- isDebug,
- type ITrace,
- type ITraceWith,
- type Supplier,
-} from "@emprespresso/pengueno";
+import { isDebug, type ITrace, type ITraceWith, type Supplier } from '@emprespresso/pengueno';
export type LogTraceSupplier = ITraceWith<Supplier<string> | Error>;
const defaultTrace = () => `TimeStamp = ${new Date().toISOString()}`;
export class LogTrace implements ITrace<LogTraceSupplier> {
- constructor(
- private readonly logger: ILogger = new LoggerImpl(),
- private readonly traces: Array<LogTraceSupplier> = [defaultTrace],
- private readonly defaultLevel: LogLevel = LogLevel.INFO,
- private readonly allowedLevels: Supplier<
- Array<LogLevel>
- > = defaultAllowedLevels,
- ) {}
+ constructor(
+ private readonly logger: ILogger = new LoggerImpl(),
+ private readonly traces: Array<LogTraceSupplier> = [defaultTrace],
+ private readonly defaultLevel: LogLevel = LogLevel.INFO,
+ private readonly allowedLevels: Supplier<Array<LogLevel>> = defaultAllowedLevels,
+ ) {}
- public addTrace(trace: LogTraceSupplier): ITrace<LogTraceSupplier> {
- return new LogTrace(
- this.logger,
- this.traces.concat(trace),
- this.defaultLevel,
- this.allowedLevels,
- );
- }
+ public addTrace(trace: LogTraceSupplier): ITrace<LogTraceSupplier> {
+ return new LogTrace(this.logger, this.traces.concat(trace), this.defaultLevel, this.allowedLevels);
+ }
- public trace(trace: LogTraceSupplier) {
- const { traces, level: _level } = this.foldTraces(
- this.traces.concat(trace),
- );
- if (!this.allowedLevels().includes(_level)) return;
+ public trace(trace: LogTraceSupplier) {
+ const { traces, level: _level } = this.foldTraces(this.traces.concat(trace));
+ if (!this.allowedLevels().includes(_level)) return;
- const level = _level === LogLevel.UNKNOWN ? this.defaultLevel : _level;
- this.logger.log(level, ...traces);
- }
+ const level = _level === LogLevel.UNKNOWN ? this.defaultLevel : _level;
+ this.logger.log(level, ...traces);
+ }
- private foldTraces(_traces: Array<LogTraceSupplier>) {
- const _logTraces = _traces.map((trace) =>
- typeof trace === "function" ? trace() : trace,
- );
- const _level = _logTraces
- .filter((trace) => isLogLevel(trace))
- .reduce((acc, level) => Math.max(logLevelOrder.indexOf(level), acc), -1);
- const level = logLevelOrder[_level] ?? LogLevel.UNKNOWN;
+ private foldTraces(_traces: Array<LogTraceSupplier>) {
+ const _logTraces = _traces.map((trace) => (typeof trace === 'function' ? trace() : trace));
+ const _level = _logTraces
+ .filter((trace) => isLogLevel(trace))
+ .reduce((acc, level) => Math.max(logLevelOrder.indexOf(level), acc), -1);
+ const level = logLevelOrder[_level] ?? LogLevel.UNKNOWN;
- const traces = _logTraces.filter((trace) => !isLogLevel(trace)).map((trace) => {
- if (typeof trace === 'object') {
- return `TracedException.Name = ${trace.name}, TracedException.Message = ${trace.message}, TracedException.Stack = ${trace.stack}`
- }
- return trace;
- });
- return {
- level,
- traces,
- };
- }
+ const traces = _logTraces
+ .filter((trace) => !isLogLevel(trace))
+ .map((trace) => {
+ if (typeof trace === 'object') {
+ return `TracedException.Name = ${trace.name}, TracedException.Message = ${trace.message}, TracedException.Stack = ${trace.stack}`;
+ }
+ return trace;
+ });
+ return {
+ level,
+ traces,
+ };
+ }
}
export enum LogLevel {
- UNKNOWN = "UNKNOWN",
- INFO = "INFO",
- WARN = "WARN",
- DEBUG = "DEBUG",
- ERROR = "ERROR",
- SYS = "SYS",
+ UNKNOWN = 'UNKNOWN',
+ INFO = 'INFO',
+ WARN = 'WARN',
+ DEBUG = 'DEBUG',
+ ERROR = 'ERROR',
+ SYS = 'SYS',
}
-const logLevelOrder: Array<LogLevel> = [
- LogLevel.DEBUG,
- LogLevel.INFO,
- LogLevel.WARN,
- LogLevel.ERROR,
- LogLevel.SYS,
-];
+const logLevelOrder: Array<LogLevel> = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.SYS];
export const isLogLevel = (l: unknown): l is LogLevel =>
- typeof l === "string" && logLevelOrder.some((level) => level === l);
-
+ typeof l === 'string' && logLevelOrder.some((level) => level === l);
const defaultAllowedLevels = () =>
- [
- LogLevel.UNKNOWN,
- ...(isDebug() ? [LogLevel.DEBUG] : []),
- LogLevel.INFO,
- LogLevel.WARN,
- LogLevel.ERROR,
- LogLevel.SYS,
- ] as Array<LogLevel>;
+ [
+ LogLevel.UNKNOWN,
+ ...(isDebug() ? [LogLevel.DEBUG] : []),
+ LogLevel.INFO,
+ LogLevel.WARN,
+ LogLevel.ERROR,
+ LogLevel.SYS,
+ ] as Array<LogLevel>;
export interface ILogger {
- readonly log: (level: LogLevel, ...args: string[]) => void;
+ readonly log: (level: LogLevel, ...args: string[]) => void;
}
class LoggerImpl implements ILogger {
private readonly textEncoder = new TextEncoder();
public log(level: LogLevel, ...trace: string[]) {
- const message = JSON.stringify({
- level,
- trace,
- }, null, 4);
+ const message = JSON.stringify(
+ {
+ level,
+ trace,
+ },
+ null,
+ 4,
+ );
const styled = `${this.getStyle(level)}${message}${ANSI.RESET}\n`;
- this.getStream(level).writeSync(this.textEncoder.encode(styled));
+ this.getStream(level)(this.textEncoder.encode(styled));
}
private getStream(level: LogLevel) {
if (level === LogLevel.ERROR) {
- return Deno.stderr;
+ return console.error;
}
- return Deno.stdout;
+ return console.log;
}
private getStyle(level: LogLevel) {
@@ -127,17 +110,17 @@ class LoggerImpl implements ILogger {
}
export const ANSI = {
- RESET: "\x1b[0m",
- BOLD: "\x1b[1m",
- DIM: "\x1b[2m",
- RED: "\x1b[31m",
- GREEN: "\x1b[32m",
- YELLOW: "\x1b[33m",
- BLUE: "\x1b[34m",
- MAGENTA: "\x1b[35m",
- CYAN: "\x1b[36m",
- WHITE: "\x1b[37m",
- BRIGHT_RED: "\x1b[91m",
- BRIGHT_YELLOW: "\x1b[93m",
- GRAY: "\x1b[90m",
+ RESET: '\x1b[0m',
+ BOLD: '\x1b[1m',
+ DIM: '\x1b[2m',
+ RED: '\x1b[31m',
+ GREEN: '\x1b[32m',
+ YELLOW: '\x1b[33m',
+ BLUE: '\x1b[34m',
+ MAGENTA: '\x1b[35m',
+ CYAN: '\x1b[36m',
+ WHITE: '\x1b[37m',
+ BRIGHT_RED: '\x1b[91m',
+ BRIGHT_YELLOW: '\x1b[93m',
+ GRAY: '\x1b[90m',
};
diff --git a/u/trace/metrics.ts b/u/trace/metrics.ts
index 822fc38..2301afd 100644
--- a/u/trace/metrics.ts
+++ b/u/trace/metrics.ts
@@ -1,151 +1,140 @@
import {
- isObject,
- type ITrace,
- type ITraceWith,
- type Mapper,
- type SideEffect,
- type Supplier,
-} from "@emprespresso/pengueno";
+ isObject,
+ type ITrace,
+ type ITraceWith,
+ type Mapper,
+ type SideEffect,
+ type Supplier,
+} from '@emprespresso/pengueno';
export enum Unit {
- COUNT = "COUNT",
- MILLISECONDS = "MILLISECONDS",
+ COUNT = 'COUNT',
+ MILLISECONDS = 'MILLISECONDS',
}
export interface IMetric {
- readonly count: IEmittableMetric;
- readonly time: IEmittableMetric;
- readonly failure?: IMetric;
- readonly success?: IMetric;
- readonly warn?: IMetric;
- readonly children: Supplier<Array<IMetric>>;
-
- readonly _tag: "IMetric";
+ readonly count: IEmittableMetric;
+ readonly time: IEmittableMetric;
+ readonly failure: undefined | IMetric;
+ readonly success: undefined | IMetric;
+ readonly warn: undefined | IMetric;
+ readonly children: Supplier<Array<IMetric>>;
+
+ readonly _tag: 'IMetric';
}
-export const isIMetric = (t: unknown): t is IMetric =>
- isObject(t) && "_tag" in t && t._tag === "IMetric";
+export const isIMetric = (t: unknown): t is IMetric => isObject(t) && '_tag' in t && t._tag === 'IMetric';
export interface IEmittableMetric {
- readonly name: string;
- readonly unit: Unit;
- withValue: Mapper<number, MetricValue>;
+ readonly name: string;
+ readonly unit: Unit;
+ withValue: Mapper<number, MetricValue>;
}
export class EmittableMetric implements IEmittableMetric {
- constructor(
- public readonly name: string,
- public readonly unit: Unit,
- ) {}
-
- public withValue(value: number): MetricValue {
- return {
- name: this.name,
- unit: this.unit,
- emissionTimestamp: Date.now(),
- value,
- _tag: "MetricValue",
- };
- }
+ constructor(
+ public readonly name: string,
+ public readonly unit: Unit,
+ ) {}
+
+ public withValue(value: number): MetricValue {
+ return {
+ name: this.name,
+ unit: this.unit,
+ emissionTimestamp: Date.now(),
+ value,
+ _tag: 'MetricValue',
+ };
+ }
}
export class Metric implements IMetric {
- constructor(
- public readonly count: IEmittableMetric,
- public readonly time: IEmittableMetric,
- public readonly failure?: Metric,
- public readonly success?: Metric,
- public readonly warn?: Metric,
- public readonly _tag: "IMetric" = "IMetric",
- ) {}
-
- public children() {
- return [this.failure, this.success, this.warn].filter(
- (x) => x,
- ) as IMetric[];
- }
-
- static fromName(name: string, addChildren = true): Metric {
- return new Metric(
- new EmittableMetric(`${name}.count`, Unit.COUNT),
- new EmittableMetric(`${name}.elapsed`, Unit.MILLISECONDS),
- addChildren ? Metric.fromName(`${name}.failure`, false) : undefined,
- addChildren ? Metric.fromName(`${name}.success`, false) : undefined,
- addChildren ? Metric.fromName(`${name}.warn`, false) : undefined,
- );
- }
+ constructor(
+ public readonly count: IEmittableMetric,
+ public readonly time: IEmittableMetric,
+ public readonly failure: undefined | Metric = undefined,
+ public readonly success: undefined | Metric = undefined,
+ public readonly warn: undefined | Metric = undefined,
+ public readonly _tag: 'IMetric' = 'IMetric',
+ ) {}
+
+ public children() {
+ return [this.failure, this.success, this.warn].filter((x) => x) as IMetric[];
+ }
+
+ static fromName(name: string, addChildren = true): Metric {
+ return new Metric(
+ new EmittableMetric(`${name}.count`, Unit.COUNT),
+ new EmittableMetric(`${name}.elapsed`, Unit.MILLISECONDS),
+ addChildren ? Metric.fromName(`${name}.failure`, false) : undefined,
+ addChildren ? Metric.fromName(`${name}.success`, false) : undefined,
+ addChildren ? Metric.fromName(`${name}.warn`, false) : undefined,
+ );
+ }
}
export interface MetricValue {
- readonly name: string;
- readonly unit: Unit;
- readonly value: number;
- readonly emissionTimestamp: number;
- readonly _tag: "MetricValue";
+ readonly name: string;
+ readonly unit: Unit;
+ readonly value: number;
+ readonly emissionTimestamp: number;
+ readonly _tag: 'MetricValue';
}
-export const isMetricValue = (t: unknown): t is MetricValue =>
- isObject(t) && "_tag" in t && t._tag === "MetricValue";
+export const isMetricValue = (t: unknown): t is MetricValue => isObject(t) && '_tag' in t && t._tag === 'MetricValue';
-export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier =>
- isMetricValue(t) || isIMetric(t);
+export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier => isMetricValue(t) || isIMetric(t);
-export type MetricsTraceSupplier = ITraceWith<
- IMetric | MetricValue | undefined
->;
+export type MetricsTraceSupplier = ITraceWith<IMetric | MetricValue | undefined>;
type MetricTracingTuple = [IMetric, Date];
export class MetricsTrace implements ITrace<MetricsTraceSupplier> {
- constructor(
- private readonly metricConsumer: SideEffect<Array<MetricValue>>,
- private readonly tracing: Array<MetricTracingTuple> = [],
- private readonly flushed: Set<IMetric> = new Set(),
- ) {}
-
- public addTrace(trace: MetricsTraceSupplier) {
- if (!isIMetric(trace)) return this;
- return new MetricsTrace(this.metricConsumer)._nowTracing(trace);
- }
-
- public trace(metric: MetricsTraceSupplier) {
- if (typeof metric === "undefined" || typeof metric === "string")
- return this;
- if (isMetricValue(metric)) {
- this.metricConsumer([metric]);
- return this;
+ constructor(
+ private readonly metricConsumer: SideEffect<Array<MetricValue>>,
+ private readonly tracing: Array<MetricTracingTuple> = [],
+ private readonly flushed: Set<IMetric> = new Set(),
+ ) {}
+
+ public addTrace(trace: MetricsTraceSupplier) {
+ if (!isIMetric(trace)) return this;
+ return new MetricsTrace(this.metricConsumer)._nowTracing(trace);
}
- const foundMetricValues = this.tracing
- .flatMap(([tracing, startedTracing]) =>
- [tracing, ...tracing.children()]
- .filter((_tracing) => metric === _tracing)
- .flatMap((metric) => [
- this.addMetric(metric, startedTracing),
- this.addMetric(tracing, startedTracing),
- ]),
- )
- .flatMap((values) => values);
-
- if (foundMetricValues.length === 0) {
- return this._nowTracing(metric);
+ public trace(metric: MetricsTraceSupplier) {
+ if (typeof metric === 'undefined' || typeof metric === 'string') return this;
+ if (isMetricValue(metric)) {
+ this.metricConsumer([metric]);
+ return this;
+ }
+
+ const foundMetricValues = this.tracing
+ .flatMap(([tracing, startedTracing]) =>
+ [tracing, ...tracing.children()]
+ .filter((_tracing) => metric === _tracing)
+ .flatMap((metric) => [
+ this.addMetric(metric, startedTracing),
+ this.addMetric(tracing, startedTracing),
+ ]),
+ )
+ .flatMap((values) => values);
+
+ if (foundMetricValues.length === 0) {
+ return this._nowTracing(metric);
+ }
+
+ this.metricConsumer(foundMetricValues);
+ return this;
}
- this.metricConsumer(foundMetricValues);
- return this;
- }
+ private addMetric(metric: IMetric, startedTracing: Date): Array<MetricValue> {
+ if (this.flushed.has(metric)) {
+ return [];
+ }
- private addMetric(metric: IMetric, startedTracing: Date): Array<MetricValue> {
- if (this.flushed.has(metric)) {
- return [];
+ this.flushed.add(metric);
+ return [metric.count.withValue(1.0), metric.time.withValue(Date.now() - startedTracing.getTime())];
}
- this.flushed.add(metric);
- return [
- metric.count.withValue(1.0),
- metric.time.withValue(Date.now() - startedTracing.getTime()),
- ];
- }
-
- private _nowTracing(metric?: IMetric): MetricsTrace {
- if (!metric) return this;
- this.tracing.push([metric, new Date()]);
- return this;
- }
+ private _nowTracing(metric?: IMetric): MetricsTrace {
+ if (!metric) return this;
+ this.tracing.push([metric, new Date()]);
+ return this;
+ }
}
diff --git a/u/trace/mod.ts b/u/trace/mod.ts
deleted file mode 100644
index 0f9b61b..0000000
--- a/u/trace/mod.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export * from "./itrace.ts";
-export * from "./util.ts";
-export * from "./logger.ts";
-export * from "./metrics.ts";
-export * from "./trace.ts";
diff --git a/u/trace/trace.ts b/u/trace/trace.ts
index 5629c28..acc116f 100644
--- a/u/trace/trace.ts
+++ b/u/trace/trace.ts
@@ -1,86 +1,69 @@
import {
- isMetricsTraceSupplier,
- type ITrace,
- type ITraceable,
- type ITraceWith,
- LogLevel,
- LogTrace,
- type LogTraceSupplier,
- MetricsTrace,
- type MetricsTraceSupplier,
- type MetricValue,
- TraceableImpl,
-} from "@emprespresso/pengueno";
+ isMetricsTraceSupplier,
+ type ITrace,
+ type ITraceable,
+ type ITraceWith,
+ LogLevel,
+ LogTrace,
+ type LogTraceSupplier,
+ MetricsTrace,
+ type MetricsTraceSupplier,
+ type MetricValue,
+ TraceableImpl,
+} from '@emprespresso/pengueno';
export class LogTraceable<T> extends TraceableImpl<T, LogTraceSupplier> {
- public static LogTrace = new LogTrace();
- static of<T>(t: T) {
- return new LogTraceable(t, LogTraceable.LogTrace);
- }
+ public static LogTrace = new LogTrace();
+ static of<T>(t: T) {
+ return new LogTraceable(t, LogTraceable.LogTrace);
+ }
}
-const getEmbeddedMetricConsumer =
- (logTrace: ITrace<LogTraceSupplier>) =>
- (metrics: Array<MetricValue>) =>
+const getEmbeddedMetricConsumer = (logTrace: ITrace<LogTraceSupplier>) => (metrics: Array<MetricValue>) =>
logTrace.addTrace(LogLevel.SYS).trace(`Metrics = <metrics>${JSON.stringify(metrics)}</metrics>`);
-export class EmbeddedMetricsTraceable<T> extends TraceableImpl<
- T,
- MetricsTraceSupplier
-> {
- public static MetricsTrace = new MetricsTrace(
- getEmbeddedMetricConsumer(LogTraceable.LogTrace),
- );
+export class EmbeddedMetricsTraceable<T> extends TraceableImpl<T, MetricsTraceSupplier> {
+ public static MetricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(LogTraceable.LogTrace));
- static of<T>(t: T, metricsTrace = EmbeddedMetricsTraceable.MetricsTrace) {
- return new EmbeddedMetricsTraceable(t, metricsTrace);
- }
+ static of<T>(t: T, metricsTrace = EmbeddedMetricsTraceable.MetricsTrace) {
+ return new EmbeddedMetricsTraceable(t, metricsTrace);
+ }
}
-export type LogMetricTraceSupplier = ITraceWith<
- LogTraceSupplier | MetricsTraceSupplier
->;
+export type LogMetricTraceSupplier = ITraceWith<LogTraceSupplier | MetricsTraceSupplier>;
export class LogMetricTrace implements ITrace<LogMetricTraceSupplier> {
- constructor(
- private logTrace: ITrace<LogTraceSupplier>,
- private metricsTrace: ITrace<MetricsTraceSupplier>,
- ) {}
+ constructor(
+ private logTrace: ITrace<LogTraceSupplier>,
+ private metricsTrace: ITrace<MetricsTraceSupplier>,
+ ) {}
- public addTrace(
- trace: LogTraceSupplier | MetricsTraceSupplier,
- ): LogMetricTrace {
- if (isMetricsTraceSupplier(trace)) {
- this.metricsTrace = this.metricsTrace.addTrace(trace);
- return this;
+ public addTrace(trace: LogTraceSupplier | MetricsTraceSupplier): LogMetricTrace {
+ if (isMetricsTraceSupplier(trace)) {
+ this.metricsTrace = this.metricsTrace.addTrace(trace);
+ return this;
+ }
+ this.logTrace = this.logTrace.addTrace(trace);
+ return this;
}
- this.logTrace = this.logTrace.addTrace(trace);
- return this;
- }
- public trace(trace: LogTraceSupplier | MetricsTraceSupplier) {
- if (isMetricsTraceSupplier(trace)) {
- this.metricsTrace.trace(trace);
- return this;
+ public trace(trace: LogTraceSupplier | MetricsTraceSupplier) {
+ if (isMetricsTraceSupplier(trace)) {
+ this.metricsTrace.trace(trace);
+ return this;
+ }
+ this.logTrace.trace(trace);
+ return this;
}
- this.logTrace.trace(trace);
- return this;
- }
}
-export class LogMetricTraceable<T> extends TraceableImpl<
- T,
- MetricsTraceSupplier | LogTraceSupplier
-> {
- static ofLogTraceable<T>(t: ITraceable<T, LogTraceSupplier>) {
- const metricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(t.trace));
- return new LogMetricTraceable(
- t.get(),
- new LogMetricTrace(t.trace, metricsTrace),
- );
- }
+export class LogMetricTraceable<T> extends TraceableImpl<T, MetricsTraceSupplier | LogTraceSupplier> {
+ static ofLogTraceable<T>(t: ITraceable<T, LogTraceSupplier>) {
+ const metricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(t.trace));
+ return new LogMetricTraceable(t.get(), new LogMetricTrace(t.trace, metricsTrace));
+ }
- static of<T>(t: T) {
- const logTrace = LogTraceable.of(t);
- return LogMetricTraceable.ofLogTraceable(logTrace);
- }
+ static of<T>(t: T) {
+ const logTrace = LogTraceable.of(t);
+ return LogMetricTraceable.ofLogTraceable(logTrace);
+ }
}
diff --git a/u/trace/util.ts b/u/trace/util.ts
index e2200b9..db1db63 100644
--- a/u/trace/util.ts
+++ b/u/trace/util.ts
@@ -1,47 +1,45 @@
import {
-ANSI,
- type Callable,
- type IMetric,
- type ITraceableMapper,
- type ITraceableTuple,
- type MetricsTraceSupplier,
-} from "@emprespresso/pengueno";
+ ANSI,
+ type Callable,
+ type IMetric,
+ type ITraceableMapper,
+ type ITraceableTuple,
+ type MetricsTraceSupplier,
+} from '@emprespresso/pengueno';
export class TraceUtil {
- static withTrace<T, Trace>(
- trace: string,
- ansi?: Array<keyof typeof ANSI>
- ): ITraceableMapper<T, ITraceableTuple<T, Trace | Array<Trace>>, Trace> {
- if (ansi) {
- return (t) => [t.get(), `${ansi.join("")}${trace}${ANSI.RESET}`];
- }
- return (t) => [t.get(), trace];
- }
+ static withTrace<T, Trace>(
+ trace: string,
+ ansi?: Array<keyof typeof ANSI>,
+ ): ITraceableMapper<T, ITraceableTuple<T, Trace | Array<Trace>>, Trace> {
+ if (ansi) {
+ return (t) => [t.get(), `${ansi.join('')}${trace}${ANSI.RESET}`];
+ }
+ return (t) => [t.get(), trace];
+ }
- static withMetricTrace<T, Trace extends MetricsTraceSupplier>(
- metric: IMetric,
- ): ITraceableMapper<T, ITraceableTuple<T, Trace | Array<Trace>>, Trace> {
- return (t) => [t.get(), metric as Trace];
- }
+ static withMetricTrace<T, Trace extends MetricsTraceSupplier>(
+ metric: IMetric,
+ ): ITraceableMapper<T, ITraceableTuple<T, Trace | Array<Trace>>, Trace> {
+ return (t) => [t.get(), metric as Trace];
+ }
- static withFunctionTrace<F extends Callable, T, Trace>(
- f: F,
- ): ITraceableMapper<T, ITraceableTuple<T, Trace | Array<Trace>>, Trace> {
- return TraceUtil.withTrace(`fn.${f.name}`);
- }
+ static withFunctionTrace<F extends Callable, T, Trace>(
+ f: F,
+ ): ITraceableMapper<T, ITraceableTuple<T, Trace | Array<Trace>>, Trace> {
+ return TraceUtil.withTrace(`fn.${f.name}`);
+ }
- static withClassTrace<C extends object, T, Trace>(
- c: C,
- ): ITraceableMapper<T, ITraceableTuple<T, Trace | Array<Trace>>, Trace> {
- return TraceUtil.withTrace(`class.${c.constructor.name}`);
- }
+ static withClassTrace<C extends object, T, Trace>(
+ c: C,
+ ): ITraceableMapper<T, ITraceableTuple<T, Trace | Array<Trace>>, Trace> {
+ return TraceUtil.withTrace(`class.${c.constructor.name}`);
+ }
- static promiseify<T, U, Trace>(
- mapper: ITraceableMapper<T, U, Trace>,
- ): ITraceableMapper<Promise<T>, Promise<U>, Trace> {
- return (traceablePromise) =>
- traceablePromise
- .flatMapAsync(async (t) => t.move(await t.get()).map(mapper))
- .get();
- }
+ static promiseify<T, U, Trace>(
+ mapper: ITraceableMapper<T, U, Trace>,
+ ): ITraceableMapper<Promise<T>, Promise<U>, Trace> {
+ return (traceablePromise) =>
+ traceablePromise.flatMapAsync(async (t) => t.move(await t.get()).map(mapper)).get();
+ }
}
diff --git a/u/tsconfig.json b/u/tsconfig.json
new file mode 100644
index 0000000..80e5ff4
--- /dev/null
+++ b/u/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./",
+ "composite": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "noEmit": false,
+ "moduleResolution": "node"
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
+}
diff --git a/worker/deno.json b/worker/deno.json
deleted file mode 100644
index c908330..0000000
--- a/worker/deno.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "@emprespresso/ci_worker",
- "exports": "./mod.ts"
-}
diff --git a/worker/executor.ts b/worker/executor.ts
index ea79995..f4b7906 100644
--- a/worker/executor.ts
+++ b/worker/executor.ts
@@ -1,101 +1,86 @@
import {
- getStdout,
- type ITraceable,
- LogLevel,
- type LogMetricTraceSupplier,
- memoize,
- Metric,
- TraceUtil,
- validateExecutionEntries,
- Either,
- type IEither,
-} from "@emprespresso/pengueno";
-import type { Job, JobArgT, Pipeline } from "@emprespresso/ci_model";
+ getStdout,
+ type ITraceable,
+ LogLevel,
+ type LogMetricTraceSupplier,
+ memoize,
+ Metric,
+ TraceUtil,
+ validateExecutionEntries,
+ Either,
+ type IEither,
+} from '@emprespresso/pengueno';
+import type { Job, JobArgT, Pipeline } from '@emprespresso/ci_model';
// -- <job.exectuor> --
const jobTypeMetric = memoize((type: string) => Metric.fromName(`run.${type}`));
export const executeJob = (tJob: ITraceable<Job, LogMetricTraceSupplier>) =>
- tJob
- .bimap(TraceUtil.withMetricTrace(jobTypeMetric(tJob.get().type)))
- .peek((tJob) =>
- tJob.trace.trace(`let's do this little job ok!! ${tJob.get()}`),
- )
- .map((tJob) =>
- validateExecutionEntries(tJob.get().arguments)
- .mapLeft((badEntries) => {
- tJob.trace.addTrace(LogLevel.ERROR).trace(badEntries.toString());
- return new Error("invalid job arguments");
- })
- .flatMapAsync((args) =>
- getStdout(tJob.move(tJob.get().type), { env: args }),
- ),
- )
- .peek(
- TraceUtil.promiseify((q) =>
- q.trace.trace(
- q
- .get()
- .fold(
- ({ isLeft }) =>
- jobTypeMetric(tJob.get().type)[isLeft ? "failure" : "success"],
+ tJob
+ .bimap(TraceUtil.withMetricTrace(jobTypeMetric(tJob.get().type)))
+ .peek((tJob) => tJob.trace.trace(`let's do this little job ok!! ${tJob.get()}`))
+ .map((tJob) =>
+ validateExecutionEntries(tJob.get().arguments)
+ .mapLeft((badEntries) => {
+ tJob.trace.addTrace(LogLevel.ERROR).trace(badEntries.toString());
+ return new Error('invalid job arguments');
+ })
+ .flatMapAsync((args) => getStdout(tJob.move(tJob.get().type), { env: args })),
+ )
+ .peek(
+ TraceUtil.promiseify((q) =>
+ q.trace.trace(
+ q.get().fold(({ isLeft }) => jobTypeMetric(tJob.get().type)[isLeft ? 'failure' : 'success']),
+ ),
),
- ),
- ),
- )
- .get();
+ )
+ .get();
// -- </job.exectuor> --
// -- <pipeline.executor> --
-const pipelinesMetric = Metric.fromName("pipelines");
+const pipelinesMetric = Metric.fromName('pipelines');
export const executePipeline = (
- tPipeline: ITraceable<Pipeline, LogMetricTraceSupplier>,
- baseEnv?: JobArgT,
+ tPipeline: ITraceable<Pipeline, LogMetricTraceSupplier>,
+ baseEnv?: JobArgT,
): Promise<IEither<Error, void>> =>
- tPipeline
- .bimap(TraceUtil.withFunctionTrace(executePipeline))
- .bimap(TraceUtil.withMetricTrace(pipelinesMetric))
- .map(async (tJobs): Promise<IEither<Error, void>> => {
- for (const [i, serialStage] of tJobs.get().serialJobs.entries()) {
- tJobs.trace.trace(
- `executing stage ${i}. do your best little stage :>\n${serialStage}`,
- );
- const jobResults = await Promise.all(
- serialStage.parallelJobs.map((job) =>
- tJobs
- .bimap((_) => [job, `stage ${i}`])
- .map(
- (tJob) =>
- <Job>{
- ...tJob.get(),
- arguments: {
- ...baseEnv,
- ...tJob.get().arguments,
- },
- },
- )
- .map(executeJob)
- .peek(
- TraceUtil.promiseify((tEitherJobOutput) =>
- tEitherJobOutput
- .get()
- .mapRight((stdout) =>
- tEitherJobOutput.trace.addTrace("STDOUT").trace(stdout),
+ tPipeline
+ .bimap(TraceUtil.withFunctionTrace(executePipeline))
+ .bimap(TraceUtil.withMetricTrace(pipelinesMetric))
+ .map(async (tJobs): Promise<IEither<Error, void>> => {
+ for (const [i, serialStage] of tJobs.get().serialJobs.entries()) {
+ tJobs.trace.trace(`executing stage ${i}. do your best little stage :>\n${serialStage}`);
+ const jobResults = await Promise.all(
+ serialStage.parallelJobs.map((job) =>
+ tJobs
+ .bimap((_) => [job, `stage ${i}`])
+ .map(
+ (tJob) =>
+ <Job>{
+ ...tJob.get(),
+ arguments: {
+ ...baseEnv,
+ ...tJob.get().arguments,
+ },
+ },
+ )
+ .map(executeJob)
+ .peek(
+ TraceUtil.promiseify((tEitherJobOutput) =>
+ tEitherJobOutput
+ .get()
+ .mapRight((stdout) => tEitherJobOutput.trace.addTrace('STDOUT').trace(stdout)),
+ ),
+ )
+ .get(),
),
- ),
- )
- .get(),
- ),
- );
- const failures = jobResults.filter((e) =>
- e.fold(({ isLeft }) => isLeft),
- );
- if (failures.length > 0) {
- tJobs.trace.trace(pipelinesMetric.failure);
- return Either.left(new Error(failures.toString()));
- }
- }
- tJobs.trace.trace(pipelinesMetric.success);
- return Either.right(undefined);
- })
- .get();
+ );
+ const failures = jobResults.filter((e) => e.fold(({ isLeft }) => isLeft));
+ if (failures.length > 0) {
+ tJobs.trace.trace(pipelinesMetric.failure);
+ return Either.left(new Error(failures.toString()));
+ }
+ }
+ tJobs.trace.trace(pipelinesMetric.success);
+ return Either.right(undefined);
+ })
+ .get();
// -- </pipeline.executor> --
diff --git a/worker/index.ts b/worker/index.ts
new file mode 100644
index 0000000..9ad32b9
--- /dev/null
+++ b/worker/index.ts
@@ -0,0 +1,2 @@
+export * from './secret.js';
+export * from './executor.js';
diff --git a/worker/mod.ts b/worker/mod.ts
deleted file mode 100644
index 97980a8..0000000
--- a/worker/mod.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./secret.ts";
-export * from "./executor.ts";
diff --git a/worker/package.json b/worker/package.json
new file mode 100644
index 0000000..c49dfaf
--- /dev/null
+++ b/worker/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@emprespresso/ci_worker",
+ "version": "0.1.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ },
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "clean": "rm -rf dist",
+ "type-check": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@emprespresso/pengueno": "*",
+ "@emprespresso/ci_model": "*"
+ },
+ "files": [
+ "dist/**/*",
+ "package.json",
+ "README.md"
+ ]
+}
diff --git a/worker/scripts/ansible_playbook.ts b/worker/scripts/ansible_playbook.ts
index 0879dc5..c6d8f2c 100755
--- a/worker/scripts/ansible_playbook.ts
+++ b/worker/scripts/ansible_playbook.ts
@@ -1,113 +1,100 @@
-#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run --allow-read --allow-write
+#!/usr/bin/env node
import {
- Either,
- getRequiredEnvVars,
- getStdout,
- type IEither,
- LogTraceable,
- LogMetricTraceable,
- Metric,
- prependWith,
- TraceUtil,
-} from "@emprespresso/pengueno";
-import type { AnsiblePlaybookJob } from "@emprespresso/ci_model";
-import { Bitwarden, type SecureNote } from "@emprespresso/ci_worker";
+ Either,
+ getRequiredEnvVars,
+ getStdout,
+ type IEither,
+ LogTraceable,
+ LogMetricTraceable,
+ Metric,
+ prependWith,
+ TraceUtil,
+} from '@emprespresso/pengueno';
+import type { AnsiblePlaybookJob } from '@emprespresso/ci_model';
+import { Bitwarden, type SecureNote } from '@emprespresso/ci_worker';
+import { writeFile, mkdtemp } from 'fs/promises';
+import { join } from 'path';
+import { tmpdir } from 'os';
-const eitherJob = getRequiredEnvVars([
- "path",
- "playbooks",
-])
- .mapRight((baseArgs) => (
- <AnsiblePlaybookJob> {
- type: "ansible_playbook.ts",
- arguments: baseArgs,
- }
- ));
+const eitherJob = getRequiredEnvVars(['path', 'playbooks']).mapRight(
+ (baseArgs) =>
+ <AnsiblePlaybookJob>{
+ type: 'ansible_playbook.ts',
+ arguments: baseArgs,
+ },
+);
-const eitherVault = Bitwarden.getConfigFromEnvironment()
- .mapRight((config) => new Bitwarden(config));
+const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config));
-const playbookMetric = Metric.fromName("ansiblePlaybook.playbook");
-const _logJob = LogTraceable.of(eitherJob).bimap(TraceUtil.withTrace("ansible_playbook"));
-await LogMetricTraceable.ofLogTraceable(_logJob).bimap(TraceUtil.withMetricTrace(playbookMetric))
- .peek((tEitherJob) =>
- tEitherJob.trace.trace("starting ansible playbook job! (⑅˘꒳˘)")
- )
- .map((tEitherJob) =>
- tEitherJob.get().flatMapAsync((job) =>
- eitherVault.flatMapAsync(async (vault) => {
- const eitherKey = await vault.unlock(tEitherJob);
- return eitherKey.mapRight((key) => ({ job, key, vault }));
- })
+const playbookMetric = Metric.fromName('ansiblePlaybook.playbook');
+const _logJob = LogTraceable.of(eitherJob).bimap(TraceUtil.withTrace('ansible_playbook'));
+await LogMetricTraceable.ofLogTraceable(_logJob)
+ .bimap(TraceUtil.withMetricTrace(playbookMetric))
+ .peek((tEitherJob) => tEitherJob.trace.trace('starting ansible playbook job! (⑅˘꒳˘)'))
+ .map((tEitherJob) =>
+ tEitherJob.get().flatMapAsync((job) =>
+ eitherVault.flatMapAsync(async (vault) => {
+ const eitherKey = await vault.unlock(tEitherJob);
+ return eitherKey.mapRight((key) => ({ job, key, vault }));
+ }),
+ ),
)
- )
- .map(async (tEitherJobVault) => {
- tEitherJobVault.trace.trace(
- "getting ansible secwets uwu~",
- );
- const eitherJobVault = await tEitherJobVault.get();
-
- const eitherSshKey = await eitherJobVault
- .flatMapAsync(({ key, vault }) =>
- vault.fetchSecret<SecureNote>(tEitherJobVault, key, "ssh_key")
- );
- const eitherSshKeyFile = await eitherSshKey.mapRight(({ notes }) => notes)
- .flatMapAsync(saveToTempFile);
- const eitherAnsibleSecrets = await eitherJobVault
- .flatMapAsync(({ key, vault }) =>
- vault.fetchSecret<SecureNote>(tEitherJobVault, key, "ansible_playbooks")
- );
- const eitherAnsibleSecretsFile = await eitherAnsibleSecrets.mapRight((
- { notes },
- ) => notes).flatMapAsync(saveToTempFile);
+ .map(async (tEitherJobVault) => {
+ tEitherJobVault.trace.trace('getting ansible secwets uwu~');
+ const eitherJobVault = await tEitherJobVault.get();
- return eitherJobVault.flatMapAsync(async ({ job, vault, key }) => {
- const eitherLocked = await vault.lock(tEitherJobVault, key);
- return eitherLocked.flatMap((_locked) =>
- eitherSshKeyFile.flatMap((sshKeyFile) =>
- eitherAnsibleSecretsFile.mapRight((secretsFile) => ({
- job,
- sshKeyFile,
- secretsFile,
- }))
- )
- );
- });
- })
- .map(async (tEitherJobAndSecrets) => {
- const eitherJobAndSecrets = await tEitherJobAndSecrets.get();
- return eitherJobAndSecrets.flatMapAsync(
- ({ job, sshKeyFile, secretsFile }) => {
- const volumes = [
- `${job.arguments.path}:/ansible`,
- `${sshKeyFile}:/root/id_rsa`,
- `${secretsFile}:/ansible/secrets.yml`,
- ];
- const playbookCmd =
- `ansible-playbook -e @secrets.yml ${job.arguments.playbooks}`;
- const deployCmd = [
- "docker",
- "run",
- ...prependWith(volumes, "-v"),
- "willhallonline/ansible:latest",
- ...playbookCmd.split(" "),
- ];
- tEitherJobAndSecrets.trace.trace(
- `running ansible magic~ (◕ᴗ◕✿) ${deployCmd}`,
+ const eitherSshKey = await eitherJobVault.flatMapAsync(({ key, vault }) =>
+ vault.fetchSecret<SecureNote>(tEitherJobVault, key, 'ssh_key'),
);
- return tEitherJobAndSecrets.move(deployCmd).map(getStdout).get();
- },
- );
- })
- .get();
+ const eitherSshKeyFile = await eitherSshKey.mapRight(({ notes }) => notes).flatMapAsync(saveToTempFile);
+ const eitherAnsibleSecrets = await eitherJobVault.flatMapAsync(({ key, vault }) =>
+ vault.fetchSecret<SecureNote>(tEitherJobVault, key, 'ansible_playbooks'),
+ );
+ const eitherAnsibleSecretsFile = await eitherAnsibleSecrets
+ .mapRight(({ notes }) => notes)
+ .flatMapAsync(saveToTempFile);
+
+ return eitherJobVault.flatMapAsync(async ({ job, vault, key }) => {
+ const eitherLocked = await vault.lock(tEitherJobVault, key);
+ return eitherLocked.flatMap((_locked) =>
+ eitherSshKeyFile.flatMap((sshKeyFile) =>
+ eitherAnsibleSecretsFile.mapRight((secretsFile) => ({
+ job,
+ sshKeyFile,
+ secretsFile,
+ })),
+ ),
+ );
+ });
+ })
+ .map(async (tEitherJobAndSecrets) => {
+ const eitherJobAndSecrets = await tEitherJobAndSecrets.get();
+ return eitherJobAndSecrets.flatMapAsync(({ job, sshKeyFile, secretsFile }) => {
+ const volumes = [
+ `${job.arguments.path}:/ansible`,
+ `${sshKeyFile}:/root/id_rsa`,
+ `${secretsFile}:/ansible/secrets.yml`,
+ ];
+ const playbookCmd = `ansible-playbook -e @secrets.yml ${job.arguments.playbooks}`;
+ const deployCmd = [
+ 'docker',
+ 'run',
+ ...prependWith(volumes, '-v'),
+ 'willhallonline/ansible:latest',
+ ...playbookCmd.split(' '),
+ ];
+ tEitherJobAndSecrets.trace.trace(`running ansible magic~ (◕ᴗ◕✿) ${deployCmd}`);
+ return tEitherJobAndSecrets.move(deployCmd).map(getStdout).get();
+ });
+ })
+ .get();
const saveToTempFile = (text: string): Promise<IEither<Error, string>> =>
- Either.fromFailableAsync(
- () => Deno.makeTempDir({ dir: Deno.cwd() })
- .then((dir) => Deno.makeTempFile({ dir }))
- .then(async (f) => {
- await Deno.writeTextFile(f, text);
- return f;
- }),
- );
+ Either.fromFailableAsync(() =>
+ mkdtemp(join(tmpdir(), 'ci-')).then(async (dir) => {
+ const filePath = join(dir, 'temp-file');
+ await writeFile(filePath, text);
+ return filePath;
+ }),
+ );
diff --git a/worker/scripts/build_docker_image.ts b/worker/scripts/build_docker_image.ts
index 49abe41..228dfcc 100755
--- a/worker/scripts/build_docker_image.ts
+++ b/worker/scripts/build_docker_image.ts
@@ -1,162 +1,131 @@
-#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run
+#!/usr/bin/env node
import {
- getRequiredEnvVars,
- getStdout,
- LogLevel,
- LogTraceable,
- LogMetricTraceable,
- Metric,
- TraceUtil,
-} from "@emprespresso/pengueno";
-import type {
- BuildDockerImageJob,
- BuildDockerImageJobProps,
-} from "@emprespresso/ci_model";
-import { Bitwarden, type LoginItem } from "@emprespresso/ci_worker";
+ getRequiredEnvVars,
+ getStdout,
+ LogLevel,
+ LogTraceable,
+ LogMetricTraceable,
+ Metric,
+ TraceUtil,
+} from '@emprespresso/pengueno';
+import type { BuildDockerImageJob, BuildDockerImageJobProps } from '@emprespresso/ci_model';
+import { Bitwarden, type LoginItem } from '@emprespresso/ci_worker';
const eitherJob = getRequiredEnvVars([
- "registry",
- "namespace",
- "repository",
- "imageTag",
- "context",
- "dockerfile",
- "buildTarget",
-])
- .mapRight((baseArgs) => (
- <BuildDockerImageJob> {
- type: "build_docker_image.ts",
- arguments: baseArgs,
- }
- ));
-const eitherVault = Bitwarden.getConfigFromEnvironment()
- .mapRight((config) => new Bitwarden(config));
+ 'registry',
+ 'namespace',
+ 'repository',
+ 'imageTag',
+ 'context',
+ 'dockerfile',
+ 'buildTarget',
+]).mapRight(
+ (baseArgs) =>
+ <BuildDockerImageJob>{
+ type: 'build_docker_image.ts',
+ arguments: baseArgs,
+ },
+);
+const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config));
-const buildImageMetric = Metric.fromName("dockerImage.build");
-const loginMetric = Metric.fromName("dockerRegistry.login");
-const _logJob = LogTraceable.of(eitherJob).bimap(
- (tEitherJob) => {
- const trace = "build_docker_image." +
- tEitherJob.get().fold(({ isRight, value }) => isRight ? value.arguments.buildTarget : "");
- return [tEitherJob.get(), trace];
- },
- );
+const buildImageMetric = Metric.fromName('dockerImage.build');
+const loginMetric = Metric.fromName('dockerRegistry.login');
+const _logJob = LogTraceable.of(eitherJob).bimap((tEitherJob) => {
+ const trace =
+ 'build_docker_image.' +
+ tEitherJob.get().fold(({ isRight, value }) => (isRight ? value.arguments.buildTarget : ''));
+ return [tEitherJob.get(), trace];
+});
await LogMetricTraceable.ofLogTraceable(_logJob)
- .bimap(TraceUtil.withMetricTrace(buildImageMetric))
- .bimap(TraceUtil.withMetricTrace(loginMetric))
- .peek((tEitherJob) =>
- tEitherJob.trace.trace("starting docker image build job! (⑅˘꒳˘)")
- )
- .map((tEitherJob) =>
- tEitherJob.get()
- .flatMapAsync((job) =>
- eitherVault.flatMapAsync(async (vault) => {
- const eitherKey = await vault.unlock(tEitherJob);
- return eitherKey.mapRight((key) => ({ job, key, vault }));
- })
- )
- )
- .map(async (tEitherJobVault) => {
- tEitherJobVault.trace.trace("logging into the wegistwy uwu~");
- const eitherJobVault = await tEitherJobVault.get();
- const eitherDockerRegistryLoginItem = await eitherJobVault.flatMapAsync((
- { job, key, vault },
- ) =>
- vault.fetchSecret<LoginItem>(tEitherJobVault, key, job.arguments.registry)
- .finally(() => vault.lock(tEitherJobVault, key))
- );
- return eitherDockerRegistryLoginItem.flatMapAsync(({ login }) =>
- eitherJobVault.flatMapAsync(async ({ job }) => {
- const loginCommand = getDockerLoginCommand(
- login.username,
- job.arguments.registry,
+ .bimap(TraceUtil.withMetricTrace(buildImageMetric))
+ .bimap(TraceUtil.withMetricTrace(loginMetric))
+ .peek((tEitherJob) => tEitherJob.trace.trace('starting docker image build job! (⑅˘꒳˘)'))
+ .map((tEitherJob) =>
+ tEitherJob.get().flatMapAsync((job) =>
+ eitherVault.flatMapAsync(async (vault) => {
+ const eitherKey = await vault.unlock(tEitherJob);
+ return eitherKey.mapRight((key) => ({ job, key, vault }));
+ }),
+ ),
+ )
+ .map(async (tEitherJobVault) => {
+ tEitherJobVault.trace.trace('logging into the wegistwy uwu~');
+ const eitherJobVault = await tEitherJobVault.get();
+ const eitherDockerRegistryLoginItem = await eitherJobVault.flatMapAsync(({ job, key, vault }) =>
+ vault
+ .fetchSecret<LoginItem>(tEitherJobVault, key, job.arguments.registry)
+ .finally(() => vault.lock(tEitherJobVault, key)),
);
- const eitherLoggedIn = await tEitherJobVault.move(loginCommand).map((
- tLoginCmd,
- ) =>
- getStdout(tLoginCmd, { env: { REGISTRY_PASSWORD: login.password } })
- ).get();
- return eitherLoggedIn.moveRight(job);
- })
- );
- })
- .peek(async (tEitherWithAuthdRegistry) => {
- const eitherWithAuthdRegistry = await tEitherWithAuthdRegistry.get();
- return tEitherWithAuthdRegistry.trace.trace(
- eitherWithAuthdRegistry.fold(({ isLeft}) =>
- loginMetric[isLeft ? "failure" : "success"]
- ),
- );
- })
- .map(async (tEitherWithAuthdRegistryBuildJob) => {
- const eitherWithAuthdRegistryBuildJob =
- await tEitherWithAuthdRegistryBuildJob.get();
- tEitherWithAuthdRegistryBuildJob.trace.trace(
- "finally building the image~ (◕ᴗ◕✿)",
- );
- const eitherBuiltImage = await eitherWithAuthdRegistryBuildJob.flatMapAsync(
- (job) =>
- tEitherWithAuthdRegistryBuildJob
- .move(getBuildCommand(job.arguments))
- .map((tBuildCmd) =>
- getStdout(tBuildCmd, {
- env: {},
- clearEnv: true,
- })
- )
- .get(),
- );
- return eitherBuiltImage.flatMap((buildOutput) =>
- eitherWithAuthdRegistryBuildJob.mapRight((job) => ({ buildOutput, job }))
- );
- })
- .peek(async (tEitherWithBuiltImage) => {
- const eitherWithBuiltImage = await tEitherWithBuiltImage.get();
- eitherWithBuiltImage.fold(({ isLeft, value}) => {
- tEitherWithBuiltImage.trace.trace(
- buildImageMetric[isLeft ? "failure" : "success"],
- );
- if (isLeft) {
- tEitherWithBuiltImage.trace.addTrace(LogLevel.ERROR).trace(
- `oh nyoo we couldn't buiwd the img :(( ${value}`,
+ return eitherDockerRegistryLoginItem.flatMapAsync(({ login }) =>
+ eitherJobVault.flatMapAsync(async ({ job }) => {
+ const loginCommand = getDockerLoginCommand(login.username, job.arguments.registry);
+ const eitherLoggedIn = await tEitherJobVault
+ .move(loginCommand)
+ .map((tLoginCmd) => getStdout(tLoginCmd, { env: { REGISTRY_PASSWORD: login.password } }))
+ .get();
+ return eitherLoggedIn.moveRight(job);
+ }),
);
- return;
- }
- tEitherWithBuiltImage.trace.addTrace("buildOutput").trace(value.buildOutput);
- });
- })
- .map(async (tEitherWithBuiltImage) => {
- const eitherWithBuiltImage = await tEitherWithBuiltImage.get();
- return eitherWithBuiltImage
- .mapRight(({ job }) =>
- tEitherWithBuiltImage.move(getPushCommand(job.arguments.imageTag))
- )
- .flatMapAsync((tPushCommand) => getStdout(tPushCommand));
- })
- .get();
+ })
+ .peek(async (tEitherWithAuthdRegistry) => {
+ const eitherWithAuthdRegistry = await tEitherWithAuthdRegistry.get();
+ return tEitherWithAuthdRegistry.trace.trace(
+ eitherWithAuthdRegistry.fold(({ isLeft }) => loginMetric[isLeft ? 'failure' : 'success']),
+ );
+ })
+ .map(async (tEitherWithAuthdRegistryBuildJob) => {
+ const eitherWithAuthdRegistryBuildJob = await tEitherWithAuthdRegistryBuildJob.get();
+ tEitherWithAuthdRegistryBuildJob.trace.trace('finally building the image~ (◕ᴗ◕✿)');
+ const eitherBuiltImage = await eitherWithAuthdRegistryBuildJob.flatMapAsync((job) =>
+ tEitherWithAuthdRegistryBuildJob
+ .move(getBuildCommand(job.arguments))
+ .map((tBuildCmd) =>
+ getStdout(tBuildCmd, {
+ env: {},
+ clearEnv: true,
+ }),
+ )
+ .get(),
+ );
+ return eitherBuiltImage.flatMap((buildOutput) =>
+ eitherWithAuthdRegistryBuildJob.mapRight((job) => ({ buildOutput, job })),
+ );
+ })
+ .peek(async (tEitherWithBuiltImage) => {
+ const eitherWithBuiltImage = await tEitherWithBuiltImage.get();
+ eitherWithBuiltImage.fold(({ isLeft, value }) => {
+ tEitherWithBuiltImage.trace.trace(buildImageMetric[isLeft ? 'failure' : 'success']);
+ if (isLeft) {
+ tEitherWithBuiltImage.trace
+ .addTrace(LogLevel.ERROR)
+ .trace(`oh nyoo we couldn't buiwd the img :(( ${value}`);
+ return;
+ }
+ tEitherWithBuiltImage.trace.addTrace('buildOutput').trace(value.buildOutput);
+ });
+ })
+ .map(async (tEitherWithBuiltImage) => {
+ const eitherWithBuiltImage = await tEitherWithBuiltImage.get();
+ return eitherWithBuiltImage
+ .mapRight(({ job }) => tEitherWithBuiltImage.move(getPushCommand(job.arguments.imageTag)))
+ .flatMapAsync((tPushCommand) => getStdout(tPushCommand));
+ })
+ .get();
const getDockerLoginCommand = (username: string, registry: string) =>
- `docker login --username ${username} --password $REGISTRY_PASSWORD ${registry}`
- .split(" ");
+ `docker login --username ${username} --password $REGISTRY_PASSWORD ${registry}`.split(' ');
-const getBuildCommand = (
- {
+const getBuildCommand = ({ buildTarget, imageTag, dockerfile, context }: BuildDockerImageJobProps) => [
+ 'docker',
+ 'build',
+ '--target',
buildTarget,
+ '-t',
imageTag,
+ '-f',
dockerfile,
context,
- }: BuildDockerImageJobProps,
-) => [
- "docker",
- "build",
- "--target",
- buildTarget,
- "-t",
- imageTag,
- "-f",
- dockerfile,
- context,
];
-const getPushCommand = (tag: string) => ["docker", "push", tag];
+const getPushCommand = (tag: string) => ['docker', 'push', tag];
diff --git a/worker/scripts/checkout_ci.ts b/worker/scripts/checkout_ci.ts
index efe74fb..8e4dcca 100755
--- a/worker/scripts/checkout_ci.ts
+++ b/worker/scripts/checkout_ci.ts
@@ -1,182 +1,152 @@
-#!/usr/bin/env -S deno run --allow-all
+#!/usr/bin/env node
import {
- type Command,
- Either,
- LogTraceable,
- getRequiredEnvVars,
- getStdout,
- isObject,
- LogMetricTraceable,
- Metric,
- prependWith,
- TraceUtil,
-} from "@emprespresso/pengueno";
-import {
- type CheckoutCiJob,
- type FetchCodeJob,
- PipelineImpl,
-} from "@emprespresso/ci_model";
-import { executeJob, executePipeline } from "@emprespresso/ci_worker";
+ type Command,
+ Either,
+ LogTraceable,
+ getRequiredEnvVars,
+ getStdout,
+ isObject,
+ LogMetricTraceable,
+ Metric,
+ prependWith,
+ TraceUtil,
+} from '@emprespresso/pengueno';
+import { mkdir, readFile, rm } from 'fs/promises';
+import { join } from 'path';
+import { type CheckoutCiJob, type FetchCodeJob, PipelineImpl } from '@emprespresso/ci_model';
+import { executeJob, executePipeline } from '@emprespresso/ci_worker';
const run = Date.now().toString();
-const eitherJob = getRequiredEnvVars(["remote", "refname", "rev"]).mapRight(
- (baseArgs) =>
- <CheckoutCiJob>{
- type: "checkout_ci.ts",
- arguments: {
- ...baseArgs,
- run,
- returnPath: Deno.cwd(),
- },
- },
+const eitherJob = getRequiredEnvVars(['remote', 'refname', 'rev']).mapRight(
+ (baseArgs) =>
+ <CheckoutCiJob>{
+ type: 'checkout_ci.ts',
+ arguments: {
+ ...baseArgs,
+ run,
+ returnPath: process.cwd(),
+ },
+ },
);
-const ciRunMetric = Metric.fromName("checkout_ci.run");
-const _logJob = LogTraceable.of(eitherJob).bimap(
- TraceUtil.withTrace(`checkout_ci.${run}`),
-);
+const ciRunMetric = Metric.fromName('checkout_ci.run');
+const _logJob = LogTraceable.of(eitherJob).bimap(TraceUtil.withTrace(`checkout_ci.${run}`));
await LogMetricTraceable.ofLogTraceable(_logJob)
- .bimap(TraceUtil.withMetricTrace(ciRunMetric))
- .map((tEitherJob) =>
- tEitherJob.get().flatMapAsync((ciJob) => {
- const wd = getWorkingDirectoryForCiJob(ciJob);
- const fetchPackageJob = <FetchCodeJob>{
- type: "fetch_code.ts",
- arguments: {
- remoteUrl: ciJob.arguments.remote,
- checkout: ciJob.arguments.rev,
- path: getSrcDirectoryForCiJob(ciJob),
- },
- };
- return Either.fromFailableAsync<Error, CheckoutCiJob>(() =>
- Deno.mkdir(wd)
- .then(() => Deno.chdir(wd))
- .then(() => tEitherJob.move(fetchPackageJob).map(executeJob).get())
- .then(() => ciJob),
- );
- }),
- )
- .map((tEitherCiJob) =>
- tEitherCiJob.get().then((eitherCiJob) =>
- eitherCiJob.flatMapAsync<{ cmd: Command; job: CheckoutCiJob }>((ciJob) =>
- Either.fromFailableAsync<Error, string>(() =>
- Deno.readTextFile(
- `${getSrcDirectoryForCiJob(ciJob)}/${CI_WORKFLOW_FILE}`,
- ),
- ).then((eitherWorkflowJson) =>
- eitherWorkflowJson
- .flatMap((json) =>
- Either.fromFailable<Error, unknown>(JSON.parse(json)),
- )
- .flatMap((eitherWorkflowParse) => {
- if (isCiWorkflow(eitherWorkflowParse)) {
- return Either.right({
- cmd: getPipelineGenerationCommand(
- ciJob,
- eitherWorkflowParse.workflow,
- ),
- job: ciJob,
- });
- }
- return Either.left(
- new Error(
- "couldn't find any valid ci configuration (。•́︿•̀。), that's okay~",
+ .bimap(TraceUtil.withMetricTrace(ciRunMetric))
+ .map((tEitherJob) =>
+ tEitherJob.get().flatMapAsync((ciJob) => {
+ const wd = getWorkingDirectoryForCiJob(ciJob);
+ const fetchPackageJob = <FetchCodeJob>{
+ type: 'fetch_code.ts',
+ arguments: {
+ remoteUrl: ciJob.arguments.remote,
+ checkout: ciJob.arguments.rev,
+ path: getSrcDirectoryForCiJob(ciJob),
+ },
+ };
+ return Either.fromFailableAsync<Error, CheckoutCiJob>(() =>
+ mkdir(wd, { recursive: true })
+ .then(() => process.chdir(wd))
+ .then(() => tEitherJob.move(fetchPackageJob).map(executeJob).get())
+ .then(() => ciJob),
+ );
+ }),
+ )
+ .map((tEitherCiJob) =>
+ tEitherCiJob.get().then((eitherCiJob) =>
+ eitherCiJob.flatMapAsync<{ cmd: Command; job: CheckoutCiJob }>((ciJob) =>
+ Either.fromFailableAsync<Error, string>(() =>
+ readFile(join(getSrcDirectoryForCiJob(ciJob), CI_WORKFLOW_FILE), 'utf-8'),
+ ).then((eitherWorkflowJson) =>
+ eitherWorkflowJson
+ .flatMap((json) => Either.fromFailable<Error, unknown>(JSON.parse(json)))
+ .flatMap((eitherWorkflowParse) => {
+ if (isCiWorkflow(eitherWorkflowParse)) {
+ return Either.right({
+ cmd: getPipelineGenerationCommand(ciJob, eitherWorkflowParse.workflow),
+ job: ciJob,
+ });
+ }
+ return Either.left(
+ new Error("couldn't find any valid ci configuration (。•́︿•̀。), that's okay~"),
+ );
+ }),
),
- );
- }),
+ ),
),
- ),
- ),
- )
- .map(async (tEitherPipelineGenerationCommand) => {
- const eitherJobCommand = await tEitherPipelineGenerationCommand.get();
- const eitherPipeline = await eitherJobCommand.flatMapAsync((jobCommand) =>
- tEitherPipelineGenerationCommand
- .move(jobCommand.cmd)
- .map(getStdout)
- .get(),
- );
- return eitherPipeline
- .flatMap(PipelineImpl.from)
- .flatMap((pipeline) =>
- eitherJobCommand.mapRight(({ job }) => ({ job, pipeline })),
- );
- })
- .peek(
- TraceUtil.promiseify((tEitherPipeline) =>
- tEitherPipeline
- .get()
- .mapRight((val) => val.pipeline.serialize())
- .mapRight(
- (pipeline) =>
- `built the pipeline~ (◕ᴗ◕✿) let's make something amazing! ${pipeline}`,
- )
- .mapRight((msg) => tEitherPipeline.trace.trace(msg)),
- ),
- )
- .map(async (tEitherPipeline) => {
- const eitherPipeline = await tEitherPipeline.get();
- return eitherPipeline.flatMapAsync(({ pipeline, job }) =>
- tEitherPipeline
- .move(pipeline)
- .map((p) =>
- executePipeline(p, {
- HOME: getWorkingDirectoryForCiJob(job),
- }),
- )
- .get(),
+ )
+ .map(async (tEitherPipelineGenerationCommand) => {
+ const eitherJobCommand = await tEitherPipelineGenerationCommand.get();
+ const eitherPipeline = await eitherJobCommand.flatMapAsync((jobCommand) =>
+ tEitherPipelineGenerationCommand.move(jobCommand.cmd).map(getStdout).get(),
+ );
+ return eitherPipeline
+ .flatMap(PipelineImpl.from)
+ .flatMap((pipeline) => eitherJobCommand.mapRight(({ job }) => ({ job, pipeline })));
+ })
+ .peek(
+ TraceUtil.promiseify((tEitherPipeline) =>
+ tEitherPipeline
+ .get()
+ .mapRight((val) => val.pipeline.serialize())
+ .mapRight((pipeline) => `built the pipeline~ (◕ᴗ◕✿) let's make something amazing! ${pipeline}`)
+ .mapRight((msg) => tEitherPipeline.trace.trace(msg)),
+ ),
+ )
+ .map(async (tEitherPipeline) => {
+ const eitherPipeline = await tEitherPipeline.get();
+ return eitherPipeline.flatMapAsync(({ pipeline, job }) =>
+ tEitherPipeline
+ .move(pipeline)
+ .map((p) =>
+ executePipeline(p, {
+ HOME: getWorkingDirectoryForCiJob(job),
+ }),
+ )
+ .get(),
+ );
+ })
+ .get()
+ .then((e) =>
+ e
+ .flatMap(() => eitherJob)
+ .fold(({ isLeft, isRight, value }) => {
+ if (isLeft || !isRight) throw value;
+ return rm(getWorkingDirectoryForCiJob(value), {
+ recursive: true,
+ });
+ }),
);
- })
- .get()
- .then((e) =>
- e
- .flatMap(() => eitherJob)
- .fold(({ isLeft, isRight, value }) => {
- if (isLeft || !isRight) throw value;
- return Deno.remove(getWorkingDirectoryForCiJob(value), {
- recursive: true,
- });
- }),
- );
-const getWorkingDirectoryForCiJob = (job: CheckoutCiJob) =>
- `${job.arguments.returnPath}/${job.arguments.run}`;
+const getWorkingDirectoryForCiJob = (job: CheckoutCiJob) => `${job.arguments.returnPath}/${job.arguments.run}`;
-const getSrcDirectoryForCiJob = (job: CheckoutCiJob) =>
- `${job.arguments.returnPath}/${job.arguments.run}/src`;
+const getSrcDirectoryForCiJob = (job: CheckoutCiJob) => `${job.arguments.returnPath}/${job.arguments.run}/src`;
-const _runFlags = (
- "--rm --network none --cap-drop ALL" + "--security-opt no-new-privileges"
-).split(" ");
-const _image = "oci.liz.coffee/img/ci-worker:release";
+const _runFlags = ('--rm --network none --cap-drop ALL' + '--security-opt no-new-privileges').split(' ');
+const _image = 'oci.liz.coffee/img/ci-worker:release';
const getPipelineGenerationCommand = (
- job: CheckoutCiJob,
- pipelineGeneratorPath: string,
- image = _image,
- runFlags = _runFlags,
+ job: CheckoutCiJob,
+ pipelineGeneratorPath: string,
+ image = _image,
+ runFlags = _runFlags,
) => [
- "docker",
- "run",
- ...runFlags,
- ...prependWith(
- Object.entries(job.arguments).map(([key, val]) => `"${key}"="${val}"`),
- "-e",
- ),
- "-v",
- `${getSrcDirectoryForCiJob(
- job,
- )}/${pipelineGeneratorPath}:/pipeline_generator`,
- image,
- "/pipeline_generator",
+ 'docker',
+ 'run',
+ ...runFlags,
+ ...prependWith(
+ Object.entries(job.arguments).map(([key, val]) => `"${key}"="${val}"`),
+ '-e',
+ ),
+ '-v',
+ `${getSrcDirectoryForCiJob(job)}/${pipelineGeneratorPath}:/pipeline_generator`,
+ image,
+ '/pipeline_generator',
];
export interface CiWorkflow {
- workflow: string;
+ workflow: string;
}
export const isCiWorkflow = (t: unknown): t is CiWorkflow =>
- isObject(t) &&
- "workflow" in t &&
- typeof t.workflow === "string" &&
- !t.workflow.includes("..");
-const CI_WORKFLOW_FILE = ".ci/ci.json";
+ isObject(t) && 'workflow' in t && typeof t.workflow === 'string' && !t.workflow.includes('..');
+const CI_WORKFLOW_FILE = '.ci/ci.json';
diff --git a/worker/secret.ts b/worker/secret.ts
index 951c539..e3edb2d 100644
--- a/worker/secret.ts
+++ b/worker/secret.ts
@@ -1,36 +1,32 @@
import {
- Either,
- getRequiredEnvVars,
- getStdout,
- type IEither,
- type ITraceable,
- type LogMetricTraceSupplier,
- Metric,
- TraceUtil,
-} from "@emprespresso/pengueno";
+ Either,
+ getRequiredEnvVars,
+ getStdout,
+ type IEither,
+ type ITraceable,
+ type LogMetricTraceSupplier,
+ Metric,
+ TraceUtil,
+} from '@emprespresso/pengueno';
// -- <ISecret> --
export interface LoginItem {
- login: {
- username: string;
- password: string;
- };
+ login: {
+ username: string;
+ password: string;
+ };
}
export interface SecureNote {
- notes: string;
+ notes: string;
}
export type SecretItem = LoginItem | SecureNote;
export interface IVault<TClient, TKey, TItemId> {
- unlock: (client: TClient) => Promise<IEither<Error, TKey>>;
- lock: (client: TClient, key: TKey) => Promise<IEither<Error, TKey>>;
+ unlock: (client: TClient) => Promise<IEither<Error, TKey>>;
+ lock: (client: TClient, key: TKey) => Promise<IEither<Error, TKey>>;
- fetchSecret: <T extends SecretItem>(
- client: TClient,
- key: TKey,
- item: TItemId,
- ) => Promise<IEither<Error, T>>;
+ fetchSecret: <T extends SecretItem>(client: TClient, key: TKey, item: TItemId) => Promise<IEither<Error, T>>;
}
// -- </ISecret> --
@@ -39,156 +35,122 @@ type TClient = ITraceable<unknown, LogMetricTraceSupplier>;
type TKey = string;
type TItemId = string;
export class Bitwarden implements IVault<TClient, TKey, TItemId> {
- constructor(private readonly config: BitwardenConfig) {}
+ constructor(private readonly config: BitwardenConfig) {}
- public unlock(client: TClient) {
- return client
- .move(this.config)
- .bimap(TraceUtil.withMetricTrace(Bitwarden.loginMetric))
- .flatMap((tConfig) =>
- tConfig.move(`bw config server ${tConfig.get().server}`).map(getStdout),
- )
- .map(async (tEitherWithConfig) => {
- const eitherWithConfig = await tEitherWithConfig.get();
- tEitherWithConfig.trace.trace("logging in~ ^.^");
- return eitherWithConfig.flatMapAsync((_) =>
- tEitherWithConfig
- .move("bw login --apikey --quiet")
- .map(getStdout)
- .get(),
- );
- })
- .peek(async (tEitherWithAuthd) => {
- const eitherWithAuthd = await tEitherWithAuthd.get();
- return tEitherWithAuthd.trace.trace(
- eitherWithAuthd.fold(
- ({ isLeft }) =>
- Bitwarden.loginMetric[isLeft ? "failure" : "success"],
- ),
- );
- })
- .map(async (tEitherWithAuthd) => {
- const eitherWithAuthd = await tEitherWithAuthd.get();
- tEitherWithAuthd.trace.trace("unlocking the secret vault~ (◕ᴗ◕✿)");
- return eitherWithAuthd.flatMapAsync((_) =>
- tEitherWithAuthd
- .move("bw unlock --passwordenv BW_PASSWORD --raw")
- .map(getStdout)
- .get(),
- );
- })
- .peek(async (tEitherWithSession) => {
- const eitherWithAuthd = await tEitherWithSession.get();
- return tEitherWithSession.trace.trace(
- eitherWithAuthd.fold(
- ({ isLeft }) =>
- Bitwarden.unlockVaultMetric[isLeft ? "failure" : "success"],
- ),
- );
- })
- .get();
- }
+ public unlock(client: TClient) {
+ return client
+ .move(this.config)
+ .bimap(TraceUtil.withMetricTrace(Bitwarden.loginMetric))
+ .flatMap((tConfig) => tConfig.move(`bw config server ${tConfig.get().server}`).map(getStdout))
+ .map(async (tEitherWithConfig) => {
+ const eitherWithConfig = await tEitherWithConfig.get();
+ tEitherWithConfig.trace.trace('logging in~ ^.^');
+ return eitherWithConfig.flatMapAsync((_) =>
+ tEitherWithConfig.move('bw login --apikey --quiet').map(getStdout).get(),
+ );
+ })
+ .peek(async (tEitherWithAuthd) => {
+ const eitherWithAuthd = await tEitherWithAuthd.get();
+ return tEitherWithAuthd.trace.trace(
+ eitherWithAuthd.fold(({ isLeft }) => Bitwarden.loginMetric[isLeft ? 'failure' : 'success']),
+ );
+ })
+ .map(async (tEitherWithAuthd) => {
+ const eitherWithAuthd = await tEitherWithAuthd.get();
+ tEitherWithAuthd.trace.trace('unlocking the secret vault~ (◕ᴗ◕✿)');
+ return eitherWithAuthd.flatMapAsync((_) =>
+ tEitherWithAuthd.move('bw unlock --passwordenv BW_PASSWORD --raw').map(getStdout).get(),
+ );
+ })
+ .peek(async (tEitherWithSession) => {
+ const eitherWithAuthd = await tEitherWithSession.get();
+ return tEitherWithSession.trace.trace(
+ eitherWithAuthd.fold(({ isLeft }) => Bitwarden.unlockVaultMetric[isLeft ? 'failure' : 'success']),
+ );
+ })
+ .get();
+ }
- public fetchSecret<T extends SecretItem>(
- client: TClient,
- key: string,
- item: string,
- ): Promise<IEither<Error, T>> {
- return client
- .move(key)
- .bimap(TraceUtil.withMetricTrace(Bitwarden.fetchSecretMetric))
- .peek((tSession) =>
- tSession.trace.trace(`looking for your secret ${item} (⑅˘꒳˘)`),
- )
- .flatMap((tSession) =>
- tSession.move("bw list items").map((listCmd) =>
- getStdout(listCmd, {
- env: { BW_SESSION: tSession.get() },
- }),
- ),
- )
- .map(
- TraceUtil.promiseify((tEitherItemsJson) =>
- tEitherItemsJson
- .get()
- .flatMap(
- (itemsJson): IEither<Error, Array<T & { name: string }>> =>
- Either.fromFailable(() => JSON.parse(itemsJson)),
+ public fetchSecret<T extends SecretItem>(client: TClient, key: string, item: string): Promise<IEither<Error, T>> {
+ return client
+ .move(key)
+ .bimap(TraceUtil.withMetricTrace(Bitwarden.fetchSecretMetric))
+ .peek((tSession) => tSession.trace.trace(`looking for your secret ${item} (⑅˘꒳˘)`))
+ .flatMap((tSession) =>
+ tSession.move('bw list items').map((listCmd) =>
+ getStdout(listCmd, {
+ env: { BW_SESSION: tSession.get() },
+ }),
+ ),
+ )
+ .map(
+ TraceUtil.promiseify((tEitherItemsJson) =>
+ tEitherItemsJson
+ .get()
+ .flatMap(
+ (itemsJson): IEither<Error, Array<T & { name: string }>> =>
+ Either.fromFailable(() => JSON.parse(itemsJson)),
+ )
+ .flatMap((itemsList): IEither<Error, T> => {
+ const secret = itemsList.find(({ name }) => name === item);
+ if (!secret) {
+ return Either.left(new Error(`couldn't find the item ${item} (。•́︿•̀。)`));
+ }
+ return Either.right(secret);
+ }),
+ ),
)
- .flatMap((itemsList): IEither<Error, T> => {
- const secret = itemsList.find(({ name }) => name === item);
- if (!secret) {
- return Either.left(
- new Error(`couldn't find the item ${item} (。•́︿•̀。)`),
+ .peek(async (tEitherWithSecret) => {
+ const eitherWithSecret = await tEitherWithSecret.get();
+ return tEitherWithSecret.trace.trace(
+ eitherWithSecret.fold(({ isLeft }) => Bitwarden.fetchSecretMetric[isLeft ? 'failure' : 'success']),
);
- }
- return Either.right(secret);
- }),
- ),
- )
- .peek(async (tEitherWithSecret) => {
- const eitherWithSecret = await tEitherWithSecret.get();
- return tEitherWithSecret.trace.trace(
- eitherWithSecret.fold(
- ({ isLeft }) =>
- Bitwarden.fetchSecretMetric[isLeft ? "failure" : "success"],
- ),
- );
- })
- .get();
- }
+ })
+ .get();
+ }
- public lock(client: TClient, key: TKey) {
- return client
- .move(key)
- .bimap(TraceUtil.withMetricTrace(Bitwarden.lockVaultMetric))
- .peek((tSession) =>
- tSession.trace.trace(`taking care of locking the vault :3`),
- )
- .flatMap((tSession) =>
- tSession.move("bw lock").map((lockCmd) =>
- getStdout(lockCmd, {
- env: { BW_SESSION: tSession.get() },
- }),
- ),
- )
- .peek(async (tEitherWithLocked) => {
- const eitherWithLocked = await tEitherWithLocked.get();
- return eitherWithLocked.fold(({ isLeft }) => {
- tEitherWithLocked.trace.trace(
- Bitwarden.lockVaultMetric[isLeft ? "failure" : "success"],
- );
- if (isLeft) return;
- tEitherWithLocked.trace.trace(
- "all locked up and secure now~ (。•̀ᴗ-)✧",
- );
- });
- })
- .get();
- }
+ public lock(client: TClient, key: TKey) {
+ return client
+ .move(key)
+ .bimap(TraceUtil.withMetricTrace(Bitwarden.lockVaultMetric))
+ .peek((tSession) => tSession.trace.trace(`taking care of locking the vault :3`))
+ .flatMap((tSession) =>
+ tSession.move('bw lock').map((lockCmd) =>
+ getStdout(lockCmd, {
+ env: { BW_SESSION: tSession.get() },
+ }),
+ ),
+ )
+ .peek(async (tEitherWithLocked) => {
+ const eitherWithLocked = await tEitherWithLocked.get();
+ return eitherWithLocked.fold(({ isLeft }) => {
+ tEitherWithLocked.trace.trace(Bitwarden.lockVaultMetric[isLeft ? 'failure' : 'success']);
+ if (isLeft) return;
+ tEitherWithLocked.trace.trace('all locked up and secure now~ (。•̀ᴗ-)✧');
+ });
+ })
+ .get();
+ }
- public static getConfigFromEnvironment(): IEither<Error, BitwardenConfig> {
- return getRequiredEnvVars([
- "BW_SERVER",
- "BW_CLIENTSECRET",
- "BW_CLIENTID",
- "BW_PASSWORD",
- ]).mapRight(({ BW_SERVER, BW_CLIENTSECRET, BW_CLIENTID }) => ({
- clientId: BW_CLIENTID,
- secret: BW_CLIENTSECRET,
- server: BW_SERVER,
- }));
- }
+ public static getConfigFromEnvironment(): IEither<Error, BitwardenConfig> {
+ return getRequiredEnvVars(['BW_SERVER', 'BW_CLIENTSECRET', 'BW_CLIENTID', 'BW_PASSWORD']).mapRight(
+ ({ BW_SERVER, BW_CLIENTSECRET, BW_CLIENTID }) => ({
+ clientId: BW_CLIENTID,
+ secret: BW_CLIENTSECRET,
+ server: BW_SERVER,
+ }),
+ );
+ }
- private static loginMetric = Metric.fromName("Bitwarden.login");
- private static unlockVaultMetric = Metric.fromName("Bitwarden.unlockVault");
- private static fetchSecretMetric = Metric.fromName("Bitwarden.fetchSecret");
- private static lockVaultMetric = Metric.fromName("Bitwarden.lock");
+ private static loginMetric = Metric.fromName('Bitwarden.login');
+ private static unlockVaultMetric = Metric.fromName('Bitwarden.unlockVault');
+ private static fetchSecretMetric = Metric.fromName('Bitwarden.fetchSecret');
+ private static lockVaultMetric = Metric.fromName('Bitwarden.lock');
}
export interface BitwardenConfig {
- server: string;
- secret: string;
- clientId: string;
+ server: string;
+ secret: string;
+ clientId: string;
}
// -- </IVault> --
diff --git a/worker/tsconfig.json b/worker/tsconfig.json
new file mode 100644
index 0000000..58e9147
--- /dev/null
+++ b/worker/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./",
+ "composite": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "noEmit": false
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
+ "references": [{ "path": "../u" }, { "path": "../model" }]
+}