@valbuild/server 0.97.7 → 0.98.0

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.
@@ -6,16 +6,17 @@ import * as path from 'path';
6
6
  import path__default from 'path';
7
7
  import fs, { promises } from 'fs';
8
8
  import vm from 'node:vm';
9
- import { Module } from 'node:module';
9
+ import { Module, createRequire } from 'node:module';
10
10
  import { resolveSchemaSourceFixForError, getErrorMessageFromUnknownJson, Patch, ParentRef, VAL_ENABLE_COOKIE_NAME, VAL_STATE_COOKIE, VAL_SESSION_COOKIE, Api } from '@valbuild/shared/internal';
11
11
  import { createUIRequestHandler } from '@valbuild/ui/server';
12
12
  import crypto$1 from 'crypto';
13
- import { z } from 'zod';
13
+ import z$1, { z } from 'zod';
14
14
  import sizeOf from 'image-size';
15
15
  import { fromError } from 'zod-validation-error';
16
16
  import { transform } from 'sucrase';
17
17
  import http from 'http';
18
18
  import https from 'https';
19
+ import fs$1 from 'fs/promises';
19
20
 
20
21
  class ValSyntaxError {
21
22
  constructor(message, node) {
@@ -925,6 +926,15 @@ const getCompilerOptions = (rootDir, parseConfigHost) => {
925
926
  return parsedConfigFile.options;
926
927
  };
927
928
 
929
+ /**
930
+ * The filesystem seam `loadValModules` reads through.
931
+ *
932
+ * Defaults to `ts.sys`, i.e. the real filesystem. An editor integration (see
933
+ * `@valbuild/language-server`) passes a host that overlays unsaved buffers, so
934
+ * that evaluation sees what the user is looking at rather than what was last
935
+ * saved.
936
+ */
937
+
928
938
  /**
929
939
  * Loads the project's root `val.modules.ts` (or `.js`) using Node's `vm`
930
940
  * module and returns its default export (a `ValModules` registry).
@@ -946,24 +956,24 @@ const getCompilerOptions = (rootDir, parseConfigHost) => {
946
956
  * same trust level as running the project's build. It must never be used to
947
957
  * evaluate untrusted or third-party modules.
948
958
  */
949
- function loadValModules(projectRoot) {
950
- const valModulesPath = findValModulesPath(projectRoot);
959
+ function loadValModules(projectRoot, host = ts.sys) {
960
+ const valModulesPath = findValModulesPath(projectRoot, host);
951
961
  if (!valModulesPath) {
952
962
  throw Error(`Could not find 'val.modules.ts' nor 'val.modules.js' in project root: '${projectRoot}'`);
953
963
  }
954
- const compilerOptions = getCompilerOptions(projectRoot, ts.sys);
964
+ const compilerOptions = getCompilerOptions(projectRoot, host);
955
965
  const cache = {};
956
- const loaded = loadModule(valModulesPath, cache, compilerOptions);
966
+ const loaded = loadModule(valModulesPath, cache, compilerOptions, host);
957
967
  const valModules = loaded.exports.default;
958
968
  if (!valModules) {
959
969
  throw Error(`Val modules file at path: '${valModulesPath}' must have a default export. Got: ${valModules}`);
960
970
  }
961
971
  return valModules;
962
972
  }
963
- function findValModulesPath(projectRoot) {
973
+ function findValModulesPath(projectRoot, host) {
964
974
  for (const fileName of ["val.modules.ts", "val.modules.js"]) {
965
975
  const candidate = path__default.join(projectRoot, fileName);
966
- if (fs.existsSync(candidate)) {
976
+ if (host.fileExists(candidate)) {
967
977
  return candidate;
968
978
  }
969
979
  }
@@ -1004,12 +1014,15 @@ function makeStub(spec) {
1004
1014
  const stub = new Proxy({}, handler);
1005
1015
  return stub;
1006
1016
  }
1007
- function loadModule(absPath, cache, compilerOptions) {
1017
+ function loadModule(absPath, cache, compilerOptions, host) {
1008
1018
  const cached = cache[absPath];
1009
1019
  if (cached) {
1010
1020
  return cached;
1011
1021
  }
1012
- const code = fs.readFileSync(absPath, "utf-8");
1022
+ const code = host.readFile(absPath);
1023
+ if (code === undefined) {
1024
+ throw Error(`Could not read Val module file: '${absPath}'`);
1025
+ }
1013
1026
  const transpiled = ts.transpileModule(code, {
1014
1027
  compilerOptions: {
1015
1028
  target: ts.ScriptTarget.ES2020,
@@ -1032,17 +1045,17 @@ function loadModule(absPath, cache, compilerOptions) {
1032
1045
  return makeStub(spec);
1033
1046
  }
1034
1047
  if (spec.startsWith(".") || path__default.isAbsolute(spec)) {
1035
- const resolved = resolveRelative(dirName, spec);
1048
+ const resolved = resolveRelative(dirName, spec, host);
1036
1049
  if (!resolved) {
1037
1050
  throw Error(`Could not resolve module '${spec}' from '${absPath}'`);
1038
1051
  }
1039
- return loadModule(resolved, cache, compilerOptions).exports;
1052
+ return loadModule(resolved, cache, compilerOptions, host).exports;
1040
1053
  }
1041
1054
  // Non-relative specifier: it might be a tsconfig path alias (e.g. "_/val.config")
1042
1055
  // pointing at a local source file, or an actual node_modules package.
1043
- const tsResolved = (_ts$resolveModuleName = ts.resolveModuleName(spec, absPath, compilerOptions, ts.sys).resolvedModule) === null || _ts$resolveModuleName === void 0 ? void 0 : _ts$resolveModuleName.resolvedFileName;
1056
+ const tsResolved = (_ts$resolveModuleName = ts.resolveModuleName(spec, absPath, compilerOptions, host).resolvedModule) === null || _ts$resolveModuleName === void 0 ? void 0 : _ts$resolveModuleName.resolvedFileName;
1044
1057
  if (tsResolved && !tsResolved.includes("/node_modules/") && !tsResolved.endsWith(".d.ts")) {
1045
- return loadModule(tsResolved, cache, compilerOptions).exports;
1058
+ return loadModule(tsResolved, cache, compilerOptions, host).exports;
1046
1059
  }
1047
1060
  // Real node_modules package – use the real require so user modules share
1048
1061
  // the same @valbuild/core instance as extractValModules.
@@ -1068,24 +1081,25 @@ function loadModule(absPath, cache, compilerOptions) {
1068
1081
  script.runInContext(context);
1069
1082
  return moduleObj;
1070
1083
  }
1071
- function resolveRelative(dirName, spec) {
1084
+ function resolveRelative(dirName, spec, host) {
1085
+ var _host$directoryExists;
1072
1086
  const base = path__default.resolve(dirName, spec);
1073
1087
  // Exact file (with extension)
1074
- if (fs.existsSync(base) && fs.statSync(base).isFile()) {
1088
+ if (host.fileExists(base)) {
1075
1089
  return base;
1076
1090
  }
1077
1091
  // Probe extensions (handles `./x.val` -> `./x.val.ts`)
1078
1092
  for (const ext of RESOLVE_EXTENSIONS) {
1079
1093
  const candidate = base + ext;
1080
- if (fs.existsSync(candidate)) {
1094
+ if (host.fileExists(candidate)) {
1081
1095
  return candidate;
1082
1096
  }
1083
1097
  }
1084
1098
  // Directory index
1085
- if (fs.existsSync(base) && fs.statSync(base).isDirectory()) {
1099
+ if (((_host$directoryExists = host.directoryExists) === null || _host$directoryExists === void 0 ? void 0 : _host$directoryExists.call(host, base)) ?? fs.existsSync(base)) {
1086
1100
  for (const ext of RESOLVE_EXTENSIONS) {
1087
1101
  const candidate = path__default.join(base, "index" + ext);
1088
- if (fs.existsSync(candidate)) {
1102
+ if (host.fileExists(candidate)) {
1089
1103
  return candidate;
1090
1104
  }
1091
1105
  }
@@ -1112,7 +1126,9 @@ async function createService(projectRoot, host = {
1112
1126
  }) {
1113
1127
  const compilerOptions = getCompilerOptions(projectRoot, host);
1114
1128
  const sourceFileHandler = new ValSourceFileHandler(projectRoot, compilerOptions, host);
1115
- const valModules = loadValModules(projectRoot);
1129
+ // Read val.modules (and everything it imports) through the same host, so an
1130
+ // embedder that overlays unsaved editor buffers evaluates what the user sees.
1131
+ const valModules = loadValModules(projectRoot, host);
1116
1132
  const extracted = await extractValModules(valModules);
1117
1133
  return new Service(projectRoot, sourceFileHandler, extracted);
1118
1134
  }
@@ -8209,6 +8225,294 @@ async function getFileMetadata(projectRoot, validationError) {
8209
8225
  return extractFileMetadata(fileRef);
8210
8226
  }
8211
8227
 
8228
+ function validateMetadata(actualMetadata, expectedMetadata) {
8229
+ const missingMetadata = [];
8230
+ const erroneousMetadata = {};
8231
+ if (typeof actualMetadata !== "object" || actualMetadata === null) {
8232
+ return {
8233
+ globalErrors: ["Metadata is wrong type: must be an object."]
8234
+ };
8235
+ }
8236
+ if (Array.isArray(actualMetadata)) {
8237
+ return {
8238
+ globalErrors: ["Metadata is wrong type: cannot be an array."]
8239
+ };
8240
+ }
8241
+ const recordMetadata = actualMetadata;
8242
+ const globalErrors = [];
8243
+ for (const anyKey in expectedMetadata) {
8244
+ if (typeof anyKey !== "string") {
8245
+ globalErrors.push(`Expected metadata has key '${anyKey}' that is not typeof 'string', but: '${typeof anyKey}'. This is most likely a Val bug.`);
8246
+ } else {
8247
+ if (anyKey in actualMetadata) {
8248
+ const key = anyKey;
8249
+ if (expectedMetadata[key] !== recordMetadata[key]) {
8250
+ erroneousMetadata[key] = `Expected metadata '${key}' to be ${JSON.stringify(expectedMetadata[key])}, but got ${JSON.stringify(recordMetadata[key])}.`;
8251
+ }
8252
+ } else {
8253
+ missingMetadata.push(anyKey);
8254
+ }
8255
+ }
8256
+ }
8257
+ if (globalErrors.length === 0 && missingMetadata.length === 0 && Object.keys(erroneousMetadata).length === 0) {
8258
+ return false;
8259
+ }
8260
+ return {
8261
+ missingMetadata,
8262
+ erroneousMetadata
8263
+ };
8264
+ }
8265
+
8266
+ /**
8267
+ * NOTE: this is intentionally NOT `SharedValConfig` from `@valbuild/shared`.
8268
+ * That schema requires `files.directory` to be exactly `/public/val`, whereas
8269
+ * this one accepts any path beneath `/public`. Unifying them would change which
8270
+ * configs are accepted, so they are kept separate on purpose.
8271
+ */
8272
+ const ValConfigSchema = z$1.object({
8273
+ project: z$1.string().optional(),
8274
+ root: z$1.string().optional(),
8275
+ files: z$1.object({
8276
+ directory: z$1.string().refine(val => (val === "/public" || val.startsWith("/public/") && !val.endsWith("/")) &&
8277
+ // Reject path traversal so the directory cannot escape /public
8278
+ !val.split("/").some(segment => segment === "." || segment === ".."), {
8279
+ message: "files.directory must start with '/public', must not end with '/' and must not contain '.' or '..' segments"
8280
+ })
8281
+ }).optional(),
8282
+ gitCommit: z$1.string().optional(),
8283
+ gitBranch: z$1.string().optional(),
8284
+ defaultTheme: z$1.union([z$1.literal("light"), z$1.literal("dark")]).optional(),
8285
+ ai: z$1.object({
8286
+ commitMessages: z$1.object({
8287
+ disabled: z$1.boolean().optional()
8288
+ }).optional()
8289
+ }).optional()
8290
+ });
8291
+
8292
+ /**
8293
+ * `ENOENT` (nothing at this path) and `ENOTDIR` (a path segment is a file) both
8294
+ * mean the config file simply is not there.
8295
+ */
8296
+ function isFileNotFound(err) {
8297
+ const code = err && typeof err === "object" && "code" in err ? err.code : undefined;
8298
+ return code === "ENOENT" || code === "ENOTDIR";
8299
+ }
8300
+
8301
+ /**
8302
+ * Read and evaluate a `val.config.{ts,js}` file from disk.
8303
+ *
8304
+ * Returns `null` if the file does not exist, and throws if it exists but does
8305
+ * not export a valid `config` object.
8306
+ *
8307
+ * Used by the Val CLI and by `@valbuild/language-server`, so that an editor
8308
+ * resolves project config exactly the way the CLI does.
8309
+ */
8310
+ async function evalValConfigFile(projectRoot, configFileName) {
8311
+ const valConfigPath = path__default.join(projectRoot, configFileName);
8312
+ let code;
8313
+ try {
8314
+ code = await fs$1.readFile(valConfigPath, "utf-8");
8315
+ } catch (err) {
8316
+ // A missing file means "this project does not use this config file name",
8317
+ // which callers handle by trying the next candidate. Anything else (no read
8318
+ // permission, an IO error, a directory where a file was expected) is a real
8319
+ // problem and must not be reported as "no config".
8320
+ if (isFileNotFound(err)) {
8321
+ return null;
8322
+ }
8323
+ throw Error(`Could not read Val config file at path: '${valConfigPath}': ${err instanceof Error ? err.message : String(err)}`);
8324
+ }
8325
+ if (!code.trim()) {
8326
+ return null;
8327
+ }
8328
+ const transpiled = ts.transpileModule(code, {
8329
+ compilerOptions: {
8330
+ target: ts.ScriptTarget.ES2020,
8331
+ module: ts.ModuleKind.CommonJS,
8332
+ esModuleInterop: true
8333
+ },
8334
+ fileName: valConfigPath
8335
+ });
8336
+ const projectRootRequire = createRequire(valConfigPath);
8337
+ const exportsObj = {};
8338
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8339
+ const sandbox = {
8340
+ exports: exportsObj,
8341
+ module: {
8342
+ exports: exportsObj
8343
+ },
8344
+ require: projectRootRequire,
8345
+ // NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
8346
+ __filename: valConfigPath,
8347
+ __dirname: projectRoot,
8348
+ console,
8349
+ process
8350
+ };
8351
+ sandbox.global = sandbox;
8352
+ const context = vm.createContext(sandbox);
8353
+ const script = new vm.Script(transpiled.outputText, {
8354
+ filename: valConfigPath
8355
+ });
8356
+ script.runInContext(context);
8357
+ const valConfig = sandbox.module.exports.config;
8358
+ if (!valConfig) {
8359
+ throw Error(`Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`);
8360
+ }
8361
+ const result = ValConfigSchema.safeParse(valConfig);
8362
+ if (!result.success) {
8363
+ throw Error(`Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`);
8364
+ }
8365
+ return result.data;
8366
+ }
8367
+
8368
+ /**
8369
+ * Resolve the project's Val config, trying the TypeScript file first and
8370
+ * falling back to JavaScript. Mirrors what the CLI has always done at each of
8371
+ * its call sites.
8372
+ */
8373
+ async function findAndEvalValConfigFile(projectRoot) {
8374
+ return (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
8375
+ }
8376
+
8377
+ /**
8378
+ * The Val login device flow, as reusable primitives.
8379
+ *
8380
+ * The CLI wraps these with terminal output, and `@valbuild/language-server`
8381
+ * wraps them with LSP `window/showDocument` and progress reporting. Neither the
8382
+ * polling nor the token handling is duplicated between them.
8383
+ *
8384
+ * Deliberately no `console` output and no `process.exit`: every failure is a
8385
+ * thrown {@link ValLoginError} so an embedder can decide how to surface it.
8386
+ */
8387
+
8388
+ const DEFAULT_LOGIN_HOST = "https://admin.val.build";
8389
+ function defaultHost() {
8390
+ return process.env.VAL_BUILD_URL || DEFAULT_LOGIN_HOST;
8391
+ }
8392
+ class ValLoginError extends Error {
8393
+ constructor(code, message, details) {
8394
+ super(message);
8395
+ this.code = code;
8396
+ this.details = details;
8397
+ this.name = "ValLoginError";
8398
+ }
8399
+ }
8400
+
8401
+ /** A login attempt that is waiting for the user to confirm in a browser. */
8402
+
8403
+ /**
8404
+ * Begin a login attempt. The caller is responsible for getting
8405
+ * {@link ValLoginSession.url} in front of the user.
8406
+ */
8407
+ async function startValLogin(options = {}) {
8408
+ var _response$headers$get;
8409
+ const host = options.host ?? defaultHost();
8410
+ const response = await fetch(`${host}/api/login`, {
8411
+ method: "POST",
8412
+ headers: {
8413
+ "Content-Type": "application/json"
8414
+ }
8415
+ });
8416
+ if (response.status >= 500) {
8417
+ const text = await response.text().catch(() => "");
8418
+ throw new ValLoginError("server-error", "An error occurred on the server.", text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
8419
+ }
8420
+ if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
8421
+ const text = await response.text().catch(() => "");
8422
+ throw new ValLoginError("unexpected-content-type", "Unexpected failure while trying to login (content type was not JSON).", text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
8423
+ }
8424
+ const json = await response.json();
8425
+ const nonce = json === null || json === void 0 ? void 0 : json.nonce;
8426
+ const url = json === null || json === void 0 ? void 0 : json.url;
8427
+ if (typeof nonce !== "string" || typeof url !== "string") {
8428
+ throw new ValLoginError("unexpected-response", "Unexpected response from the server.", JSON.stringify(json));
8429
+ }
8430
+ return {
8431
+ nonce,
8432
+ url
8433
+ };
8434
+ }
8435
+ const DEFAULT_LOGIN_MAX_DURATION = 5 * 60 * 1000; // 5 minutes
8436
+ const DEFAULT_LOGIN_POLL_INTERVAL = 1000;
8437
+
8438
+ /**
8439
+ * Poll until the user confirms the login in their browser.
8440
+ *
8441
+ * Accepts an `AbortSignal` so an editor can cancel the flow when the user
8442
+ * dismisses the prompt, instead of leaving a poll loop running for 5 minutes.
8443
+ */
8444
+ async function awaitValLoginConfirmation(nonce, options = {}) {
8445
+ const host = options.host ?? defaultHost();
8446
+ const maxDuration = options.maxDurationMs ?? DEFAULT_LOGIN_MAX_DURATION;
8447
+ const pollInterval = options.pollIntervalMs ?? DEFAULT_LOGIN_POLL_INTERVAL;
8448
+ const now = options.now ?? (() => Date.now());
8449
+ const start = now();
8450
+ while (now() - start < maxDuration) {
8451
+ var _options$signal, _options$signal2;
8452
+ if ((_options$signal = options.signal) !== null && _options$signal !== void 0 && _options$signal.aborted) {
8453
+ throw new ValLoginError("aborted", "Login was cancelled.");
8454
+ }
8455
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
8456
+ if ((_options$signal2 = options.signal) !== null && _options$signal2 !== void 0 && _options$signal2.aborted) {
8457
+ throw new ValLoginError("aborted", "Login was cancelled.");
8458
+ }
8459
+ const response = await fetch(`${host}/api/login?token=${nonce}&consume=true`, {
8460
+ method: "POST"
8461
+ });
8462
+ if (response.status >= 500) {
8463
+ throw new ValLoginError("server-error", "An error occurred on the server.", `Status: ${response.status}`);
8464
+ }
8465
+ if (response.status === 200) {
8466
+ var _json$profile;
8467
+ const json = await response.json().catch(() => null);
8468
+ if (!json) {
8469
+ // A 200 with an empty body is not a confirmation; keep polling rather
8470
+ // than failing the login, which is what the CLI has always done.
8471
+ continue;
8472
+ }
8473
+ if (typeof ((_json$profile = json.profile) === null || _json$profile === void 0 ? void 0 : _json$profile.email) === "string" && typeof json.pat === "string") {
8474
+ return json;
8475
+ }
8476
+ throw new ValLoginError("unexpected-response", "Unexpected response from the server.", JSON.stringify(json));
8477
+ }
8478
+ }
8479
+ throw new ValLoginError("timeout", "Login confirmation timed out.");
8480
+ }
8481
+
8482
+ /**
8483
+ * Write a completed login to the project's personal access token file.
8484
+ *
8485
+ * @returns the path the token was written to.
8486
+ */
8487
+ function persistPersonalAccessToken(projectRoot, result) {
8488
+ const filePath = getPersonalAccessTokenPath(projectRoot);
8489
+ // NOTE: no restrictive mode on the directory. This is the project's shared
8490
+ // `.val`, which also holds the pending patches the dev server reads and
8491
+ // writes; making it owner-only would lock out anyone who did not run the
8492
+ // login. Only the token file itself is tightened.
8493
+ fs.mkdirSync(path__default.dirname(filePath), {
8494
+ recursive: true
8495
+ });
8496
+ // The token is a credential: keep it owner-only rather than at the mercy of
8497
+ // the process umask. `mode` only applies when the file is created, so an
8498
+ // already existing file is chmod-ed explicitly.
8499
+ fs.writeFileSync(filePath, JSON.stringify(result, null, 2), {
8500
+ mode: 0o600
8501
+ });
8502
+ try {
8503
+ fs.chmodSync(filePath, 0o600);
8504
+ } catch {
8505
+ // Not all platforms / filesystems support POSIX modes (Windows, some
8506
+ // network mounts). Failing to tighten them must not fail the login.
8507
+ }
8508
+ return filePath;
8509
+ }
8510
+
8511
+ /**
8512
+ * NOTE: `@valbuild/language-server` has a near-identical `modulePathMap.ts`.
8513
+ * Fix traversal bugs in both, or fold them together.
8514
+ */
8515
+
8212
8516
  function getModulePathRange(modulePath, modulePathMap,
8213
8517
  // Which part of an object/record member to point at. For an object property
8214
8518
  // the resolved node's own range is the *key* (property name); the *value*
@@ -8467,4 +8771,4 @@ function readCapturedReport(snapshotDir) {
8467
8771
  return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
8468
8772
  }
8469
8773
 
8470
- export { Service, ValFSHost, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, compareWithCapturedReport, createFixPatch, createModulePathMap, createService, createValApiRouter, createValServer, decodeJwt, encodeJwt, formatPatchSourceError, formatSyntaxErrorTree, getCompilerOptions, getExpire, getModulePathRange, getPersonalAccessTokenPath, getSettings, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, readCapturedReport, replaySnapshot, safeReadGit, uploadRemoteFile };
8774
+ export { DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_MAX_DURATION, DEFAULT_LOGIN_POLL_INTERVAL, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, compareWithCapturedReport, createFixPatch, createModulePathMap, createService, createValApiRouter, createValServer, decodeJwt, downloadFileFromRemote, encodeJwt, evalValConfigFile, extractFileMetadata, extractImageMetadata, findAndEvalValConfigFile, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, hasRemoteFileSchema, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, replaySnapshot, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata };
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "types": "dist/valbuild-server.cjs.d.ts",
19
- "version": "0.97.7",
19
+ "version": "0.98.0",
20
20
  "devDependencies": {
21
21
  "@prettier/sync": "^0.6.1",
22
22
  "@types/jest": "^30.0.0"