mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-22 02:04:22 +01:00
sync
This commit is contained in:
34
js/.gitignore
vendored
Normal file
34
js/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
js/README.md
Normal file
15
js/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# js
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.2.12. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
85
js/bun.lock
Normal file
85
js/bun.lock
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "js",
|
||||
"dependencies": {
|
||||
"@flystorage/file-storage": "^1.1.0",
|
||||
"@flystorage/local-fs": "^1.1.0",
|
||||
"ai": "^5.0.0-alpha.2",
|
||||
"ulid": "^3.0.0",
|
||||
"zod": "^3.24.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "^1.0.7",
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0-alpha.2", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-jgRpHhpKmXnUEp41xUZyqJ8VPF9gS6W7SP2iYRaM9jaq66edcg6gTYOJLqM+nSU2tXYfkzfoBGGRvtl9ijH/VQ=="],
|
||||
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0-alpha.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.2", "@standard-schema/spec": "^1.0.0", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-oTlF6UlVitSdVPQv0e+kAkZmbuunJAUYdVEh7ZRvoti+kY/T4vOT6p22X0xTaWgl0+MI1igAT+c83j7tCMuo2w=="],
|
||||
|
||||
"@flystorage/dynamic-import": ["@flystorage/dynamic-import@1.0.0", "", {}, "sha512-CIbIUrBdaPFyKnkVBaqzksvzNtsMSXITR/G/6zlil3MBnPFq2LX+X4Mv5p2XOmv/3OulFs/ff2SNb+5dc2Twtg=="],
|
||||
|
||||
"@flystorage/file-storage": ["@flystorage/file-storage@1.1.0", "", {}, "sha512-25Gd5EsXDmhHrK5orpRuVqebQms1Cm9m5ACMZ0sVDX+Sbl1V0G88CbcWt7mEoWRYLvQ1U072htqg6Sav76ZlVA=="],
|
||||
|
||||
"@flystorage/local-fs": ["@flystorage/local-fs@1.1.0", "", { "dependencies": { "@flystorage/dynamic-import": "^1.0.0", "@flystorage/file-storage": "^1.1.0", "file-type": "^20.5.0", "mime-types": "^3.0.1" } }, "sha512-dbErRhqmCv2UF0zPdeH7iVWuVeTWAJHuJD/mXDe2V370/SL7XIvdE3ditBHWC+1SzBKXJ0lkykOenwlum+oqIA=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||
|
||||
"@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="],
|
||||
|
||||
"@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="],
|
||||
|
||||
"ai": ["ai@5.0.0-alpha.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.2", "@ai-sdk/provider-utils": "3.0.0-alpha.2", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-42asUyoFcqjV5AoZezJPawODCPT5Rb1y/UipVlcXn1tpqlypCchSEukjNw/l09YPVucqCbW19IVqojLttkTTVA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
|
||||
|
||||
"file-type": ["file-type@20.5.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"peek-readable": ["peek-readable@7.0.0", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="],
|
||||
|
||||
"strtok3": ["strtok3@10.2.2", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="],
|
||||
|
||||
"token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
|
||||
|
||||
"ulid": ["ulid@3.0.0", "", { "bin": { "ulid": "dist/cli.js" } }, "sha512-yvZYdXInnJve6LdlPIuYmURdS2NP41ZoF4QW7SXwbUKYt53+0eDAySO+rGSvM2O/ciuB/G+8N7GQrZ1mCJpuqw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
||||
}
|
||||
}
|
||||
3
js/opencode.jsonc
Normal file
3
js/opencode.jsonc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"lol": "jsonc"
|
||||
}
|
||||
19
js/package.json
Normal file
19
js/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "^1.0.7",
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@flystorage/file-storage": "^1.1.0",
|
||||
"@flystorage/local-fs": "^1.1.0",
|
||||
"ai": "5.0.0-alpha.2",
|
||||
"ulid": "3.0.0",
|
||||
"zod": "3.24.4"
|
||||
}
|
||||
}
|
||||
46
js/src/app/index.ts
Normal file
46
js/src/app/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import fs from "fs/promises";
|
||||
import { AppPath } from "./path";
|
||||
import { Log } from "../util/log";
|
||||
import { Context } from "../util/context";
|
||||
|
||||
export namespace App {
|
||||
const log = Log.create({ service: "app" });
|
||||
|
||||
export type Info = Awaited<ReturnType<typeof create>>;
|
||||
|
||||
const ctx = Context.create<Info>("app");
|
||||
|
||||
export async function create(input: { directory: string }) {
|
||||
log.info("creating");
|
||||
|
||||
const dataDir = AppPath.data(input.directory);
|
||||
await fs.mkdir(dataDir, { recursive: true });
|
||||
log.info("created", { path: dataDir });
|
||||
|
||||
const services = new Map<any, any>();
|
||||
|
||||
return {
|
||||
get root() {
|
||||
return input.directory;
|
||||
},
|
||||
service<T extends () => any>(service: any, init: T) {
|
||||
if (!services.has(service)) {
|
||||
log.info("registering service", { name: service });
|
||||
services.set(service, init());
|
||||
}
|
||||
return services.get(service) as ReturnType<T>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function service<T extends () => any>(key: any, init: T) {
|
||||
const app = ctx.use();
|
||||
return app.service(key, init);
|
||||
}
|
||||
|
||||
export async function use() {
|
||||
return ctx.use();
|
||||
}
|
||||
|
||||
export const provide = ctx.provide;
|
||||
}
|
||||
11
js/src/app/path.ts
Normal file
11
js/src/app/path.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import path from "path";
|
||||
|
||||
export namespace AppPath {
|
||||
export function data(input: string) {
|
||||
return path.join(input, ".opencode");
|
||||
}
|
||||
|
||||
export function storage(input: string) {
|
||||
return path.join(data(input), "storage");
|
||||
}
|
||||
}
|
||||
42
js/src/config/config.ts
Normal file
42
js/src/config/config.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import path from "node:path";
|
||||
import { Log } from "../util/log";
|
||||
import { App } from "../app";
|
||||
|
||||
export namespace Config {
|
||||
const log = Log.create({ service: "config" });
|
||||
|
||||
// TODO: this should be zod
|
||||
export interface Info {
|
||||
mcp: any; // TODO
|
||||
lsp: any; // TODO
|
||||
}
|
||||
|
||||
function state() {
|
||||
return App.service("config", async () => {
|
||||
const app = await App.use();
|
||||
let result: Info = {
|
||||
mcp: {},
|
||||
lsp: {},
|
||||
};
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const resolved = path.join(app.root, file);
|
||||
log.info("searching", { path: resolved });
|
||||
try {
|
||||
result = await import(path.join(app.root, file)).then(
|
||||
(mod) => mod.default,
|
||||
);
|
||||
log.info("found", { path: resolved });
|
||||
break;
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
log.info("loaded", result);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function get() {
|
||||
return state();
|
||||
}
|
||||
}
|
||||
23
js/src/id/id.ts
Normal file
23
js/src/id/id.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ulid } from "ulid";
|
||||
import { z } from "zod";
|
||||
|
||||
export namespace Identifier {
|
||||
const prefixes = {
|
||||
session: "ses",
|
||||
} as const;
|
||||
|
||||
export function create(
|
||||
prefix: keyof typeof prefixes,
|
||||
given?: string,
|
||||
): string {
|
||||
if (given) {
|
||||
if (given.startsWith(prefixes[prefix])) return given;
|
||||
throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`);
|
||||
}
|
||||
return [prefixes[prefix], ulid()].join("_");
|
||||
}
|
||||
|
||||
export function schema(prefix: keyof typeof prefixes) {
|
||||
return z.string().startsWith(prefixes[prefix]);
|
||||
}
|
||||
}
|
||||
13
js/src/index.ts
Normal file
13
js/src/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { App } from "./app";
|
||||
import process from "node:process";
|
||||
import { RPC } from "./server/server";
|
||||
import { Session } from "./session/session";
|
||||
|
||||
const app = await App.create({
|
||||
directory: process.cwd(),
|
||||
});
|
||||
|
||||
App.provide(app, async () => {
|
||||
const session = await Session.create();
|
||||
const rpc = RPC.listen();
|
||||
});
|
||||
34
js/src/server/server.ts
Normal file
34
js/src/server/server.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Log } from "../util/log";
|
||||
|
||||
export namespace RPC {
|
||||
const log = Log.create({ service: "rpc" });
|
||||
const PORT = 16713;
|
||||
export function listen(input?: { port?: number }) {
|
||||
const port = input?.port ?? PORT;
|
||||
log.info("trying", { port });
|
||||
try {
|
||||
const server = Bun.serve({
|
||||
port,
|
||||
websocket: {
|
||||
open() {},
|
||||
message() {},
|
||||
},
|
||||
routes: {
|
||||
"/ws": (req, server) => {
|
||||
if (server.upgrade(req)) return;
|
||||
return new Response("Not a websocket request", { status: 400 });
|
||||
},
|
||||
},
|
||||
});
|
||||
log.info("listening", { port });
|
||||
return {
|
||||
server,
|
||||
};
|
||||
} catch (e: any) {
|
||||
if (e?.code === "EADDRINUSE") {
|
||||
return listen({ port: port + 1 });
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
js/src/session/session.ts
Normal file
22
js/src/session/session.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Identifier } from "../id/id";
|
||||
import { Storage } from "../storage/storage";
|
||||
import { Log } from "../util/log";
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" });
|
||||
|
||||
export interface Info {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export async function create() {
|
||||
const result: Info = {
|
||||
id: Identifier.create("session"),
|
||||
title: "New Session - " + new Date().toISOString(),
|
||||
};
|
||||
log.info("created", result);
|
||||
await Storage.write("session/info/" + result.id, JSON.stringify(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
39
js/src/storage/storage.ts
Normal file
39
js/src/storage/storage.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { FileStorage } from "@flystorage/file-storage";
|
||||
import { LocalStorageAdapter } from "@flystorage/local-fs";
|
||||
import fs from "fs/promises";
|
||||
import { Log } from "../util/log";
|
||||
import { App } from "../app";
|
||||
import { AppPath } from "../app/path";
|
||||
|
||||
export namespace Storage {
|
||||
const log = Log.create({ service: "storage" });
|
||||
|
||||
function state() {
|
||||
return App.service("storage", async () => {
|
||||
const app = await App.use();
|
||||
const storageDir = AppPath.storage(app.root);
|
||||
await fs.mkdir(storageDir, { recursive: true });
|
||||
const storage = new FileStorage(new LocalStorageAdapter(storageDir));
|
||||
await storage.write("test", "test");
|
||||
log.info("created", { path: storageDir });
|
||||
return {
|
||||
storage,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function expose<T extends keyof FileStorage>(key: T) {
|
||||
const fn = FileStorage.prototype[key];
|
||||
return async (
|
||||
...args: Parameters<typeof fn>
|
||||
): Promise<ReturnType<typeof fn>> => {
|
||||
const { storage } = await state();
|
||||
const match = storage[key];
|
||||
// @ts-ignore
|
||||
return match.call(storage, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
export const write = expose("write");
|
||||
export const read = expose("read");
|
||||
}
|
||||
25
js/src/util/context.ts
Normal file
25
js/src/util/context.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
|
||||
export namespace Context {
|
||||
export class NotFound extends Error {
|
||||
constructor(public readonly name: string) {
|
||||
super(`No context found for ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function create<T>(name: string) {
|
||||
const storage = new AsyncLocalStorage<T>();
|
||||
return {
|
||||
use() {
|
||||
const result = storage.getStore();
|
||||
if (!result) {
|
||||
throw new NotFound(name);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
provide<R>(value: T, fn: () => R) {
|
||||
return storage.run<R>(value, fn);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
27
js/src/util/log.ts
Normal file
27
js/src/util/log.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export namespace Log {
|
||||
export function create(tags?: Record<string, any>) {
|
||||
tags = tags || {};
|
||||
|
||||
const result = {
|
||||
info(message?: any, extra?: Record<string, any>) {
|
||||
const prefix = Object.entries({
|
||||
...tags,
|
||||
...extra,
|
||||
})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(" ");
|
||||
console.log(prefix, message);
|
||||
return result;
|
||||
},
|
||||
tag(key: string, value: string) {
|
||||
if (tags) tags[key] = value;
|
||||
return result;
|
||||
},
|
||||
clone() {
|
||||
return Log.create({ ...tags });
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
5
js/tsconfig.json
Normal file
5
js/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {}
|
||||
}
|
||||
Reference in New Issue
Block a user