@valbuild/server 0.97.7 → 0.99.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.
@@ -13,12 +13,13 @@ var node_module = require('node:module');
13
13
  var internal = require('@valbuild/shared/internal');
14
14
  var server = require('@valbuild/ui/server');
15
15
  var crypto$1 = require('crypto');
16
- var zod = require('zod');
16
+ var z = require('zod');
17
17
  var sizeOf = require('image-size');
18
18
  var zodValidationError = require('zod-validation-error');
19
19
  var sucrase = require('sucrase');
20
20
  var http = require('http');
21
21
  var https = require('https');
22
+ var fs$1 = require('fs/promises');
22
23
 
23
24
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
24
25
 
@@ -45,9 +46,11 @@ var path__namespace = /*#__PURE__*/_interopNamespace(path);
45
46
  var fs__default = /*#__PURE__*/_interopDefault(fs);
46
47
  var vm__default = /*#__PURE__*/_interopDefault(vm);
47
48
  var crypto__default = /*#__PURE__*/_interopDefault(crypto$1);
49
+ var z__default = /*#__PURE__*/_interopDefault(z);
48
50
  var sizeOf__default = /*#__PURE__*/_interopDefault(sizeOf);
49
51
  var http__default = /*#__PURE__*/_interopDefault(http);
50
52
  var https__default = /*#__PURE__*/_interopDefault(https);
53
+ var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
51
54
 
52
55
  class ValSyntaxError {
53
56
  constructor(message, node) {
@@ -957,6 +960,15 @@ const getCompilerOptions = (rootDir, parseConfigHost) => {
957
960
  return parsedConfigFile.options;
958
961
  };
959
962
 
963
+ /**
964
+ * The filesystem seam `loadValModules` reads through.
965
+ *
966
+ * Defaults to `ts.sys`, i.e. the real filesystem. An editor integration (see
967
+ * `@valbuild/language-server`) passes a host that overlays unsaved buffers, so
968
+ * that evaluation sees what the user is looking at rather than what was last
969
+ * saved.
970
+ */
971
+
960
972
  /**
961
973
  * Loads the project's root `val.modules.ts` (or `.js`) using Node's `vm`
962
974
  * module and returns its default export (a `ValModules` registry).
@@ -978,24 +990,24 @@ const getCompilerOptions = (rootDir, parseConfigHost) => {
978
990
  * same trust level as running the project's build. It must never be used to
979
991
  * evaluate untrusted or third-party modules.
980
992
  */
981
- function loadValModules(projectRoot) {
982
- const valModulesPath = findValModulesPath(projectRoot);
993
+ function loadValModules(projectRoot, host = ts__default["default"].sys) {
994
+ const valModulesPath = findValModulesPath(projectRoot, host);
983
995
  if (!valModulesPath) {
984
996
  throw Error(`Could not find 'val.modules.ts' nor 'val.modules.js' in project root: '${projectRoot}'`);
985
997
  }
986
- const compilerOptions = getCompilerOptions(projectRoot, ts__default["default"].sys);
998
+ const compilerOptions = getCompilerOptions(projectRoot, host);
987
999
  const cache = {};
988
- const loaded = loadModule(valModulesPath, cache, compilerOptions);
1000
+ const loaded = loadModule(valModulesPath, cache, compilerOptions, host);
989
1001
  const valModules = loaded.exports.default;
990
1002
  if (!valModules) {
991
1003
  throw Error(`Val modules file at path: '${valModulesPath}' must have a default export. Got: ${valModules}`);
992
1004
  }
993
1005
  return valModules;
994
1006
  }
995
- function findValModulesPath(projectRoot) {
1007
+ function findValModulesPath(projectRoot, host) {
996
1008
  for (const fileName of ["val.modules.ts", "val.modules.js"]) {
997
1009
  const candidate = path__namespace["default"].join(projectRoot, fileName);
998
- if (fs__default["default"].existsSync(candidate)) {
1010
+ if (host.fileExists(candidate)) {
999
1011
  return candidate;
1000
1012
  }
1001
1013
  }
@@ -1036,12 +1048,15 @@ function makeStub(spec) {
1036
1048
  const stub = new Proxy({}, handler);
1037
1049
  return stub;
1038
1050
  }
1039
- function loadModule(absPath, cache, compilerOptions) {
1051
+ function loadModule(absPath, cache, compilerOptions, host) {
1040
1052
  const cached = cache[absPath];
1041
1053
  if (cached) {
1042
1054
  return cached;
1043
1055
  }
1044
- const code = fs__default["default"].readFileSync(absPath, "utf-8");
1056
+ const code = host.readFile(absPath);
1057
+ if (code === undefined) {
1058
+ throw Error(`Could not read Val module file: '${absPath}'`);
1059
+ }
1045
1060
  const transpiled = ts__default["default"].transpileModule(code, {
1046
1061
  compilerOptions: {
1047
1062
  target: ts__default["default"].ScriptTarget.ES2020,
@@ -1064,17 +1079,17 @@ function loadModule(absPath, cache, compilerOptions) {
1064
1079
  return makeStub(spec);
1065
1080
  }
1066
1081
  if (spec.startsWith(".") || path__namespace["default"].isAbsolute(spec)) {
1067
- const resolved = resolveRelative(dirName, spec);
1082
+ const resolved = resolveRelative(dirName, spec, host);
1068
1083
  if (!resolved) {
1069
1084
  throw Error(`Could not resolve module '${spec}' from '${absPath}'`);
1070
1085
  }
1071
- return loadModule(resolved, cache, compilerOptions).exports;
1086
+ return loadModule(resolved, cache, compilerOptions, host).exports;
1072
1087
  }
1073
1088
  // Non-relative specifier: it might be a tsconfig path alias (e.g. "_/val.config")
1074
1089
  // pointing at a local source file, or an actual node_modules package.
1075
- const tsResolved = (_ts$resolveModuleName = ts__default["default"].resolveModuleName(spec, absPath, compilerOptions, ts__default["default"].sys).resolvedModule) === null || _ts$resolveModuleName === void 0 ? void 0 : _ts$resolveModuleName.resolvedFileName;
1090
+ const tsResolved = (_ts$resolveModuleName = ts__default["default"].resolveModuleName(spec, absPath, compilerOptions, host).resolvedModule) === null || _ts$resolveModuleName === void 0 ? void 0 : _ts$resolveModuleName.resolvedFileName;
1076
1091
  if (tsResolved && !tsResolved.includes("/node_modules/") && !tsResolved.endsWith(".d.ts")) {
1077
- return loadModule(tsResolved, cache, compilerOptions).exports;
1092
+ return loadModule(tsResolved, cache, compilerOptions, host).exports;
1078
1093
  }
1079
1094
  // Real node_modules package – use the real require so user modules share
1080
1095
  // the same @valbuild/core instance as extractValModules.
@@ -1100,24 +1115,25 @@ function loadModule(absPath, cache, compilerOptions) {
1100
1115
  script.runInContext(context);
1101
1116
  return moduleObj;
1102
1117
  }
1103
- function resolveRelative(dirName, spec) {
1118
+ function resolveRelative(dirName, spec, host) {
1119
+ var _host$directoryExists;
1104
1120
  const base = path__namespace["default"].resolve(dirName, spec);
1105
1121
  // Exact file (with extension)
1106
- if (fs__default["default"].existsSync(base) && fs__default["default"].statSync(base).isFile()) {
1122
+ if (host.fileExists(base)) {
1107
1123
  return base;
1108
1124
  }
1109
1125
  // Probe extensions (handles `./x.val` -> `./x.val.ts`)
1110
1126
  for (const ext of RESOLVE_EXTENSIONS) {
1111
1127
  const candidate = base + ext;
1112
- if (fs__default["default"].existsSync(candidate)) {
1128
+ if (host.fileExists(candidate)) {
1113
1129
  return candidate;
1114
1130
  }
1115
1131
  }
1116
1132
  // Directory index
1117
- if (fs__default["default"].existsSync(base) && fs__default["default"].statSync(base).isDirectory()) {
1133
+ if (((_host$directoryExists = host.directoryExists) === null || _host$directoryExists === void 0 ? void 0 : _host$directoryExists.call(host, base)) ?? fs__default["default"].existsSync(base)) {
1118
1134
  for (const ext of RESOLVE_EXTENSIONS) {
1119
1135
  const candidate = path__namespace["default"].join(base, "index" + ext);
1120
- if (fs__default["default"].existsSync(candidate)) {
1136
+ if (host.fileExists(candidate)) {
1121
1137
  return candidate;
1122
1138
  }
1123
1139
  }
@@ -1144,7 +1160,9 @@ async function createService(projectRoot, host = {
1144
1160
  }) {
1145
1161
  const compilerOptions = getCompilerOptions(projectRoot, host);
1146
1162
  const sourceFileHandler = new ValSourceFileHandler(projectRoot, compilerOptions, host);
1147
- const valModules = loadValModules(projectRoot);
1163
+ // Read val.modules (and everything it imports) through the same host, so an
1164
+ // embedder that overlays unsaved editor buffers evaluates what the user sees.
1165
+ const valModules = loadValModules(projectRoot, host);
1148
1166
  const extracted = await core.extractValModules(valModules);
1149
1167
  return new Service(projectRoot, sourceFileHandler, extracted);
1150
1168
  }
@@ -1254,9 +1272,9 @@ function decodeJwt(token, secretKey) {
1254
1272
  function getExpire() {
1255
1273
  return Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 4; // 4 days
1256
1274
  }
1257
- const JwtHeaderSchema = zod.z.object({
1258
- alg: zod.z.literal("HS256"),
1259
- typ: zod.z.literal("JWT")
1275
+ const JwtHeaderSchema = z.z.object({
1276
+ alg: z.z.literal("HS256"),
1277
+ typ: z.z.literal("JWT")
1260
1278
  });
1261
1279
  const jwtHeader = {
1262
1280
  alg: "HS256",
@@ -2271,9 +2289,9 @@ class ValOpsFS extends ValOps {
2271
2289
  });
2272
2290
  if (res.ok) {
2273
2291
  const json = await res.json();
2274
- const parsed = zod.z.object({
2275
- nonce: zod.z.string(),
2276
- expiresAt: zod.z.string()
2292
+ const parsed = z.z.object({
2293
+ nonce: z.z.string(),
2294
+ expiresAt: z.z.string()
2277
2295
  }).safeParse(json);
2278
2296
  if (parsed.success) {
2279
2297
  return {
@@ -2834,7 +2852,7 @@ class ValOpsFS extends ValOps {
2834
2852
  }]
2835
2853
  };
2836
2854
  }
2837
- const metadataParseRes = this.parseJsonFile(metadataFilePath, zod.z.record(zod.z.string(), zod.z.union([zod.z.string(), zod.z.number()])));
2855
+ const metadataParseRes = this.parseJsonFile(metadataFilePath, z.z.record(z.z.string(), z.z.union([z.z.string(), z.z.number()])));
2838
2856
  if (metadataParseRes.error) {
2839
2857
  return {
2840
2858
  errors: [metadataParseRes.error]
@@ -3240,121 +3258,121 @@ class FSOpsHost {
3240
3258
  fs__default["default"].copyFileSync(from, to);
3241
3259
  }
3242
3260
  }
3243
- const FSPatch = zod.z.object({
3244
- path: zod.z.string().refine(p => p.startsWith("/") && p.includes(".val."), "Path is not valid. Must start with '/' and include '.val.'"),
3261
+ const FSPatch = z.z.object({
3262
+ path: z.z.string().refine(p => p.startsWith("/") && p.includes(".val."), "Path is not valid. Must start with '/' and include '.val.'"),
3245
3263
  patch: internal.Patch,
3246
- patchId: zod.z.string(),
3247
- baseSha: zod.z.string(),
3264
+ patchId: z.z.string(),
3265
+ baseSha: z.z.string(),
3248
3266
  parentRef: internal.ParentRef,
3249
- authorId: zod.z.string().refine(p => true).nullable(),
3250
- createdAt: zod.z.string().datetime(),
3251
- coreVersion: zod.z.string().nullable(),
3267
+ authorId: z.z.string().refine(p => true).nullable(),
3268
+ createdAt: z.z.string().datetime(),
3269
+ coreVersion: z.z.string().nullable(),
3252
3270
  // TODO: use this to check if patch is compatible with current core version?
3253
- sessionId: zod.z.string().nullable()
3271
+ sessionId: z.z.string().nullable()
3254
3272
  });
3255
- const FSPatchBase = zod.z.object({
3256
- baseSha: zod.z.string().refine(p => true),
3257
- timestamp: zod.z.string().datetime()
3273
+ const FSPatchBase = z.z.object({
3274
+ baseSha: z.z.string().refine(p => true),
3275
+ timestamp: z.z.string().datetime()
3258
3276
  });
3259
3277
 
3260
3278
  const textEncoder$1 = new TextEncoder();
3261
- const PatchId = zod.z.string().refine(s => !!s); // TODO: validate
3262
- const CommitSha = zod.z.string().refine(s => !!s); // TODO: validate
3263
- zod.z.string().refine(s => !!s); // TODO: validate
3264
- zod.z.string().refine(s => !!s); // TODO: validate
3265
- const ModuleFilePath = zod.z.string().refine(s => !!s); // TODO: validate
3266
- const Metadata = zod.z.union([zod.z.object({
3267
- mimeType: zod.z.string(),
3268
- width: zod.z.number(),
3269
- height: zod.z.number()
3270
- }), zod.z.object({
3271
- mimeType: zod.z.string()
3279
+ const PatchId = z.z.string().refine(s => !!s); // TODO: validate
3280
+ const CommitSha = z.z.string().refine(s => !!s); // TODO: validate
3281
+ z.z.string().refine(s => !!s); // TODO: validate
3282
+ z.z.string().refine(s => !!s); // TODO: validate
3283
+ const ModuleFilePath = z.z.string().refine(s => !!s); // TODO: validate
3284
+ const Metadata = z.z.union([z.z.object({
3285
+ mimeType: z.z.string(),
3286
+ width: z.z.number(),
3287
+ height: z.z.number()
3288
+ }), z.z.object({
3289
+ mimeType: z.z.string()
3272
3290
  })]);
3273
- const MetadataRes = zod.z.object({
3291
+ const MetadataRes = z.z.object({
3274
3292
  filePath: ModuleFilePath,
3275
3293
  metadata: Metadata,
3276
- type: zod.z.union([zod.z.literal("file"), zod.z.literal("image")]).nullable()
3294
+ type: z.z.union([z.z.literal("file"), z.z.literal("image")]).nullable()
3277
3295
  });
3278
- const SummaryResponse = zod.z.object({
3279
- commitSummary: zod.z.string()
3296
+ const SummaryResponse = z.z.object({
3297
+ commitSummary: z.z.string()
3280
3298
  });
3281
- const GetApplicablePatches = zod.z.object({
3282
- patches: zod.z.array(zod.z.object({
3283
- path: zod.z.string(),
3299
+ const GetApplicablePatches = z.z.object({
3300
+ patches: z.z.array(z.z.object({
3301
+ path: z.z.string(),
3284
3302
  patch: internal.Patch.nullable(),
3285
- patchId: zod.z.string(),
3286
- authorId: zod.z.string().nullable(),
3287
- baseSha: zod.z.string(),
3288
- createdAt: zod.z.string(),
3289
- applied: zod.z.object({
3290
- commitSha: zod.z.string()
3303
+ patchId: z.z.string(),
3304
+ authorId: z.z.string().nullable(),
3305
+ baseSha: z.z.string(),
3306
+ createdAt: z.z.string(),
3307
+ applied: z.z.object({
3308
+ commitSha: z.z.string()
3291
3309
  }).nullable()
3292
3310
  })),
3293
- commits: zod.z.array(zod.z.object({
3294
- commitSha: zod.z.string(),
3295
- clientCommitSha: zod.z.string(),
3296
- parentCommitSha: zod.z.string(),
3297
- commitMessage: zod.z.string().nullable(),
3298
- branch: zod.z.string(),
3299
- creator: zod.z.string(),
3300
- createdAt: zod.z.string()
3311
+ commits: z.z.array(z.z.object({
3312
+ commitSha: z.z.string(),
3313
+ clientCommitSha: z.z.string(),
3314
+ parentCommitSha: z.z.string(),
3315
+ commitMessage: z.z.string().nullable(),
3316
+ branch: z.z.string(),
3317
+ creator: z.z.string(),
3318
+ createdAt: z.z.string()
3301
3319
  })).optional(),
3302
- deployments: zod.z.array(zod.z.object({
3303
- deploymentId: zod.z.string(),
3304
- commitSha: zod.z.string(),
3305
- deploymentState: zod.z.string(),
3306
- createdAt: zod.z.string(),
3307
- updatedAt: zod.z.string()
3320
+ deployments: z.z.array(z.z.object({
3321
+ deploymentId: z.z.string(),
3322
+ commitSha: z.z.string(),
3323
+ deploymentState: z.z.string(),
3324
+ createdAt: z.z.string(),
3325
+ updatedAt: z.z.string()
3308
3326
  })).optional()
3309
3327
  });
3310
- const FilesResponse = zod.z.object({
3311
- files: zod.z.array(zod.z.union([zod.z.object({
3312
- filePath: zod.z.string(),
3313
- location: zod.z.literal("patch"),
3328
+ const FilesResponse = z.z.object({
3329
+ files: z.z.array(z.z.union([z.z.object({
3330
+ filePath: z.z.string(),
3331
+ location: z.z.literal("patch"),
3314
3332
  patchId: PatchId,
3315
- value: zod.z.string(),
3316
- remote: zod.z.boolean()
3317
- }), zod.z.object({
3318
- filePath: zod.z.string(),
3319
- location: zod.z.literal("repo"),
3333
+ value: z.z.string(),
3334
+ remote: z.z.boolean()
3335
+ }), z.z.object({
3336
+ filePath: z.z.string(),
3337
+ location: z.z.literal("repo"),
3320
3338
  commitSha: CommitSha,
3321
- value: zod.z.string()
3339
+ value: z.z.string()
3322
3340
  })])),
3323
- errors: zod.z.array(zod.z.union([zod.z.object({
3324
- filePath: zod.z.string(),
3325
- location: zod.z.literal("patch"),
3341
+ errors: z.z.array(z.z.union([z.z.object({
3342
+ filePath: z.z.string(),
3343
+ location: z.z.literal("patch"),
3326
3344
  patchId: PatchId,
3327
- message: zod.z.string(),
3328
- remote: zod.z.boolean()
3329
- }), zod.z.object({
3330
- filePath: zod.z.string(),
3331
- location: zod.z.literal("repo"),
3345
+ message: z.z.string(),
3346
+ remote: z.z.boolean()
3347
+ }), z.z.object({
3348
+ filePath: z.z.string(),
3349
+ location: z.z.literal("repo"),
3332
3350
  commitSha: CommitSha,
3333
- message: zod.z.string()
3351
+ message: z.z.string()
3334
3352
  })])).optional()
3335
3353
  });
3336
- const SavePatchResponse = zod.z.object({
3354
+ const SavePatchResponse = z.z.object({
3337
3355
  patchId: PatchId
3338
3356
  });
3339
- const DeletePatchesResponse = zod.z.object({
3340
- deleted: zod.z.array(PatchId),
3341
- errors: zod.z.array(zod.z.object({
3342
- message: zod.z.string(),
3357
+ const DeletePatchesResponse = z.z.object({
3358
+ deleted: z.z.array(PatchId),
3359
+ errors: z.z.array(z.z.object({
3360
+ message: z.z.string(),
3343
3361
  patchId: PatchId
3344
3362
  })).optional()
3345
3363
  });
3346
- const SavePatchFileResponse = zod.z.object({
3364
+ const SavePatchFileResponse = z.z.object({
3347
3365
  patchId: PatchId,
3348
3366
  filePath: ModuleFilePath
3349
3367
  });
3350
- const CommitResponse = zod.z.object({
3351
- updatedFiles: zod.z.array(zod.z.string()),
3368
+ const CommitResponse = z.z.object({
3369
+ updatedFiles: z.z.array(z.z.string()),
3352
3370
  commit: CommitSha,
3353
- branch: zod.z.string()
3371
+ branch: z.z.string()
3354
3372
  });
3355
- const NonceResponse = zod.z.object({
3356
- nonce: zod.z.string(),
3357
- url: zod.z.string()
3373
+ const NonceResponse = z.z.object({
3374
+ nonce: z.z.string(),
3375
+ url: z.z.string()
3358
3376
  });
3359
3377
  class ValOpsHttp extends ValOps {
3360
3378
  constructor(contentUrl, project, commitSha,
@@ -3397,9 +3415,9 @@ class ValOpsHttp extends ValOps {
3397
3415
  });
3398
3416
  if (res.ok) {
3399
3417
  const json = await res.json();
3400
- const parsed = zod.z.object({
3401
- nonce: zod.z.string(),
3402
- expiresAt: zod.z.string()
3418
+ const parsed = z.z.object({
3419
+ nonce: z.z.string(),
3420
+ expiresAt: z.z.string()
3403
3421
  }).safeParse(json);
3404
3422
  if (parsed.success) {
3405
3423
  const {
@@ -4238,8 +4256,8 @@ class ValOpsHttp extends ValOps {
4238
4256
  };
4239
4257
  }
4240
4258
  if ((_res$headers$get5 = res.headers.get("Content-Type")) !== null && _res$headers$get5 !== void 0 && _res$headers$get5.includes("application/json")) {
4241
- const json = zod.z.object({
4242
- isNotFastForward: zod.z.boolean().optional()
4259
+ const json = z.z.object({
4260
+ isNotFastForward: z.z.boolean().optional()
4243
4261
  }).safeParse(await res.json());
4244
4262
  if (json.success && json.data.isNotFastForward) {
4245
4263
  return {
@@ -4272,10 +4290,10 @@ class ValOpsHttp extends ValOps {
4272
4290
  }
4273
4291
 
4274
4292
  const host = process.env.VAL_CONTENT_URL || core.DEFAULT_CONTENT_HOST;
4275
- const SettingsSchema = zod.z.object({
4276
- publicProjectId: zod.z.string(),
4277
- remoteFileBuckets: zod.z.array(zod.z.object({
4278
- bucket: zod.z.string()
4293
+ const SettingsSchema = z.z.object({
4294
+ publicProjectId: z.z.string(),
4295
+ remoteFileBuckets: z.z.array(z.z.object({
4296
+ bucket: z.z.string()
4279
4297
  }))
4280
4298
  });
4281
4299
  async function getSettings(projectName, auth) {
@@ -4421,21 +4439,21 @@ function hasRemoteFileSchema(schema) {
4421
4439
 
4422
4440
  /* eslint-disable @typescript-eslint/no-unused-vars */
4423
4441
  const ValServer = (valModules, options, callbacks) => {
4424
- const AIContentBlock = zod.z.union([zod.z.object({
4425
- type: zod.z.literal("text"),
4426
- text: zod.z.string()
4427
- }), zod.z.object({
4428
- type: zod.z.literal("image_url"),
4429
- url: zod.z.string()
4442
+ const AIContentBlock = z.z.union([z.z.object({
4443
+ type: z.z.literal("text"),
4444
+ text: z.z.string()
4445
+ }), z.z.object({
4446
+ type: z.z.literal("image_url"),
4447
+ url: z.z.string()
4430
4448
  })]);
4431
- const AIMessageContent = zod.z.union([zod.z.string(), zod.z.array(AIContentBlock)]);
4432
- const ProfilesResponse = zod.z.object({
4433
- profiles: zod.z.array(zod.z.object({
4434
- profileId: zod.z.string(),
4435
- fullName: zod.z.string(),
4436
- email: zod.z.string().optional(),
4437
- avatar: zod.z.object({
4438
- url: zod.z.string()
4449
+ const AIMessageContent = z.z.union([z.z.string(), z.z.array(AIContentBlock)]);
4450
+ const ProfilesResponse = z.z.object({
4451
+ profiles: z.z.array(z.z.object({
4452
+ profileId: z.z.string(),
4453
+ fullName: z.z.string(),
4454
+ email: z.z.string().optional(),
4455
+ avatar: z.z.object({
4456
+ url: z.z.string()
4439
4457
  }).nullable()
4440
4458
  }))
4441
4459
  });
@@ -4942,12 +4960,12 @@ const ValServer = (valModules, options, callbacks) => {
4942
4960
  headers: getAuthHeaders(data.token, "application/json")
4943
4961
  });
4944
4962
  if (fetchRes.status === 200) {
4945
- const json = zod.z.object({
4946
- member_role: zod.z.union([zod.z.literal("owner"), zod.z.literal("developer"), zod.z.literal("editor")]).optional(),
4947
- id: zod.z.string(),
4948
- full_name: zod.z.string().optional(),
4949
- username: zod.z.string().optional(),
4950
- avatar_url: zod.z.string().optional()
4963
+ const json = z.z.object({
4964
+ member_role: z.z.union([z.z.literal("owner"), z.z.literal("developer"), z.z.literal("editor")]).optional(),
4965
+ id: z.z.string(),
4966
+ full_name: z.z.string().optional(),
4967
+ username: z.z.string().optional(),
4968
+ avatar_url: z.z.string().optional()
4951
4969
  }).safeParse(await fetchRes.json());
4952
4970
  if (json.success) {
4953
4971
  return {
@@ -4969,8 +4987,8 @@ const ValServer = (valModules, options, callbacks) => {
4969
4987
  };
4970
4988
  }
4971
4989
  } else {
4972
- const json = zod.z.object({
4973
- message: zod.z.string()
4990
+ const json = z.z.object({
4991
+ message: z.z.string()
4974
4992
  }).safeParse(await fetchRes.json());
4975
4993
  const message = internal.getErrorMessageFromUnknownJson(json, "Unknown error");
4976
4994
  return {
@@ -5832,8 +5850,8 @@ const ValServer = (valModules, options, callbacks) => {
5832
5850
  }
5833
5851
  };
5834
5852
  }
5835
- const PostSaveBody = zod.z.object({
5836
- patchIds: zod.z.array(zod.z.string().refine(id => true // TODO:
5853
+ const PostSaveBody = z.z.object({
5854
+ patchIds: z.z.array(z.z.string().refine(id => true // TODO:
5837
5855
  ))
5838
5856
  });
5839
5857
  const bodyRes = PostSaveBody.safeParse(body);
@@ -6069,16 +6087,16 @@ const ValServer = (valModules, options, callbacks) => {
6069
6087
  const authData = authDataRes.json.remoteFileAuth;
6070
6088
  const execFetch = async headers => {
6071
6089
  try {
6072
- const SessionsResponse = zod.z.object({
6073
- sessions: zod.z.array(zod.z.object({
6074
- id: zod.z.string(),
6075
- name: zod.z.string().nullable(),
6076
- createdAt: zod.z.string(),
6077
- updatedAt: zod.z.string()
6090
+ const SessionsResponse = z.z.object({
6091
+ sessions: z.z.array(z.z.object({
6092
+ id: z.z.string(),
6093
+ name: z.z.string().nullable(),
6094
+ createdAt: z.z.string(),
6095
+ updatedAt: z.z.string()
6078
6096
  })),
6079
- nextCursor: zod.z.object({
6080
- updatedAt: zod.z.string(),
6081
- id: zod.z.string()
6097
+ nextCursor: z.z.object({
6098
+ updatedAt: z.z.string(),
6099
+ id: z.z.string()
6082
6100
  }).nullable().optional()
6083
6101
  });
6084
6102
  const params = new URLSearchParams();
@@ -6269,14 +6287,14 @@ const ValServer = (valModules, options, callbacks) => {
6269
6287
  const authData = authDataRes.json.remoteFileAuth;
6270
6288
  const execFetch = async headers => {
6271
6289
  try {
6272
- const SessionMessagesResponse = zod.z.object({
6273
- messages: zod.z.array(zod.z.object({
6274
- role: zod.z.string(),
6290
+ const SessionMessagesResponse = z.z.object({
6291
+ messages: z.z.array(z.z.object({
6292
+ role: z.z.string(),
6275
6293
  content: AIMessageContent
6276
6294
  })),
6277
- nextCursor: zod.z.object({
6278
- updatedAt: zod.z.string(),
6279
- id: zod.z.string()
6295
+ nextCursor: z.z.object({
6296
+ updatedAt: z.z.string(),
6297
+ id: z.z.string()
6280
6298
  }).nullable().optional()
6281
6299
  });
6282
6300
  const params = new URLSearchParams();
@@ -6425,14 +6443,14 @@ const ValServer = (valModules, options, callbacks) => {
6425
6443
  };
6426
6444
  }
6427
6445
  const rawUpstreamJson = await upstreamRes.json();
6428
- const UpstreamResponse = zod.z.object({
6429
- patchId: zod.z.string(),
6430
- files: zod.z.array(zod.z.object({
6431
- filePath: zod.z.string(),
6432
- metadata: zod.z.object({
6433
- width: zod.z.number(),
6434
- height: zod.z.number(),
6435
- mimeType: zod.z.string()
6446
+ const UpstreamResponse = z.z.object({
6447
+ patchId: z.z.string(),
6448
+ files: z.z.array(z.z.object({
6449
+ filePath: z.z.string(),
6450
+ metadata: z.z.object({
6451
+ width: z.z.number(),
6452
+ height: z.z.number(),
6453
+ mimeType: z.z.string()
6436
6454
  })
6437
6455
  }))
6438
6456
  });
@@ -6591,8 +6609,8 @@ const ValServer = (valModules, options, callbacks) => {
6591
6609
  }
6592
6610
  };
6593
6611
  }
6594
- const UpstreamResponse = zod.z.object({
6595
- key: zod.z.string()
6612
+ const UpstreamResponse = z.z.object({
6613
+ key: z.z.string()
6596
6614
  });
6597
6615
  const json = UpstreamResponse.safeParse(await upstreamRes.json());
6598
6616
  if (!json.success) {
@@ -6775,18 +6793,18 @@ function getStateFromCookie(stateCookie) {
6775
6793
  function createStateCookie(state) {
6776
6794
  return Buffer.from(JSON.stringify(state), "utf8").toString("base64");
6777
6795
  }
6778
- const ValAppJwtPayload = zod.z.object({
6779
- sub: zod.z.string(),
6780
- exp: zod.z.number(),
6781
- project: zod.z.string(),
6782
- org: zod.z.string()
6796
+ const ValAppJwtPayload = z.z.object({
6797
+ sub: z.z.string(),
6798
+ exp: z.z.number(),
6799
+ project: z.z.string(),
6800
+ org: z.z.string()
6783
6801
  });
6784
- const IntegratedServerJwtPayload = zod.z.object({
6785
- sub: zod.z.string(),
6786
- exp: zod.z.number(),
6787
- token: zod.z.string(),
6788
- org: zod.z.string(),
6789
- project: zod.z.string()
6802
+ const IntegratedServerJwtPayload = z.z.object({
6803
+ sub: z.z.string(),
6804
+ exp: z.z.number(),
6805
+ token: z.z.string(),
6806
+ org: z.z.string(),
6807
+ project: z.z.string()
6790
6808
  });
6791
6809
  async function withAuth(secret, cookies, errorMessageType, handler) {
6792
6810
  const cookie = cookies[internal.VAL_SESSION_COOKIE];
@@ -7282,7 +7300,7 @@ function createValApiRouter(route, valServerPromise, convert) {
7282
7300
  let query = {};
7283
7301
  if (reqDefinition.query) {
7284
7302
  const queryRules = reqDefinition.query;
7285
- const queryRes = zod.z.object(queryRules).safeParse(actualQueryParams);
7303
+ const queryRes = z.z.object(queryRules).safeParse(actualQueryParams);
7286
7304
  if (!queryRes.success) {
7287
7305
  return zodErrorResult(queryRes.error, `invalid query params: (${JSON.stringify(actualQueryParams)})`);
7288
7306
  }
@@ -7356,7 +7374,7 @@ function getCookies(req, cookiesDef) {
7356
7374
  input[name.trim()] = value;
7357
7375
  }
7358
7376
  }
7359
- return zod.z.object(cookiesDef).safeParse(input);
7377
+ return z.z.object(cookiesDef).safeParse(input);
7360
7378
  }
7361
7379
 
7362
7380
  const JsFileLookupMapping = [
@@ -7760,6 +7778,33 @@ async function downloadFileFromRemote(ref, filePath) {
7760
7778
  // record-level fix expands into per-entry errors that should point at the
7761
7779
  // individual entry (e.g. `?p="/public/val/logo.png"`) rather than the record.
7762
7780
 
7781
+ // Gallery entries are keyed by their file path. Remote galleries key uploaded
7782
+ // entries by a remote URL while keeping the file on disk at its local path, so
7783
+ // normalize a remote-URL key back to that local path for on-disk reads.
7784
+ function galleryKeyToLocalPath(key) {
7785
+ const res = core.Internal.remote.splitRemoteRef(key);
7786
+ return res.status === "success" ? `/${res.filePath}` : key;
7787
+ }
7788
+
7789
+ // Patch path of the record that holds a media entry. Media keys are file paths
7790
+ // that can contain dots, which sourceToPatchPath cannot round-trip, so strip
7791
+ // the (JSON-encoded) key segment off the entry source path and convert the
7792
+ // remaining parent path — whose ancestors are plain object keys / array
7793
+ // indices — instead.
7794
+ function parentPatchPathOfMediaEntry(sourcePath, entryKey) {
7795
+ const jsonKey = JSON.stringify(entryKey);
7796
+ let parent = sourcePath;
7797
+ if (parent.endsWith(jsonKey)) {
7798
+ parent = parent.slice(0, parent.length - jsonKey.length);
7799
+ }
7800
+ if (parent.endsWith(".")) {
7801
+ parent = parent.slice(0, -1);
7802
+ } else if (parent.endsWith(core.Internal.ModuleFilePathSep)) {
7803
+ parent = parent.slice(0, parent.length - core.Internal.ModuleFilePathSep.length);
7804
+ }
7805
+ return patch.sourceToPatchPath(parent);
7806
+ }
7807
+
7763
7808
  // TODO: find a better name? transformFixesToPatch?
7764
7809
  async function createFixPatch(config, apply, sourcePath, validationError, remoteFiles, moduleSource, moduleSchema) {
7765
7810
  const remainingErrors = [];
@@ -7937,6 +7982,61 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
7937
7982
  path: patch.sourceToPatchPath(sourcePath)
7938
7983
  });
7939
7984
  }
7985
+ } else if (fix === "images:upload-remote" || fix === "files:upload-remote") {
7986
+ // Gallery entry: the record is keyed by the file path, so uploading to
7987
+ // remote means renaming the key from the local path to the remote URL
7988
+ // (remove the old key, add the new one with the same metadata).
7989
+ const remoteFile = remoteFiles[sourcePath];
7990
+ if (!remoteFile) {
7991
+ remainingErrors.push({
7992
+ ...validationError,
7993
+ message: "Cannot fix local to remote gallery entry: remote file was not uploaded",
7994
+ fixes: undefined
7995
+ });
7996
+ continue;
7997
+ }
7998
+ const metadata = remoteFile.metadata;
7999
+ if (!metadata) {
8000
+ remainingErrors.push({
8001
+ ...validationError,
8002
+ message: "Failed to get metadata for remote gallery file",
8003
+ fixes: undefined
8004
+ });
8005
+ continue;
8006
+ }
8007
+ // The entry is keyed by its (local) file path, which can contain dots
8008
+ // (e.g. image.png). sourceToPatchPath can't round-trip such keys, so
8009
+ // build the patch path from the parent record path plus the key (which
8010
+ // validateMediaKey carries as the error value), like the UI does.
8011
+ const entryKey = validationError.value;
8012
+ if (typeof entryKey !== "string") {
8013
+ remainingErrors.push({
8014
+ ...validationError,
8015
+ message: "Cannot rewrite remote gallery entry: missing entry key",
8016
+ fixes: undefined
8017
+ });
8018
+ continue;
8019
+ }
8020
+ const parentPath = parentPatchPathOfMediaEntry(sourcePath, entryKey);
8021
+ const removePath = parentPath.concat(entryKey);
8022
+ const addPath = parentPath.concat(remoteFile.ref);
8023
+ if (!patch.isNotRoot(removePath) || !patch.isNotRoot(addPath)) {
8024
+ remainingErrors.push({
8025
+ ...validationError,
8026
+ message: "Cannot rewrite remote gallery entry at root path",
8027
+ fixes: undefined
8028
+ });
8029
+ continue;
8030
+ }
8031
+ patch$1.push({
8032
+ op: "remove",
8033
+ path: removePath
8034
+ });
8035
+ patch$1.push({
8036
+ op: "add",
8037
+ path: addPath,
8038
+ value: metadata
8039
+ });
7940
8040
  } else if (fix === "image:download-remote" || fix === "file:download-remote") {
7941
8041
  const v = getRemoteValueFromValidationError(validationError);
7942
8042
  if (!v.success) {
@@ -8013,7 +8113,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
8013
8113
  }
8014
8114
  const gallerySource = moduleSource;
8015
8115
  for (const [entryKey, storedEntry] of Object.entries(gallerySource)) {
8016
- const filename = path__namespace["default"].join(config.projectRoot, entryKey);
8116
+ const filename = path__namespace["default"].join(config.projectRoot, galleryKeyToLocalPath(entryKey));
8017
8117
  let buffer;
8018
8118
  try {
8019
8119
  buffer = fs__default["default"].readFileSync(filename);
@@ -8241,6 +8341,294 @@ async function getFileMetadata(projectRoot, validationError) {
8241
8341
  return extractFileMetadata(fileRef);
8242
8342
  }
8243
8343
 
8344
+ function validateMetadata(actualMetadata, expectedMetadata) {
8345
+ const missingMetadata = [];
8346
+ const erroneousMetadata = {};
8347
+ if (typeof actualMetadata !== "object" || actualMetadata === null) {
8348
+ return {
8349
+ globalErrors: ["Metadata is wrong type: must be an object."]
8350
+ };
8351
+ }
8352
+ if (Array.isArray(actualMetadata)) {
8353
+ return {
8354
+ globalErrors: ["Metadata is wrong type: cannot be an array."]
8355
+ };
8356
+ }
8357
+ const recordMetadata = actualMetadata;
8358
+ const globalErrors = [];
8359
+ for (const anyKey in expectedMetadata) {
8360
+ if (typeof anyKey !== "string") {
8361
+ globalErrors.push(`Expected metadata has key '${anyKey}' that is not typeof 'string', but: '${typeof anyKey}'. This is most likely a Val bug.`);
8362
+ } else {
8363
+ if (anyKey in actualMetadata) {
8364
+ const key = anyKey;
8365
+ if (expectedMetadata[key] !== recordMetadata[key]) {
8366
+ erroneousMetadata[key] = `Expected metadata '${key}' to be ${JSON.stringify(expectedMetadata[key])}, but got ${JSON.stringify(recordMetadata[key])}.`;
8367
+ }
8368
+ } else {
8369
+ missingMetadata.push(anyKey);
8370
+ }
8371
+ }
8372
+ }
8373
+ if (globalErrors.length === 0 && missingMetadata.length === 0 && Object.keys(erroneousMetadata).length === 0) {
8374
+ return false;
8375
+ }
8376
+ return {
8377
+ missingMetadata,
8378
+ erroneousMetadata
8379
+ };
8380
+ }
8381
+
8382
+ /**
8383
+ * NOTE: this is intentionally NOT `SharedValConfig` from `@valbuild/shared`.
8384
+ * That schema requires `files.directory` to be exactly `/public/val`, whereas
8385
+ * this one accepts any path beneath `/public`. Unifying them would change which
8386
+ * configs are accepted, so they are kept separate on purpose.
8387
+ */
8388
+ const ValConfigSchema = z__default["default"].object({
8389
+ project: z__default["default"].string().optional(),
8390
+ root: z__default["default"].string().optional(),
8391
+ files: z__default["default"].object({
8392
+ directory: z__default["default"].string().refine(val => (val === "/public" || val.startsWith("/public/") && !val.endsWith("/")) &&
8393
+ // Reject path traversal so the directory cannot escape /public
8394
+ !val.split("/").some(segment => segment === "." || segment === ".."), {
8395
+ message: "files.directory must start with '/public', must not end with '/' and must not contain '.' or '..' segments"
8396
+ })
8397
+ }).optional(),
8398
+ gitCommit: z__default["default"].string().optional(),
8399
+ gitBranch: z__default["default"].string().optional(),
8400
+ defaultTheme: z__default["default"].union([z__default["default"].literal("light"), z__default["default"].literal("dark")]).optional(),
8401
+ ai: z__default["default"].object({
8402
+ commitMessages: z__default["default"].object({
8403
+ disabled: z__default["default"].boolean().optional()
8404
+ }).optional()
8405
+ }).optional()
8406
+ });
8407
+
8408
+ /**
8409
+ * `ENOENT` (nothing at this path) and `ENOTDIR` (a path segment is a file) both
8410
+ * mean the config file simply is not there.
8411
+ */
8412
+ function isFileNotFound(err) {
8413
+ const code = err && typeof err === "object" && "code" in err ? err.code : undefined;
8414
+ return code === "ENOENT" || code === "ENOTDIR";
8415
+ }
8416
+
8417
+ /**
8418
+ * Read and evaluate a `val.config.{ts,js}` file from disk.
8419
+ *
8420
+ * Returns `null` if the file does not exist, and throws if it exists but does
8421
+ * not export a valid `config` object.
8422
+ *
8423
+ * Used by the Val CLI and by `@valbuild/language-server`, so that an editor
8424
+ * resolves project config exactly the way the CLI does.
8425
+ */
8426
+ async function evalValConfigFile(projectRoot, configFileName) {
8427
+ const valConfigPath = path__namespace["default"].join(projectRoot, configFileName);
8428
+ let code;
8429
+ try {
8430
+ code = await fs__default$1["default"].readFile(valConfigPath, "utf-8");
8431
+ } catch (err) {
8432
+ // A missing file means "this project does not use this config file name",
8433
+ // which callers handle by trying the next candidate. Anything else (no read
8434
+ // permission, an IO error, a directory where a file was expected) is a real
8435
+ // problem and must not be reported as "no config".
8436
+ if (isFileNotFound(err)) {
8437
+ return null;
8438
+ }
8439
+ throw Error(`Could not read Val config file at path: '${valConfigPath}': ${err instanceof Error ? err.message : String(err)}`);
8440
+ }
8441
+ if (!code.trim()) {
8442
+ return null;
8443
+ }
8444
+ const transpiled = ts__default["default"].transpileModule(code, {
8445
+ compilerOptions: {
8446
+ target: ts__default["default"].ScriptTarget.ES2020,
8447
+ module: ts__default["default"].ModuleKind.CommonJS,
8448
+ esModuleInterop: true
8449
+ },
8450
+ fileName: valConfigPath
8451
+ });
8452
+ const projectRootRequire = node_module.createRequire(valConfigPath);
8453
+ const exportsObj = {};
8454
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8455
+ const sandbox = {
8456
+ exports: exportsObj,
8457
+ module: {
8458
+ exports: exportsObj
8459
+ },
8460
+ require: projectRootRequire,
8461
+ // NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
8462
+ __filename: valConfigPath,
8463
+ __dirname: projectRoot,
8464
+ console,
8465
+ process
8466
+ };
8467
+ sandbox.global = sandbox;
8468
+ const context = vm__default["default"].createContext(sandbox);
8469
+ const script = new vm__default["default"].Script(transpiled.outputText, {
8470
+ filename: valConfigPath
8471
+ });
8472
+ script.runInContext(context);
8473
+ const valConfig = sandbox.module.exports.config;
8474
+ if (!valConfig) {
8475
+ throw Error(`Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`);
8476
+ }
8477
+ const result = ValConfigSchema.safeParse(valConfig);
8478
+ if (!result.success) {
8479
+ throw Error(`Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`);
8480
+ }
8481
+ return result.data;
8482
+ }
8483
+
8484
+ /**
8485
+ * Resolve the project's Val config, trying the TypeScript file first and
8486
+ * falling back to JavaScript. Mirrors what the CLI has always done at each of
8487
+ * its call sites.
8488
+ */
8489
+ async function findAndEvalValConfigFile(projectRoot) {
8490
+ return (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
8491
+ }
8492
+
8493
+ /**
8494
+ * The Val login device flow, as reusable primitives.
8495
+ *
8496
+ * The CLI wraps these with terminal output, and `@valbuild/language-server`
8497
+ * wraps them with LSP `window/showDocument` and progress reporting. Neither the
8498
+ * polling nor the token handling is duplicated between them.
8499
+ *
8500
+ * Deliberately no `console` output and no `process.exit`: every failure is a
8501
+ * thrown {@link ValLoginError} so an embedder can decide how to surface it.
8502
+ */
8503
+
8504
+ const DEFAULT_LOGIN_HOST = "https://admin.val.build";
8505
+ function defaultHost() {
8506
+ return process.env.VAL_BUILD_URL || DEFAULT_LOGIN_HOST;
8507
+ }
8508
+ class ValLoginError extends Error {
8509
+ constructor(code, message, details) {
8510
+ super(message);
8511
+ this.code = code;
8512
+ this.details = details;
8513
+ this.name = "ValLoginError";
8514
+ }
8515
+ }
8516
+
8517
+ /** A login attempt that is waiting for the user to confirm in a browser. */
8518
+
8519
+ /**
8520
+ * Begin a login attempt. The caller is responsible for getting
8521
+ * {@link ValLoginSession.url} in front of the user.
8522
+ */
8523
+ async function startValLogin(options = {}) {
8524
+ var _response$headers$get;
8525
+ const host = options.host ?? defaultHost();
8526
+ const response = await fetch(`${host}/api/login`, {
8527
+ method: "POST",
8528
+ headers: {
8529
+ "Content-Type": "application/json"
8530
+ }
8531
+ });
8532
+ if (response.status >= 500) {
8533
+ const text = await response.text().catch(() => "");
8534
+ throw new ValLoginError("server-error", "An error occurred on the server.", text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
8535
+ }
8536
+ if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
8537
+ const text = await response.text().catch(() => "");
8538
+ 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}`);
8539
+ }
8540
+ const json = await response.json();
8541
+ const nonce = json === null || json === void 0 ? void 0 : json.nonce;
8542
+ const url = json === null || json === void 0 ? void 0 : json.url;
8543
+ if (typeof nonce !== "string" || typeof url !== "string") {
8544
+ throw new ValLoginError("unexpected-response", "Unexpected response from the server.", JSON.stringify(json));
8545
+ }
8546
+ return {
8547
+ nonce,
8548
+ url
8549
+ };
8550
+ }
8551
+ const DEFAULT_LOGIN_MAX_DURATION = 5 * 60 * 1000; // 5 minutes
8552
+ const DEFAULT_LOGIN_POLL_INTERVAL = 1000;
8553
+
8554
+ /**
8555
+ * Poll until the user confirms the login in their browser.
8556
+ *
8557
+ * Accepts an `AbortSignal` so an editor can cancel the flow when the user
8558
+ * dismisses the prompt, instead of leaving a poll loop running for 5 minutes.
8559
+ */
8560
+ async function awaitValLoginConfirmation(nonce, options = {}) {
8561
+ const host = options.host ?? defaultHost();
8562
+ const maxDuration = options.maxDurationMs ?? DEFAULT_LOGIN_MAX_DURATION;
8563
+ const pollInterval = options.pollIntervalMs ?? DEFAULT_LOGIN_POLL_INTERVAL;
8564
+ const now = options.now ?? (() => Date.now());
8565
+ const start = now();
8566
+ while (now() - start < maxDuration) {
8567
+ var _options$signal, _options$signal2;
8568
+ if ((_options$signal = options.signal) !== null && _options$signal !== void 0 && _options$signal.aborted) {
8569
+ throw new ValLoginError("aborted", "Login was cancelled.");
8570
+ }
8571
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
8572
+ if ((_options$signal2 = options.signal) !== null && _options$signal2 !== void 0 && _options$signal2.aborted) {
8573
+ throw new ValLoginError("aborted", "Login was cancelled.");
8574
+ }
8575
+ const response = await fetch(`${host}/api/login?token=${nonce}&consume=true`, {
8576
+ method: "POST"
8577
+ });
8578
+ if (response.status >= 500) {
8579
+ throw new ValLoginError("server-error", "An error occurred on the server.", `Status: ${response.status}`);
8580
+ }
8581
+ if (response.status === 200) {
8582
+ var _json$profile;
8583
+ const json = await response.json().catch(() => null);
8584
+ if (!json) {
8585
+ // A 200 with an empty body is not a confirmation; keep polling rather
8586
+ // than failing the login, which is what the CLI has always done.
8587
+ continue;
8588
+ }
8589
+ if (typeof ((_json$profile = json.profile) === null || _json$profile === void 0 ? void 0 : _json$profile.email) === "string" && typeof json.pat === "string") {
8590
+ return json;
8591
+ }
8592
+ throw new ValLoginError("unexpected-response", "Unexpected response from the server.", JSON.stringify(json));
8593
+ }
8594
+ }
8595
+ throw new ValLoginError("timeout", "Login confirmation timed out.");
8596
+ }
8597
+
8598
+ /**
8599
+ * Write a completed login to the project's personal access token file.
8600
+ *
8601
+ * @returns the path the token was written to.
8602
+ */
8603
+ function persistPersonalAccessToken(projectRoot, result) {
8604
+ const filePath = getPersonalAccessTokenPath(projectRoot);
8605
+ // NOTE: no restrictive mode on the directory. This is the project's shared
8606
+ // `.val`, which also holds the pending patches the dev server reads and
8607
+ // writes; making it owner-only would lock out anyone who did not run the
8608
+ // login. Only the token file itself is tightened.
8609
+ fs__default["default"].mkdirSync(path__namespace["default"].dirname(filePath), {
8610
+ recursive: true
8611
+ });
8612
+ // The token is a credential: keep it owner-only rather than at the mercy of
8613
+ // the process umask. `mode` only applies when the file is created, so an
8614
+ // already existing file is chmod-ed explicitly.
8615
+ fs__default["default"].writeFileSync(filePath, JSON.stringify(result, null, 2), {
8616
+ mode: 0o600
8617
+ });
8618
+ try {
8619
+ fs__default["default"].chmodSync(filePath, 0o600);
8620
+ } catch {
8621
+ // Not all platforms / filesystems support POSIX modes (Windows, some
8622
+ // network mounts). Failing to tighten them must not fail the login.
8623
+ }
8624
+ return filePath;
8625
+ }
8626
+
8627
+ /**
8628
+ * NOTE: `@valbuild/language-server` has a near-identical `modulePathMap.ts`.
8629
+ * Fix traversal bugs in both, or fold them together.
8630
+ */
8631
+
8244
8632
  function getModulePathRange(modulePath, modulePathMap,
8245
8633
  // Which part of an object/record member to point at. For an object property
8246
8634
  // the resolved node's own range is the *key* (property name); the *value*
@@ -8250,31 +8638,39 @@ function getModulePathRange(modulePath, modulePathMap,
8250
8638
  // existing callers.
8251
8639
  target = "key") {
8252
8640
  var _range$children;
8253
- // Handle empty or invalid module paths gracefully
8254
- if (!modulePath || typeof modulePath !== "string") {
8255
- return undefined;
8256
- }
8257
- let segments;
8258
- try {
8259
- // Quote-aware splitter that correctly handles keys containing dots
8260
- // (e.g. file refs like `"/public/val/images/logo.png"`), unlike a naive
8261
- // split on ".". Throws on malformed input (e.g. unbalanced quotes).
8262
- segments = core.Internal.splitModulePath(modulePath);
8263
- } catch {
8264
- // Return undefined if the module path is malformed. This can happen when
8265
- // there are upstream errors in schema serialization.
8641
+ // Handle invalid module paths gracefully
8642
+ if (typeof modulePath !== "string") {
8266
8643
  return undefined;
8267
8644
  }
8268
- if (segments.length === 0) {
8269
- return undefined;
8270
- }
8271
- let range = modulePathMap[segments[0]];
8272
- for (const pathSegment of segments.slice(1)) {
8273
- var _range;
8274
- if (!range) {
8275
- break;
8645
+ let range;
8646
+ if (modulePath === "") {
8647
+ // The empty module path is the module root, stored under the "" key: the
8648
+ // source argument of `c.define`. This is what module-level diagnostics
8649
+ // (which have no module path) resolve to.
8650
+ range = modulePathMap[""];
8651
+ } else {
8652
+ let segments;
8653
+ try {
8654
+ // Quote-aware splitter that correctly handles keys containing dots
8655
+ // (e.g. file refs like `"/public/val/images/logo.png"`), unlike a naive
8656
+ // split on ".". Throws on malformed input (e.g. unbalanced quotes).
8657
+ segments = core.Internal.splitModulePath(modulePath);
8658
+ } catch {
8659
+ // Return undefined if the module path is malformed. This can happen when
8660
+ // there are upstream errors in schema serialization.
8661
+ return undefined;
8662
+ }
8663
+ if (segments.length === 0) {
8664
+ return undefined;
8665
+ }
8666
+ range = modulePathMap[segments[0]];
8667
+ for (const pathSegment of segments.slice(1)) {
8668
+ var _range;
8669
+ if (!range) {
8670
+ break;
8671
+ }
8672
+ range = (_range = range) === null || _range === void 0 || (_range = _range.children) === null || _range === void 0 ? void 0 : _range[pathSegment];
8276
8673
  }
8277
- range = (_range = range) === null || _range === void 0 || (_range = _range.children) === null || _range === void 0 ? void 0 : _range[pathSegment];
8278
8674
  }
8279
8675
  if (!range) {
8280
8676
  return undefined;
@@ -8306,7 +8702,20 @@ function createModulePathMap(sourceFile) {
8306
8702
  if (ts__default["default"].isExportAssignment(child)) {
8307
8703
  const contentNode = child.expression && ts__default["default"].isCallExpression(child.expression) && child.expression.arguments[2];
8308
8704
  if (contentNode) {
8309
- return traverse(contentNode, sourceFile);
8705
+ const map = traverse(contentNode, sourceFile) ?? {};
8706
+ // Always expose the module root under the "" key so diagnostics without
8707
+ // a module path still resolve to a location. `traverse` already emits ""
8708
+ // for primitive roots (with a tighter range), so keep that if present.
8709
+ // Object/record keys are never "" (see traverseObjectLiteral), so this
8710
+ // cannot collide with a real key.
8711
+ return {
8712
+ ...map,
8713
+ "": map[""] ?? {
8714
+ children: {},
8715
+ start: sourceFile.getLineAndCharacterOfPosition(contentNode.getStart(sourceFile)),
8716
+ end: sourceFile.getLineAndCharacterOfPosition(contentNode.getEnd())
8717
+ }
8718
+ };
8310
8719
  }
8311
8720
  }
8312
8721
  }
@@ -8499,12 +8908,19 @@ function readCapturedReport(snapshotDir) {
8499
8908
  return JSON.parse(fs__default["default"].readFileSync(reportPath, "utf-8"));
8500
8909
  }
8501
8910
 
8911
+ exports.DEFAULT_LOGIN_HOST = DEFAULT_LOGIN_HOST;
8912
+ exports.DEFAULT_LOGIN_MAX_DURATION = DEFAULT_LOGIN_MAX_DURATION;
8913
+ exports.DEFAULT_LOGIN_POLL_INTERVAL = DEFAULT_LOGIN_POLL_INTERVAL;
8502
8914
  exports.Service = Service;
8503
8915
  exports.ValFSHost = ValFSHost;
8916
+ exports.ValLoginError = ValLoginError;
8504
8917
  exports.ValModuleLoader = ValModuleLoader;
8505
8918
  exports.ValOpsFS = ValOpsFS;
8506
8919
  exports.ValOpsHttp = ValOpsHttp;
8507
8920
  exports.ValSourceFileHandler = ValSourceFileHandler;
8921
+ exports.analyzeValModule = analyzeValModule;
8922
+ exports.awaitValLoginConfirmation = awaitValLoginConfirmation;
8923
+ exports.checkRemoteRef = checkRemoteRef;
8508
8924
  exports.compareWithCapturedReport = compareWithCapturedReport;
8509
8925
  exports.createFixPatch = createFixPatch;
8510
8926
  exports.createModulePathMap = createModulePathMap;
@@ -8512,18 +8928,31 @@ exports.createService = createService;
8512
8928
  exports.createValApiRouter = createValApiRouter;
8513
8929
  exports.createValServer = createValServer;
8514
8930
  exports.decodeJwt = decodeJwt;
8931
+ exports.downloadFileFromRemote = downloadFileFromRemote;
8515
8932
  exports.encodeJwt = encodeJwt;
8933
+ exports.evalValConfigFile = evalValConfigFile;
8934
+ exports.extractFileMetadata = extractFileMetadata;
8935
+ exports.extractImageMetadata = extractImageMetadata;
8936
+ exports.findAndEvalValConfigFile = findAndEvalValConfigFile;
8516
8937
  exports.formatPatchSourceError = formatPatchSourceError;
8517
8938
  exports.formatSyntaxErrorTree = formatSyntaxErrorTree;
8939
+ exports.getCachedRemoteFileDir = getCachedRemoteFileDir;
8940
+ exports.getCachedRemoteFilePath = getCachedRemoteFilePath;
8518
8941
  exports.getCompilerOptions = getCompilerOptions;
8519
8942
  exports.getExpire = getExpire;
8943
+ exports.getFileExt = getFileExt;
8520
8944
  exports.getModulePathRange = getModulePathRange;
8521
8945
  exports.getPersonalAccessTokenPath = getPersonalAccessTokenPath;
8522
8946
  exports.getSettings = getSettings;
8947
+ exports.getValidationErrorFileRef = getValidationErrorFileRef;
8948
+ exports.hasRemoteFileSchema = hasRemoteFileSchema;
8523
8949
  exports.loadValModules = loadValModules;
8524
8950
  exports.parsePersonalAccessTokenFile = parsePersonalAccessTokenFile;
8525
8951
  exports.patchSourceFile = patchSourceFile;
8952
+ exports.persistPersonalAccessToken = persistPersonalAccessToken;
8526
8953
  exports.readCapturedReport = readCapturedReport;
8527
8954
  exports.replaySnapshot = replaySnapshot;
8528
8955
  exports.safeReadGit = safeReadGit;
8956
+ exports.startValLogin = startValLogin;
8529
8957
  exports.uploadRemoteFile = uploadRemoteFile;
8958
+ exports.validateMetadata = validateMetadata;