@valbuild/cli 0.72.3 → 0.73.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.
@@ -9,6 +9,7 @@ var fastGlob = require('fast-glob');
9
9
  var picocolors = require('picocolors');
10
10
  var eslint = require('eslint');
11
11
  var fs = require('fs/promises');
12
+ var fs$1 = require('fs');
12
13
 
13
14
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
14
15
 
@@ -35,6 +36,7 @@ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
35
36
  var path__default = /*#__PURE__*/_interopDefault(path);
36
37
  var picocolors__default = /*#__PURE__*/_interopDefault(picocolors);
37
38
  var fs__default = /*#__PURE__*/_interopDefault(fs);
39
+ var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
38
40
 
39
41
  function error(message) {
40
42
  console.error(chalk__default["default"].red("❌Error: ") + message);
@@ -45,6 +47,7 @@ async function validate({
45
47
  fix,
46
48
  noEslint
47
49
  }) {
50
+ const valRemoteHost = process.env.VAL_REMOTE_HOST || core.DEFAULT_VAL_REMOTE_HOST;
48
51
  const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
49
52
  const eslint$1 = new eslint.ESLint({
50
53
  cwd: projectRoot,
@@ -118,6 +121,7 @@ async function validate({
118
121
  console.log(errors === 0 ? picocolors__default["default"].green("✔") : picocolors__default["default"].red("✘"), "ESlint complete", lintFiles.length, "files");
119
122
  }
120
123
  console.log("Validating...", valFiles.length, "files");
124
+ let publicProjectId;
121
125
  let didFix = false; // TODO: ugly
122
126
  async function validateFile(file) {
123
127
  var _eslintResultsByFile;
@@ -130,6 +134,9 @@ async function validate({
130
134
  });
131
135
  const fileContent = await fs__default["default"].readFile(path__default["default"].join(projectRoot, file), "utf-8");
132
136
  const eslintResult = (_eslintResultsByFile = eslintResultsByFile) === null || _eslintResultsByFile === void 0 ? void 0 : _eslintResultsByFile[file];
137
+ const remoteFiles = {};
138
+ let remoteFileBuckets = null;
139
+ let remoteFilesCounter = 0;
133
140
  eslintResult === null || eslintResult === void 0 || eslintResult.messages.forEach(m => {
134
141
  // display surrounding code
135
142
  logEslintMessage(fileContent, moduleFilePath, m);
@@ -140,6 +147,7 @@ async function validate({
140
147
  } else {
141
148
  var _eslintResultsByFile2;
142
149
  let errors = ((_eslintResultsByFile2 = eslintResultsByFile) === null || _eslintResultsByFile2 === void 0 || (_eslintResultsByFile2 = _eslintResultsByFile2[file]) === null || _eslintResultsByFile2 === void 0 ? void 0 : _eslintResultsByFile2.messages.reduce((prev, m) => m.severity >= 2 ? prev + 1 : prev, 0)) || 0;
150
+ let fixedErrors = 0;
143
151
  if (valModule.errors) {
144
152
  if (valModule.errors.validation) {
145
153
  for (const [sourcePath, validationErrors] of Object.entries(valModule.errors.validation)) {
@@ -150,20 +158,24 @@ async function validate({
150
158
  ) || v.fixes.includes("image:check-metadata") || v.fixes.includes("image:add-metadata") || v.fixes.includes("file:check-metadata") || v.fixes.includes("file:add-metadata")) {
151
159
  const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
152
160
  if (valModule.source && valModule.schema) {
153
- var _fileSource$source;
154
161
  const fileSource = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
155
- const filePath = path__default["default"].join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
- (_fileSource$source = fileSource.source) === null || _fileSource$source === void 0 ? void 0 : _fileSource$source[core.FILE_REF_PROP]);
162
+ let filePath = null;
157
163
  try {
164
+ var _fileSource$source;
165
+ filePath = path__default["default"].join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
+ (_fileSource$source = fileSource.source) === null || _fileSource$source === void 0 ? void 0 : _fileSource$source[core.FILE_REF_PROP]);
158
167
  await fs__default["default"].access(filePath);
159
168
  } catch {
160
- console.log(picocolors__default["default"].red("✘"), `File ${filePath} does not exist`);
169
+ if (filePath) {
170
+ console.log(picocolors__default["default"].red("✘"), `File ${filePath} does not exist`);
171
+ } else {
172
+ console.log(picocolors__default["default"].red("✘"), `Expected file to be defined at: ${sourcePath} but no file was found`);
173
+ }
161
174
  errors += 1;
162
175
  continue;
163
176
  }
164
177
  }
165
178
  } else if (v.fixes.includes("keyof:check-keys")) {
166
- const prevErrors = errors;
167
179
  if (v.value && typeof v.value === "object" && "key" in v.value && "sourcePath" in v.value) {
168
180
  const {
169
181
  key,
@@ -186,32 +198,193 @@ async function validate({
186
198
  console.log(picocolors__default["default"].red("✘"), "Unexpected error in", `${sourcePath}:`, v.message, " (Expected value to be an object with 'key' and 'sourcePath' properties - this is likely a bug in Val)");
187
199
  errors += 1;
188
200
  }
189
- if (prevErrors < errors) {
190
- console.log(picocolors__default["default"].red("✘"), "Found error in", `${sourcePath}`);
201
+ } else if (v.fixes.includes("image:upload-remote") || v.fixes.includes("file:upload-remote")) {
202
+ if (!fix) {
203
+ console.log(picocolors__default["default"].red("✘"), `Remote file ${sourcePath} needs to be uploaded (use --fix to upload)`);
204
+ errors += 1;
205
+ continue;
191
206
  }
192
- } else {
193
- console.log(picocolors__default["default"].red("✘"), "Found error in", `${sourcePath}:`, v.message);
207
+ const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
208
+ if (valModule.source && valModule.schema) {
209
+ const resolvedRemoteFileAtSourcePath = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
210
+ let filePath = null;
211
+ console.log(sourcePath, resolvedRemoteFileAtSourcePath.source);
212
+ try {
213
+ var _resolvedRemoteFileAt;
214
+ filePath = path__default["default"].join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
+ (_resolvedRemoteFileAt = resolvedRemoteFileAtSourcePath.source) === null || _resolvedRemoteFileAt === void 0 ? void 0 : _resolvedRemoteFileAt[core.FILE_REF_PROP]);
216
+ await fs__default["default"].access(filePath);
217
+ } catch {
218
+ if (filePath) {
219
+ console.log(picocolors__default["default"].red("✘"), `File ${filePath} does not exist`);
220
+ } else {
221
+ console.log(picocolors__default["default"].red("✘"), `Expected file to be defined at: ${sourcePath} but no file was found`);
222
+ }
223
+ errors += 1;
224
+ continue;
225
+ }
226
+ const patFile = server.getPersonalAccessTokenPath(projectRoot);
227
+ try {
228
+ await fs__default["default"].access(patFile);
229
+ } catch {
230
+ // TODO: display this error only once:
231
+ console.log(picocolors__default["default"].red("✘"), `File: ${path__default["default"].join(projectRoot, file)} has remote images that are not uploaded and you are not logged in.\n\nFix this error by logging in:\n\t"npx val login"\n`);
232
+ errors += 1;
233
+ continue;
234
+ }
235
+ const parsedPatFile = server.parsePersonalAccessTokenFile(await fs__default["default"].readFile(patFile, "utf-8"));
236
+ if (!parsedPatFile.success) {
237
+ console.log(picocolors__default["default"].red("✘"), `Error parsing personal access token file: ${parsedPatFile.error}. You need to login again.`);
238
+ errors += 1;
239
+ continue;
240
+ }
241
+ const {
242
+ pat
243
+ } = parsedPatFile.data;
244
+ if (remoteFiles[sourcePath]) {
245
+ console.log(picocolors__default["default"].yellow("⚠"), `Remote file ${filePath} already uploaded`);
246
+ continue;
247
+ }
248
+ // TODO: parallelize this:
249
+ console.log(picocolors__default["default"].yellow("⚠"), `Uploading remote file ${filePath}...`);
250
+ if (!resolvedRemoteFileAtSourcePath.schema) {
251
+ console.log(picocolors__default["default"].red("✘"), `Cannot upload remote file: schema not found for ${sourcePath}`);
252
+ errors += 1;
253
+ continue;
254
+ }
255
+ const actualRemoteFileSource = resolvedRemoteFileAtSourcePath.source;
256
+ const fileSourceMetadata = core.Internal.isFile(actualRemoteFileSource) ? actualRemoteFileSource.metadata : undefined;
257
+ const resolveRemoteFileSchema = resolvedRemoteFileAtSourcePath.schema;
258
+ if (!resolveRemoteFileSchema) {
259
+ console.log(picocolors__default["default"].red("✘"), `Could not resolve schema for remote file: ${sourcePath}`);
260
+ errors += 1;
261
+ continue;
262
+ }
263
+ if (!publicProjectId || !remoteFileBuckets) {
264
+ let projectName = process.env.VAL_PROJECT;
265
+ if (!projectName) {
266
+ try {
267
+ var _require;
268
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
269
+ projectName = (_require = require(`${root}/val.config`)) === null || _require === void 0 || (_require = _require.config) === null || _require === void 0 ? void 0 : _require.project;
270
+ } catch {
271
+ // ignore
272
+ }
273
+ }
274
+ if (!projectName) {
275
+ try {
276
+ var _require2;
277
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
278
+ projectName = (_require2 = require(`${root}/val.config.ts`)) === null || _require2 === void 0 || (_require2 = _require2.config) === null || _require2 === void 0 ? void 0 : _require2.project;
279
+ } catch {
280
+ // ignore
281
+ }
282
+ }
283
+ if (!projectName) {
284
+ try {
285
+ var _require3;
286
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
287
+ projectName = (_require3 = require(`${root}/val.config.js`)) === null || _require3 === void 0 || (_require3 = _require3.config) === null || _require3 === void 0 ? void 0 : _require3.project;
288
+ } catch {
289
+ // ignore
290
+ }
291
+ }
292
+ if (!projectName) {
293
+ console.log(picocolors__default["default"].red("✘"), "Project name not found. Set VAL_PROJECT environment variable or add project name to val.config");
294
+ errors += 1;
295
+ continue;
296
+ }
297
+ const settingsRes = await server.getSettings(projectName, {
298
+ pat
299
+ });
300
+ if (!settingsRes.success) {
301
+ console.log(picocolors__default["default"].red("✘"), `Could not get public project id: ${settingsRes.message}.`);
302
+ errors += 1;
303
+ continue;
304
+ }
305
+ publicProjectId = settingsRes.data.publicProjectId;
306
+ remoteFileBuckets = settingsRes.data.remoteFileBuckets.map(b => b.bucket);
307
+ }
308
+ if (!publicProjectId) {
309
+ console.log(picocolors__default["default"].red("✘"), "Could not get public project id");
310
+ errors += 1;
311
+ continue;
312
+ }
313
+ if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
314
+ console.log(picocolors__default["default"].red("✘"), `The schema is the remote is neither image nor file: ${sourcePath}`);
315
+ }
316
+ remoteFilesCounter += 1;
317
+ const bucket = remoteFileBuckets[remoteFilesCounter % remoteFileBuckets.length];
318
+ if (!bucket) {
319
+ console.log(picocolors__default["default"].red("✘"), `Internal error: could not allocate a bucket for the remote file located at ${sourcePath}`);
320
+ errors += 1;
321
+ continue;
322
+ }
323
+ let fileBuffer;
324
+ try {
325
+ fileBuffer = await fs__default["default"].readFile(filePath);
326
+ } catch (e) {
327
+ console.log(picocolors__default["default"].red("✘"), `Error reading file: ${e}`);
328
+ errors += 1;
329
+ continue;
330
+ }
331
+ const relativeFilePath = path__default["default"].relative(projectRoot, filePath).split(path__default["default"].sep).join("/");
332
+ if (!relativeFilePath.startsWith("public/val/")) {
333
+ console.log(picocolors__default["default"].red("✘"), `File path must be within the public/val/ directory (e.g. public/val/path/to/file.txt). Got: ${relativeFilePath}`);
334
+ errors += 1;
335
+ continue;
336
+ }
337
+ const remoteFileUpload = await server.uploadRemoteFile(valRemoteHost, fileBuffer, publicProjectId, bucket, relativeFilePath, resolveRemoteFileSchema, fileSourceMetadata, {
338
+ pat
339
+ });
340
+ if (!remoteFileUpload.success) {
341
+ console.log(picocolors__default["default"].red("✘"), `Error uploading remote file: ${remoteFileUpload.error}`);
342
+ errors += 1;
343
+ continue;
344
+ }
345
+ console.log(picocolors__default["default"].yellow("⚠"), `Uploaded remote file ${filePath}`);
346
+ remoteFiles[sourcePath] = {
347
+ ref: remoteFileUpload.ref,
348
+ metadata: fileSourceMetadata
349
+ };
350
+ }
351
+ } else if (v.fixes.includes("image:download-remote") || v.fixes.includes("file:download-remote")) {
352
+ if (fix) {
353
+ console.log(picocolors__default["default"].yellow("⚠"), `Downloading remote file in ${sourcePath}...`);
354
+ } else {
355
+ console.log(picocolors__default["default"].red("✘"), `Remote file ${sourcePath} needs to be downloaded (use --fix to download)`);
356
+ errors += 1;
357
+ continue;
358
+ }
359
+ } else if (v.fixes.includes("image:check-remote") || v.fixes.includes("file:check-remote")) ; else {
360
+ console.log(picocolors__default["default"].red("✘"), "Unknown fix", v.fixes, "for", sourcePath);
194
361
  errors += 1;
362
+ continue;
195
363
  }
196
364
  const fixPatch = await server.createFixPatch({
197
- projectRoot
198
- }, !!fix, sourcePath, v);
365
+ projectRoot,
366
+ remoteHost: valRemoteHost
367
+ }, !!fix, sourcePath, v, remoteFiles, valModule.source, valModule.schema);
199
368
  if (fix && fixPatch !== null && fixPatch !== void 0 && fixPatch.patch && (fixPatch === null || fixPatch === void 0 ? void 0 : fixPatch.patch.length) > 0) {
200
369
  await service.patch(moduleFilePath, fixPatch.patch);
201
370
  didFix = true;
371
+ fixedErrors += 1;
202
372
  console.log(picocolors__default["default"].yellow("⚠"), "Applied fix for", sourcePath);
203
373
  }
204
374
  fixPatch === null || fixPatch === void 0 || (_fixPatch$remainingEr = fixPatch.remainingErrors) === null || _fixPatch$remainingEr === void 0 || _fixPatch$remainingEr.forEach(e => {
205
375
  errors += 1;
206
- console.log(v.fixes ? picocolors__default["default"].yellow("⚠") : picocolors__default["default"].red("✘"), `Found ${v.fixes ? "fixable " : ""}error in`, `${sourcePath}:`, e.message);
376
+ console.log(e.fixes && e.fixes.length ? picocolors__default["default"].yellow("⚠") : picocolors__default["default"].red("✘"), `Got ${e.fixes && e.fixes.length ? "fixable " : ""}error in`, `${sourcePath}:`, e.message);
207
377
  });
208
378
  } else {
209
379
  errors += 1;
210
- console.log(picocolors__default["default"].red("✘"), "Found error in", `${sourcePath}:`, v.message);
380
+ console.log(picocolors__default["default"].red("✘"), "Got error in", `${sourcePath}:`, v.message);
211
381
  }
212
382
  }
213
383
  }
214
384
  }
385
+ if (fixedErrors === errors && (!valModule.errors.fatal || valModule.errors.fatal.length == 0)) {
386
+ console.log(picocolors__default["default"].green("✔"), moduleFilePath, "is valid (" + (Date.now() - start) + "ms)");
387
+ }
215
388
  for (const fatalError of valModule.errors.fatal || []) {
216
389
  errors += 1;
217
390
  console.log(picocolors__default["default"].red("✘"), moduleFilePath, "is invalid:", fatalError.message);
@@ -219,6 +392,9 @@ async function validate({
219
392
  } else {
220
393
  console.log(picocolors__default["default"].green("✔"), moduleFilePath, "is valid (" + (Date.now() - start) + "ms)");
221
394
  }
395
+ if (errors > 0) {
396
+ console.log(picocolors__default["default"].red("✘"), `${`/${file}`} contains ${errors} error${errors > 1 ? "s" : ""}`, " (" + (Date.now() - start) + "ms)");
397
+ }
222
398
  return errors;
223
399
  }
224
400
  }
@@ -236,7 +412,7 @@ async function validate({
236
412
  }
237
413
  }
238
414
  if (errors > 0) {
239
- console.log(picocolors__default["default"].red("✘"), "Found", errors, "validation error" + (errors > 1 ? "s" : ""));
415
+ console.log(picocolors__default["default"].red("✘"), "Got", errors, "error" + (errors > 1 ? "s" : ""));
240
416
  process.exit(1);
241
417
  } else {
242
418
  console.log(picocolors__default["default"].green("✔"), "No validation errors found");
@@ -362,8 +538,8 @@ function isFileRef(value) {
362
538
  return false;
363
539
  }
364
540
 
365
- const getVersions = async () => {
366
- const coreVersion = await (() => {
541
+ const getVersions = () => {
542
+ const coreVersion = (() => {
367
543
  try {
368
544
  var _require;
369
545
  // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
@@ -372,7 +548,7 @@ const getVersions = async () => {
372
548
  return null;
373
549
  }
374
550
  })();
375
- const nextVersion = await (() => {
551
+ const nextVersion = (() => {
376
552
  try {
377
553
  var _require2;
378
554
  // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
@@ -387,6 +563,83 @@ const getVersions = async () => {
387
563
  };
388
564
  };
389
565
 
566
+ const host = process.env.VAL_BUILD_URL || "https://app.val.build";
567
+ async function login(options) {
568
+ try {
569
+ var _response$headers$get;
570
+ console.log(picocolors__default["default"].cyan("\nStarting login process...\n"));
571
+
572
+ // Step 1: Initiate login and get token and URL
573
+ const response = await fetch(`${host}/api/login`, {
574
+ method: "POST",
575
+ headers: {
576
+ "Content-Type": "application/json"
577
+ }
578
+ });
579
+ let token;
580
+ let url;
581
+ if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
582
+ const text = await response.text();
583
+ console.error(picocolors__default["default"].red("Unexpected failure while trying to login (content type was not JSON). Server response:"), text || "<empty>");
584
+ process.exit(1);
585
+ }
586
+ const json = await response.json();
587
+ if (json) {
588
+ token = json.nonce;
589
+ url = json.url;
590
+ }
591
+ if (!token || !url) {
592
+ console.error(picocolors__default["default"].red("Unexpected response from the server."), json);
593
+ process.exit(1);
594
+ }
595
+ console.log(picocolors__default["default"].green("Open the following URL in your browser to log in:"));
596
+ console.log(picocolors__default["default"].underline(picocolors__default["default"].blue(url)));
597
+ console.log(picocolors__default["default"].dim("\nWaiting for login confirmation...\n"));
598
+
599
+ // Step 2: Poll for login confirmation
600
+ const result = await pollForConfirmation(token);
601
+
602
+ // Step 3: Save the token
603
+ const filePath = server.getPersonalAccessTokenPath(options.root || process.cwd());
604
+ saveToken(result, filePath);
605
+ } catch (error) {
606
+ console.error(picocolors__default["default"].red("An error occurred during the login process. Check your internet connection. Details:"), error instanceof Error ? error.message : JSON.stringify(error, null, 2));
607
+ process.exit(1);
608
+ }
609
+ }
610
+ const MAX_DURATION = 5 * 60 * 1000; // 5 minutes
611
+ async function pollForConfirmation(token) {
612
+ const start = Date.now();
613
+ while (Date.now() - start < MAX_DURATION) {
614
+ await new Promise(resolve => setTimeout(resolve, 1000));
615
+ const response = await fetch(`${host}/api/login?token=${token}&consume=true`);
616
+ if (response.status === 500) {
617
+ console.error(picocolors__default["default"].red("An error occurred on the server."));
618
+ process.exit(1);
619
+ }
620
+ if (response.status === 200) {
621
+ const json = await response.json();
622
+ if (json) {
623
+ if (typeof json.profile.username === "string" && typeof json.pat === "string") {
624
+ return json;
625
+ } else {
626
+ console.error(picocolors__default["default"].red("Unexpected response from the server."));
627
+ process.exit(1);
628
+ }
629
+ }
630
+ }
631
+ }
632
+ console.error(picocolors__default["default"].red("Login confirmation timed out."));
633
+ process.exit(1);
634
+ }
635
+ function saveToken(result, filePath) {
636
+ fs__default$1["default"].mkdirSync(path__default["default"].dirname(filePath), {
637
+ recursive: true
638
+ });
639
+ fs__default$1["default"].writeFileSync(filePath, JSON.stringify(result, null, 2));
640
+ console.log(picocolors__default["default"].green(`Token for ${picocolors__default["default"].cyan(result.profile.username)} saved to ${picocolors__default["default"].cyan(filePath)}`));
641
+ }
642
+
390
643
  async function main() {
391
644
  const {
392
645
  input,
@@ -401,6 +654,7 @@ async function main() {
401
654
 
402
655
  Commands:
403
656
  validate
657
+ login
404
658
  list-files
405
659
  versions
406
660
 
@@ -411,6 +665,12 @@ async function main() {
411
665
  --fix [fix] Attempt to fix validation errors
412
666
  --noEslint [noEslint] Disable eslint validation during validate
413
667
 
668
+
669
+ Command: login
670
+ Description: login to app.val.build and generate a Personal Access Token
671
+ Options:
672
+ --root [root], -r [root] Set project root directory (default process.cwd())
673
+
414
674
 
415
675
  Command: files
416
676
  Description: EXPERIMENTAL.
@@ -464,6 +724,10 @@ async function main() {
464
724
  });
465
725
  case "versions":
466
726
  return versions();
727
+ case "login":
728
+ return login({
729
+ root: flags.root
730
+ });
467
731
  case "validate":
468
732
  case "idate":
469
733
  if (flags.managedDir) {
@@ -483,7 +747,7 @@ void main().catch(err => {
483
747
  process.exitCode = 1;
484
748
  });
485
749
  async function versions() {
486
- const foundVersions = await getVersions();
750
+ const foundVersions = getVersions();
487
751
  console.log(`${chalk__default["default"].cyan("@valbuild/core")}: ${foundVersions.coreVersion}`);
488
752
  console.log(`${chalk__default["default"].cyan("@valbuild/next")}: ${foundVersions.nextVersion}`);
489
753
  }