env-runner 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,27 +7,9 @@ export interface NetlifyEnvRunnerOptions {
7
7
  hooks?: WorkerHooks;
8
8
  data?: EnvRunnerData;
9
9
  /**
10
- * Module specifier for the `@netlify/runtime` package, used inside the
11
- * worker to call `startRuntime()` (full `globalThis.Netlify` + `caches`
12
- * setup). `env-runner` does not depend on `@netlify/runtime`.
13
- *
14
- * Unlike the other runtime-dependency options, this one takes a **specifier
15
- * only**: the runtime has to be instantiated inside the worker thread, and a
16
- * live module instance cannot cross that boundary.
17
- *
18
- * ```ts
19
- * new NetlifyEnvRunner({
20
- * name: "app",
21
- * netlifyRuntime: import.meta.resolve("@netlify/runtime"),
22
- * data: { entry },
23
- * });
24
- * ```
25
- *
26
- * A bare specifier (e.g. `"@netlify/runtime"`) is resolved from the current
27
- * working directory. When omitted, the worker tries
28
- * `import("@netlify/runtime")` and falls back to a lightweight
29
- * `globalThis.Netlify` shim (env access only) if it isn't installed. Pass
30
- * `false` to always use the shim.
10
+ * `@netlify/runtime` specifier (resolved from cwd), imported inside the worker
11
+ * since a module instance can't cross into it. Omitted: imported optionally,
12
+ * else an env-only `globalThis.Netlify` shim. `false` forces the shim.
31
13
  */
32
14
  netlifyRuntime?: string | URL | false;
33
15
  }
@@ -1,11 +1,6 @@
1
1
  import { IncomingMessage } from "node:http";
2
2
  import { Socket } from "node:net";
3
- /**
4
- * Handler for proxying HTTP requests to the worker.
5
- *
6
- * A relative string input (e.g. `"/path"`) is resolved against a placeholder
7
- * `http://localhost` origin before dispatching.
8
- */
3
+ /** Proxy a request to the worker; relative inputs resolve against `http://localhost`. */
9
4
  export type FetchHandler = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
10
5
  /** Callback for receiving messages from the worker. */
11
6
  export type RunnerMessageListener = (data: unknown) => void;
@@ -30,11 +25,7 @@ export interface RunnerRPCHooks {
30
25
  /** Remove a previously registered message listener. */
31
26
  offMessage: (listener: RunnerMessageListener) => void;
32
27
  }
33
- /**
34
- * Address reported by the worker once it is ready.
35
- *
36
- * Either a TCP `host`/`port` pair or a Unix `socketPath`.
37
- */
28
+ /** Address reported by the worker once it is ready. */
38
29
  export type WorkerAddress = {
39
30
  host?: string;
40
31
  port: number;
@@ -61,6 +61,7 @@ var VercelEnvRunner = class extends NodeWorkerEnvRunner {
61
61
  input = this._resolveFetchInput(input);
62
62
  const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
63
63
  const requestId = generateVercelId();
64
+ await this._waitForAddress();
64
65
  if (this._address && this._address.port != null && !headers.has("x-vercel-deployment-url")) {
65
66
  const host = this._address.host || "127.0.0.1";
66
67
  headers.set("x-vercel-deployment-url", `http://${host}:${this._address.port}`);
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { EnvServer } from "./_chunks/server.mjs";
2
2
  import { resolve } from "node:path";
3
- import { serve } from "srvx";
4
3
  import { parseArgs } from "node:util";
4
+ import { serve } from "srvx";
5
5
  const { values, positionals } = parseArgs({
6
6
  allowPositionals: true,
7
7
  options: {
package/dist/index.d.mts CHANGED
@@ -7,10 +7,7 @@ import { VercelEnvRunner } from "./_chunks/vercel-runner.mjs";
7
7
  import { NetlifyEnvRunner } from "./_chunks/netlify-runner.mjs";
8
8
  import { ServerOptions, ServerPlugin } from "srvx";
9
9
  import { Hooks } from "crossws";
10
- /**
11
- * Manages an active `EnvRunner` instance, proxying all calls to it.
12
- * Supports hot-reload, auto-restart on unexpected exit, and message queueing.
13
- */
10
+ /** Proxies an active `EnvRunner` with hot-reload, auto-restart and message queueing. */
14
11
  export declare class RunnerManager implements EnvRunner, AsyncDisposable {
15
12
  private _runner;
16
13
  private _messageQueue;
@@ -27,30 +24,18 @@ export declare class RunnerManager implements EnvRunner, AsyncDisposable {
27
24
  get closed(): boolean;
28
25
  get address(): WorkerAddress | undefined;
29
26
  /**
30
- * Replace the active runner with a new one. Closes the previous runner.
31
- *
32
- * When called without a runner, a fresh one is created via `_createRunner()`
33
- * (only available on subclasses with a runner factory, e.g. `EnvServer`).
27
+ * Replace (and close) the active runner. Without an argument, subclasses with
28
+ * `_createRunner()` (e.g. `EnvServer`) create a fresh one.
34
29
  */
35
30
  reload(runner?: EnvRunner): Promise<void>;
36
31
  /** Create a fresh runner for argument-less `reload()`. Overridden by subclasses with a runner factory. */
37
32
  protected _createRunner(): EnvRunner | Promise<EnvRunner>;
38
33
  fetch: FetchHandler;
39
34
  protected _fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
40
- /**
41
- * Lazily satisfy a pending `invalidateModule()` with a single entry reload
42
- * before serving — concurrent fetches share the same reload. A failed reload
43
- * keeps the invalidation pending, so the next fetch retries.
44
- */
35
+ /** Apply a pending invalidation with one shared reload; a failure retries on the next fetch. */
45
36
  private _flushInvalidation;
46
37
  upgrade: UpgradeHandler;
47
- /**
48
- * Create a runtime-native WebSocket reverse-proxy plugin for the public srvx
49
- * server. Attach it via `serve({ plugins: [await manager.wsSrvxPlugin()] })`:
50
- * on Node it proxies the raw upgrade socket to the worker, and on Bun/Deno it
51
- * bridges the WebSocket with crossws. The plugin reads the active runner
52
- * lazily, so it keeps working across hot-reloads.
53
- */
38
+ /** WebSocket proxy plugin: `serve({ plugins: [await manager.wsSrvxPlugin()] })`. */
54
39
  wsSrvxPlugin(): Promise<ServerPlugin>;
55
40
  sendMessage(message: unknown): void;
56
41
  onMessage(listener: RunnerMessageListener): void;
@@ -60,11 +45,7 @@ export declare class RunnerManager implements EnvRunner, AsyncDisposable {
60
45
  timeout?: number;
61
46
  }): Promise<T>;
62
47
  reloadModule(timeout?: number): Promise<void>;
63
- /**
64
- * Invalidate a virtual module on the active runner and mark the manager
65
- * dirty: the next `fetch()` reloads the entry automatically, so callers
66
- * don't need to pair the call with an explicit `reloadModule()`.
67
- */
48
+ /** Invalidate a virtual module; the next `fetch()` reloads the entry automatically. */
68
49
  invalidateModule(specifier: string, timeout?: number): Promise<void>;
69
50
  close(): Promise<void>;
70
51
  [Symbol.asyncDispose](): Promise<void>;
@@ -85,11 +66,7 @@ interface LoadRunnerOptions {
85
66
  hooks?: WorkerHooks;
86
67
  data?: EnvRunnerData;
87
68
  execArgv?: string[];
88
- /**
89
- * Additional runner-specific options (passed through to the runner
90
- * constructor) — e.g. `miniflare` (the imported `miniflare` package) for the
91
- * `miniflare` runner, which this package never imports itself.
92
- */
69
+ /** Runner-specific constructor options (e.g. `miniflare` for the `miniflare` runner). */
93
70
  [key: string]: unknown;
94
71
  }
95
72
  export declare function loadRunner(runner: RunnerName, opts: LoadRunnerOptions): Promise<EnvRunner>;
@@ -110,13 +87,7 @@ interface EnvServerOptions {
110
87
  watch?: boolean;
111
88
  /** Additional paths to watch (directories or files). */
112
89
  watchPaths?: string[];
113
- /**
114
- * Extra runner-specific constructor options, spread into the runner.
115
- *
116
- * Runners that build on external packages take them as explicit options —
117
- * e.g. `{ miniflare }` for the `miniflare` runner (env-runner never imports
118
- * those packages itself).
119
- */
90
+ /** Runner-specific constructor options (e.g. `{ miniflare }`). */
120
91
  runnerOptions?: Record<string, unknown>;
121
92
  }
122
93
  export declare class EnvServer extends RunnerManager {
@@ -132,17 +103,11 @@ export declare class EnvServer extends RunnerManager {
132
103
  offReload(listener: () => void): void;
133
104
  constructor(opts: EnvServerOptions);
134
105
  /**
135
- * Start the server by loading and attaching the runner.
136
- *
137
- * Idempotent — concurrent and repeated calls share one startup. Calling
138
- * `start()` explicitly is optional: the first `fetch()` auto-starts the
139
- * server. A failed start resets so a later call can retry.
106
+ * Load and attach the runner. Idempotent and optional (the first `fetch()`
107
+ * auto-starts); a failed start can be retried.
140
108
  */
141
109
  start(): Promise<this>;
142
- /**
143
- * Replace the active runner. When called without an argument, a fresh
144
- * runner is created from the server options.
145
- */
110
+ /** Replace the active runner; without an argument, create one from the server options. */
146
111
  reload(runner?: EnvRunner): Promise<void>;
147
112
  close(): Promise<void>;
148
113
  /** Auto-start on first fetch so an explicit `start()` call is optional. */
@@ -162,12 +127,8 @@ interface AppEntryIPC {
162
127
  onClose?: () => void | Promise<void>;
163
128
  }
164
129
  /**
165
- * User entry module shape. Besides `fetch` and the env-runner specific hooks
166
- * (`upgrade`, `websocket`, `ipc`), any srvx `ServerOptions` (e.g. `error`,
167
- * `maxRequestBodySize`, `trustProxy`, `node`/`bun`/`deno`) is forwarded to
168
- * `serve()` by the built-in workers. The listener-related keys in
169
- * {@link RESERVED_SERVER_OPTIONS} are owned by the worker (it sits behind the
170
- * runner's proxy) and are ignored if set.
130
+ * User entry module. Other srvx `ServerOptions` are forwarded to `serve()`,
131
+ * except {@link RESERVED_SERVER_OPTIONS}.
171
132
  */
172
133
  interface AppEntry extends Omit<ServerOptions, "fetch"> {
173
134
  fetch: ServerOptions["fetch"];
@@ -175,29 +136,17 @@ interface AppEntry extends Omit<ServerOptions, "fetch"> {
175
136
  websocket?: Partial<Hooks>;
176
137
  ipc?: AppEntryIPC;
177
138
  }
178
- /**
179
- * srvx options the built-in workers always control themselves: the worker
180
- * listens on a random loopback port behind the runner's proxy, so the entry
181
- * must not be able to relocate or TLS-terminate it.
182
- */
139
+ /** Worker-owned srvx options: the worker listens on a random loopback port behind the proxy. */
183
140
  export declare const RESERVED_SERVER_OPTIONS: ["port", "hostname", "protocol", "tls", "silent", "manual", "gracefulShutdown"];
184
141
  /**
185
- * Listener/TLS keys inside the runtime-specific option objects. srvx spreads
186
- * `node`/`bun`/`deno` *after* its resolved port/host/tls, so these would
187
- * otherwise bypass {@link RESERVED_SERVER_OPTIONS}. `node.http2` is dropped
188
- * too: srvx requires a TLS certificate for it, which the worker never has.
142
+ * srvx spreads `node`/`bun`/`deno` after its resolved listener options, so
143
+ * these would bypass {@link RESERVED_SERVER_OPTIONS}. `node.http2` needs TLS.
189
144
  */
190
145
  export declare const RESERVED_RUNTIME_OPTIONS: {
191
146
  readonly node: readonly ["port", "host", "path", "http2", "cert", "key", "passphrase"];
192
147
  readonly bun: readonly ["port", "hostname", "unix", "tls"];
193
148
  readonly deno: readonly ["port", "hostname", "path", "cert", "key"];
194
149
  };
195
- /**
196
- * Build the srvx `serve()` options for a user entry: forwards every srvx
197
- * option exported by the entry, drops env-runner specific keys (`fetch`,
198
- * `upgrade`, `websocket`, `ipc`) and pins the worker-owned listener options
199
- * (top-level and nested in `node`/`bun`/`deno`). Callers add `fetch` (bound
200
- * lazily so module reloads swap the handler) and the crossws plugin themselves.
201
- */
150
+ /** Callers add `fetch` (bound lazily so reloads swap it) and the crossws plugin. */
202
151
  export declare function toServerOptions(entry: AppEntry): Omit<ServerOptions, "fetch">;
203
152
  export { type AppEntry, type AppEntryIPC, type AppEntryIPCContext, BaseEnvRunner, DenoProcessEnvRunner, type EnvRunnerData as DenoProcessEnvRunnerData, type EnvRunner, type EnvRunnerData, type EnvServerOptions, type FetchHandler, type LoadRunnerOptions, MiniflareEnvRunner, type MiniflareEnvRunnerOptions, type MiniflareExportInfo, NetlifyEnvRunner, type NodeUpgradeContext, type RPCOptions, type RunnerMessageListener, type RunnerName, type RunnerRPCHooks, type RuntimeDep, type TransformResult, type UpgradeContext, type UpgradeHandler, VercelEnvRunner, type VirtualModuleSource, type VirtualModules, type WorkerAddress, type WorkerHooks, resolveRuntimeDep };
@@ -1,10 +1,9 @@
1
1
  import { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry, toServerOptions } from "../../_chunks/common-worker-utils.mjs";
2
2
  import { serve } from "srvx";
3
3
  import { plugin } from "crossws/server";
4
+ process.on("disconnect", () => process.exit(0));
4
5
  const data = JSON.parse(process.env.ENV_RUNNER_DATA || "{}");
5
- const _stdout = globalThis.Deno?.stdout ? { write: (s) => globalThis.Deno.stdout.writeSync(new TextEncoder().encode(s)) } : process.stdout;
6
- const sendMessage = (message) => _stdout.write(JSON.stringify(message) + "\n");
7
- const _stdin = globalThis.Deno?.stdin?.readable || process.stdin;
6
+ const sendMessage = (message) => process.send(message);
8
7
  const virtualEntry = isVirtualSpecifier(data.entry, data.virtual);
9
8
  let unregisterVirtualModules;
10
9
  let entry;
@@ -35,41 +34,21 @@ if (entry.upgrade) server.node?.server?.on("upgrade", (req, socket, head) => {
35
34
  } });
36
35
  });
37
36
  if (entry.ipc) await entry.ipc.onOpen?.({ sendMessage });
38
- sendMessage({ address: parseServerAddress(server) });
39
- async function readMessages() {
40
- const decoder = new TextDecoder();
41
- let buffer = "";
42
- for await (const chunk of _stdin) {
43
- buffer += decoder.decode(chunk, { stream: true });
44
- let newlineIdx;
45
- while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
46
- const line = buffer.slice(0, newlineIdx);
47
- buffer = buffer.slice(newlineIdx + 1);
48
- if (!line) continue;
49
- let message;
50
- try {
51
- message = JSON.parse(line);
52
- } catch {
53
- continue;
54
- }
55
- await handleMessage(message);
56
- }
57
- }
58
- }
59
- async function handleMessage(message) {
37
+ process.send({ address: parseServerAddress(server) });
38
+ process.on("message", async (message) => {
60
39
  if (message?.event === "shutdown") {
61
40
  Promise.resolve(entry.ipc?.onClose?.()).then(() => server.close()).then(() => {
62
41
  unregisterVirtualModules();
63
- sendMessage({ event: "exit" });
42
+ process.send({ event: "exit" });
64
43
  });
65
44
  return;
66
45
  }
67
46
  if (message?.event === "reload-module") {
68
47
  try {
69
48
  entry = await reloadEntryModule(data.entry, entry, sendMessage, virtualEntry);
70
- sendMessage({ event: "module-reloaded" });
49
+ process.send({ event: "module-reloaded" });
71
50
  } catch (error) {
72
- sendMessage({
51
+ process.send({
73
52
  event: "module-reloaded",
74
53
  error: error?.message || String(error)
75
54
  });
@@ -81,13 +60,12 @@ async function handleMessage(message) {
81
60
  return;
82
61
  }
83
62
  if (message?.type === "ping") {
84
- sendMessage({
63
+ process.send({
85
64
  type: "pong",
86
65
  data: message.data
87
66
  });
88
67
  return;
89
68
  }
90
69
  entry.ipc?.onMessage?.(message);
91
- }
92
- readMessages().catch(() => {});
70
+ });
93
71
  export {};
@@ -20,11 +20,7 @@ export type VercelQueueRetryDirective = {
20
20
  export type VercelQueueMessageHandler<T = unknown> = (message: T, metadata: VercelQueueMessageMetadata) => Promise<void> | void;
21
21
  /** Called when the message handler throws (`RetryHandler`). */
22
22
  export type VercelQueueRetryHandler = (error: unknown, metadata: VercelQueueMessageMetadata) => VercelQueueRetryDirective | void | undefined;
23
- /**
24
- * The `@vercel/queue` package, as imported by the consumer
25
- * (`import * as queue from "@vercel/queue"`). Structurally typed so the real
26
- * module namespace is assignable without env-runner depending on the package.
27
- */
23
+ /** `@vercel/queue` namespace, structurally typed to avoid depending on the package. */
28
24
  export interface VercelQueueSdk {
29
25
  QueueClient: new (options?: any) => any;
30
26
  registerDevConsumer?: (options: {
@@ -39,20 +35,8 @@ export interface VercelQueueSdk {
39
35
  }
40
36
  export interface VercelQueueDevConsumer {
41
37
  /**
42
- * The `@vercel/queue` package: the imported module, or a specifier for it.
43
- *
44
- * ```ts
45
- * import * as sdk from "@vercel/queue";
46
- * await registerVercelQueueConsumer({ sdk, topic: "orders", handler });
47
- *
48
- * // or, equivalently
49
- * await registerVercelQueueConsumer({ sdk: "@vercel/queue", topic, handler });
50
- * ```
51
- *
52
- * Passing it explicitly is preferred (`@vercel/queue` is not a dependency of
53
- * `env-runner`). Bare specifiers resolve from the current working directory.
54
- * When omitted, `@vercel/queue` is imported optionally and registration
55
- * becomes a no-op if the package isn't installed.
38
+ * The `@vercel/queue` package (`import * as sdk from "@vercel/queue"`) or a
39
+ * specifier resolved from cwd. Omitted: imported optionally.
56
40
  */
57
41
  sdk?: RuntimeDep<VercelQueueSdk>;
58
42
  /** Topic name. Wildcard patterns (e.g. `"user-*"`) are supported. */
@@ -60,39 +44,19 @@ export interface VercelQueueDevConsumer {
60
44
  /** Function invoked with each delivered message. */
61
45
  handler: VercelQueueMessageHandler;
62
46
  /**
63
- * Logical consumer identifier. Re-registering with the same group on the same
64
- * topic replaces the previous handler (HMR-safe). Use distinct groups to fan
65
- * a topic out to multiple coexisting handlers.
66
- *
47
+ * Same group + topic replaces the handler; distinct groups coexist.
67
48
  * @default "env-runner-vercel-dev"
68
49
  */
69
50
  consumerGroup?: string;
70
- /**
71
- * Lock duration for in-flight messages. Forwarded to the SDK's
72
- * `coreHandleCallback`.
73
- */
51
+ /** Lock duration for in-flight messages (SDK `coreHandleCallback`). */
74
52
  visibilityTimeoutSeconds?: number;
75
- /**
76
- * Convenience: rescheduled re-delivery delay applied when the handler throws.
77
- * Equivalent to `retry: () => ({ afterSeconds })`. Ignored if `retry` is set.
78
- */
53
+ /** Re-delivery delay when the handler throws; shorthand for `retry` (which wins). */
79
54
  retryAfterSeconds?: number;
80
- /**
81
- * Full retry handler. Receives the thrown error and message metadata; return
82
- * `{ afterSeconds }` to reschedule, `{ acknowledge: true }` to drop, or
83
- * `undefined` to let the error propagate.
84
- */
55
+ /** Return `{ afterSeconds }` to reschedule, `{ acknowledge: true }` to drop, or `undefined` to rethrow. */
85
56
  retry?: VercelQueueRetryHandler;
86
57
  }
87
58
  /**
88
- * Bind a handler to a topic. Resolves to an unregister function.
89
- *
90
- * A shared `QueueClient` is constructed from `sdk` on first use and reused for
91
- * later registrations with the same SDK. Re-registering with the same topic
92
- * replaces the handler (HMR-safe; the SDK keys consumers by `consumerGroup`,
93
- * so calling unregister on a replaced registration is a no-op).
94
- *
95
- * If no SDK is available, or it does not expose `registerDevConsumer`,
96
- * resolves to a no-op unregister and logs a one-time warning.
59
+ * Bind a handler to a topic; resolves to an unregister function (a no-op once
60
+ * replaced). Without a usable SDK, warns once and no-ops.
97
61
  */
98
62
  export declare function registerVercelQueueConsumer(consumer: VercelQueueDevConsumer): Promise<() => void>;
package/dist/vite.d.mts CHANGED
@@ -12,17 +12,7 @@ export interface ViteTransport {
12
12
  }) => void;
13
13
  send: (payload: any) => void;
14
14
  }
15
- /**
16
- * Create a Vite `HotChannel` from an env-runner's RPC hooks.
17
- *
18
- * Use on the **host side** to bridge env-runner IPC → Vite's DevEnvironment transport.
19
- * Messages are namespaced by `envName` so multiple Vite environments can share one runner.
20
- */
15
+ /** Host-side Vite `HotChannel`, namespaced by `envName` so environments can share a runner. */
21
16
  export declare function createViteHotChannel(hooks: RunnerRPCHooks, envName: string): ViteHotChannel;
22
- /**
23
- * Create a Vite `ModuleRunner` transport from worker-side IPC primitives.
24
- *
25
- * Use on the **worker side** to bridge worker IPC → Vite's `ModuleRunner` transport.
26
- * Filters messages by `envName` so multiple Vite environments can share one IPC channel.
27
- */
17
+ /** Worker-side Vite `ModuleRunner` transport, filtered by `envName` to share one IPC channel. */
28
18
  export declare function createViteTransport(sendMessage: (data: any) => void, onMessage: (listener: (value: any) => void) => void, envName: string): ViteTransport;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "env-runner",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Generic environment runner for JavaScript runtimes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,25 +38,25 @@
38
38
  "crossws": "^0.4.12",
39
39
  "exsolve": "^1.1.1",
40
40
  "httpxy": "^0.5.5",
41
- "srvx": "^1.0.3"
41
+ "srvx": "^1.0.5"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@netlify/runtime": "^5.0.3",
45
- "@types/node": "^26.4.1",
45
+ "@types/node": "^26.5.1",
46
46
  "@vercel/queue": "^0.5.1",
47
47
  "@vitest/coverage-v8": "^5.0.0",
48
48
  "automd": "^0.4.3",
49
49
  "changelogen": "^0.6.2",
50
50
  "cjs-module-lexer": "^2.2.1",
51
51
  "env-runner-fixture": "link:",
52
- "es-module-lexer": "^3.0.1",
52
+ "es-module-lexer": "^3.0.2",
53
53
  "miniflare": "^4.20260730.0",
54
54
  "obuild": "^0.4.39",
55
55
  "oxfmt": ">=0.67.0",
56
- "oxlint": "^1.81.0",
56
+ "oxlint": "^1.82.0",
57
57
  "typescript": "^7.0.2",
58
58
  "vitest": "^5.0.0",
59
- "wrangler": "^4.129.0"
59
+ "wrangler": "^4.131.0"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "obuild",