@valbuild/cli 0.72.4 → 0.73.1

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,10 @@ 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 vm = require('node:vm');
13
+ var ts = require('typescript');
14
+ var z = require('zod');
15
+ var fs$1 = require('fs');
12
16
 
13
17
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
14
18
 
@@ -35,21 +39,95 @@ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
35
39
  var path__default = /*#__PURE__*/_interopDefault(path);
36
40
  var picocolors__default = /*#__PURE__*/_interopDefault(picocolors);
37
41
  var fs__default = /*#__PURE__*/_interopDefault(fs);
42
+ var vm__default = /*#__PURE__*/_interopDefault(vm);
43
+ var ts__default = /*#__PURE__*/_interopDefault(ts);
44
+ var z__default = /*#__PURE__*/_interopDefault(z);
45
+ var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
38
46
 
39
47
  function error(message) {
40
48
  console.error(chalk__default["default"].red("❌Error: ") + message);
41
49
  }
42
50
 
51
+ const ValConfigSchema = z__default["default"].object({
52
+ project: z__default["default"].string().optional(),
53
+ root: z__default["default"].string().optional(),
54
+ files: z__default["default"].object({
55
+ directory: z__default["default"].string().refine(val => val.startsWith("/public/val"), {
56
+ message: "files.directory must start with '/public/val'"
57
+ })
58
+ }).optional(),
59
+ gitCommit: z__default["default"].string().optional(),
60
+ gitBranch: z__default["default"].string().optional(),
61
+ defaultTheme: z__default["default"].union([z__default["default"].literal("light"), z__default["default"].literal("dark")]).optional(),
62
+ ai: z__default["default"].object({
63
+ commitMessages: z__default["default"].object({
64
+ disabled: z__default["default"].boolean().optional()
65
+ }).optional()
66
+ }).optional()
67
+ });
68
+ async function evalValConfigFile(projectRoot, configFileName) {
69
+ const valConfigPath = path__default["default"].join(projectRoot, configFileName);
70
+ let code = null;
71
+ try {
72
+ code = await fs__default["default"].readFile(valConfigPath, "utf-8");
73
+ } catch (err) {
74
+ //
75
+ }
76
+ if (!code) {
77
+ return null;
78
+ }
79
+ const transpiled = ts__default["default"].transpileModule(code, {
80
+ compilerOptions: {
81
+ target: ts__default["default"].ScriptTarget.ES2020,
82
+ module: ts__default["default"].ModuleKind.CommonJS,
83
+ esModuleInterop: true
84
+ },
85
+ fileName: valConfigPath
86
+ });
87
+ const exportsObj = {};
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ const sandbox = {
90
+ exports: exportsObj,
91
+ module: {
92
+ exports: exportsObj
93
+ },
94
+ require,
95
+ // NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
96
+ __filename: valConfigPath,
97
+ __dirname: path__default["default"].dirname(valConfigPath),
98
+ console,
99
+ process
100
+ };
101
+ sandbox.global = sandbox;
102
+ const context = vm__default["default"].createContext(sandbox);
103
+ const script = new vm__default["default"].Script(transpiled.outputText, {
104
+ filename: valConfigPath
105
+ });
106
+ script.runInContext(context);
107
+ const valConfig = sandbox.module.exports.config;
108
+ if (!valConfig) {
109
+ throw Error(`Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`);
110
+ }
111
+ const result = ValConfigSchema.safeParse(valConfig);
112
+ if (!result.success) {
113
+ throw Error(`Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`);
114
+ }
115
+ return result.data;
116
+ }
117
+
43
118
  async function validate({
44
119
  root,
45
120
  fix,
46
121
  noEslint
47
122
  }) {
123
+ const valRemoteHost = process.env.VAL_REMOTE_HOST || core.DEFAULT_VAL_REMOTE_HOST;
48
124
  const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
49
125
  const eslint$1 = new eslint.ESLint({
50
126
  cwd: projectRoot,
51
127
  ignore: false
52
128
  });
129
+ const valConfigFile = (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
130
+ console.log(picocolors__default["default"].greenBright(`Validating project${valConfigFile !== null && valConfigFile !== void 0 && valConfigFile.project ? ` '${picocolors__default["default"].inverse(valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project)}'` : ""}...`));
53
131
  const service = await server.createService(projectRoot, {});
54
132
  const checkKeyIsValid = async (key, sourcePath) => {
55
133
  const [moduleFilePath, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
@@ -117,7 +195,8 @@ async function validate({
117
195
  });
118
196
  console.log(errors === 0 ? picocolors__default["default"].green("✔") : picocolors__default["default"].red("✘"), "ESlint complete", lintFiles.length, "files");
119
197
  }
120
- console.log("Validating...", valFiles.length, "files");
198
+ console.log(picocolors__default["default"].greenBright(`Found ${valFiles.length} files...`));
199
+ let publicProjectId;
121
200
  let didFix = false; // TODO: ugly
122
201
  async function validateFile(file) {
123
202
  var _eslintResultsByFile;
@@ -130,6 +209,9 @@ async function validate({
130
209
  });
131
210
  const fileContent = await fs__default["default"].readFile(path__default["default"].join(projectRoot, file), "utf-8");
132
211
  const eslintResult = (_eslintResultsByFile = eslintResultsByFile) === null || _eslintResultsByFile === void 0 ? void 0 : _eslintResultsByFile[file];
212
+ const remoteFiles = {};
213
+ let remoteFileBuckets = null;
214
+ let remoteFilesCounter = 0;
133
215
  eslintResult === null || eslintResult === void 0 || eslintResult.messages.forEach(m => {
134
216
  // display surrounding code
135
217
  logEslintMessage(fileContent, moduleFilePath, m);
@@ -140,6 +222,7 @@ async function validate({
140
222
  } else {
141
223
  var _eslintResultsByFile2;
142
224
  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;
225
+ let fixedErrors = 0;
143
226
  if (valModule.errors) {
144
227
  if (valModule.errors.validation) {
145
228
  for (const [sourcePath, validationErrors] of Object.entries(valModule.errors.validation)) {
@@ -150,20 +233,24 @@ async function validate({
150
233
  ) || 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
234
  const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
152
235
  if (valModule.source && valModule.schema) {
153
- var _fileSource$source;
154
236
  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]);
237
+ let filePath = null;
157
238
  try {
239
+ var _fileSource$source;
240
+ filePath = path__default["default"].join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
+ (_fileSource$source = fileSource.source) === null || _fileSource$source === void 0 ? void 0 : _fileSource$source[core.FILE_REF_PROP]);
158
242
  await fs__default["default"].access(filePath);
159
243
  } catch {
160
- console.log(picocolors__default["default"].red("✘"), `File ${filePath} does not exist`);
244
+ if (filePath) {
245
+ console.log(picocolors__default["default"].red("✘"), `File ${filePath} does not exist`);
246
+ } else {
247
+ console.log(picocolors__default["default"].red("✘"), `Expected file to be defined at: ${sourcePath} but no file was found`);
248
+ }
161
249
  errors += 1;
162
250
  continue;
163
251
  }
164
252
  }
165
253
  } else if (v.fixes.includes("keyof:check-keys")) {
166
- const prevErrors = errors;
167
254
  if (v.value && typeof v.value === "object" && "key" in v.value && "sourcePath" in v.value) {
168
255
  const {
169
256
  key,
@@ -186,32 +273,168 @@ async function validate({
186
273
  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
274
  errors += 1;
188
275
  }
189
- if (prevErrors < errors) {
190
- console.log(picocolors__default["default"].red("✘"), "Found error in", `${sourcePath}`);
276
+ } else if (v.fixes.includes("image:upload-remote") || v.fixes.includes("file:upload-remote")) {
277
+ if (!fix) {
278
+ console.log(picocolors__default["default"].red("✘"), `Remote file ${sourcePath} needs to be uploaded (use --fix to upload)`);
279
+ errors += 1;
280
+ continue;
281
+ }
282
+ const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
283
+ if (valModule.source && valModule.schema) {
284
+ const resolvedRemoteFileAtSourcePath = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
285
+ let filePath = null;
286
+ try {
287
+ var _resolvedRemoteFileAt;
288
+ filePath = path__default["default"].join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
+ (_resolvedRemoteFileAt = resolvedRemoteFileAtSourcePath.source) === null || _resolvedRemoteFileAt === void 0 ? void 0 : _resolvedRemoteFileAt[core.FILE_REF_PROP]);
290
+ await fs__default["default"].access(filePath);
291
+ } catch {
292
+ if (filePath) {
293
+ console.log(picocolors__default["default"].red("✘"), `File ${filePath} does not exist`);
294
+ } else {
295
+ console.log(picocolors__default["default"].red("✘"), `Expected file to be defined at: ${sourcePath} but no file was found`);
296
+ }
297
+ errors += 1;
298
+ continue;
299
+ }
300
+ const patFile = server.getPersonalAccessTokenPath(projectRoot);
301
+ try {
302
+ await fs__default["default"].access(patFile);
303
+ } catch {
304
+ // TODO: display this error only once:
305
+ 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`);
306
+ errors += 1;
307
+ continue;
308
+ }
309
+ const parsedPatFile = server.parsePersonalAccessTokenFile(await fs__default["default"].readFile(patFile, "utf-8"));
310
+ if (!parsedPatFile.success) {
311
+ console.log(picocolors__default["default"].red("✘"), `Error parsing personal access token file: ${parsedPatFile.error}. You need to login again.`);
312
+ errors += 1;
313
+ continue;
314
+ }
315
+ const {
316
+ pat
317
+ } = parsedPatFile.data;
318
+ if (remoteFiles[sourcePath]) {
319
+ console.log(picocolors__default["default"].yellow("⚠"), `Remote file ${filePath} already uploaded`);
320
+ continue;
321
+ }
322
+ // TODO: parallelize this:
323
+ console.log(picocolors__default["default"].yellow("⚠"), `Uploading remote file ${filePath}...`);
324
+ if (!resolvedRemoteFileAtSourcePath.schema) {
325
+ console.log(picocolors__default["default"].red("✘"), `Cannot upload remote file: schema not found for ${sourcePath}`);
326
+ errors += 1;
327
+ continue;
328
+ }
329
+ const actualRemoteFileSource = resolvedRemoteFileAtSourcePath.source;
330
+ const fileSourceMetadata = core.Internal.isFile(actualRemoteFileSource) ? actualRemoteFileSource.metadata : undefined;
331
+ const resolveRemoteFileSchema = resolvedRemoteFileAtSourcePath.schema;
332
+ if (!resolveRemoteFileSchema) {
333
+ console.log(picocolors__default["default"].red("✘"), `Could not resolve schema for remote file: ${sourcePath}`);
334
+ errors += 1;
335
+ continue;
336
+ }
337
+ if (!publicProjectId || !remoteFileBuckets) {
338
+ let projectName = process.env.VAL_PROJECT;
339
+ if (!projectName) {
340
+ projectName = valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project;
341
+ }
342
+ if (!projectName) {
343
+ console.log(picocolors__default["default"].red("✘"), "Project name not found. Set VAL_PROJECT environment variable or add project name to val.config");
344
+ errors += 1;
345
+ continue;
346
+ }
347
+ const settingsRes = await server.getSettings(projectName, {
348
+ pat
349
+ });
350
+ if (!settingsRes.success) {
351
+ console.log(picocolors__default["default"].red("✘"), `Could not get public project id: ${settingsRes.message}.`);
352
+ errors += 1;
353
+ continue;
354
+ }
355
+ publicProjectId = settingsRes.data.publicProjectId;
356
+ remoteFileBuckets = settingsRes.data.remoteFileBuckets.map(b => b.bucket);
357
+ }
358
+ if (!publicProjectId) {
359
+ console.log(picocolors__default["default"].red("✘"), "Could not get public project id");
360
+ errors += 1;
361
+ continue;
362
+ }
363
+ if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
364
+ console.log(picocolors__default["default"].red("✘"), `The schema is the remote is neither image nor file: ${sourcePath}`);
365
+ }
366
+ remoteFilesCounter += 1;
367
+ const bucket = remoteFileBuckets[remoteFilesCounter % remoteFileBuckets.length];
368
+ if (!bucket) {
369
+ console.log(picocolors__default["default"].red("✘"), `Internal error: could not allocate a bucket for the remote file located at ${sourcePath}`);
370
+ errors += 1;
371
+ continue;
372
+ }
373
+ let fileBuffer;
374
+ try {
375
+ fileBuffer = await fs__default["default"].readFile(filePath);
376
+ } catch (e) {
377
+ console.log(picocolors__default["default"].red("✘"), `Error reading file: ${e}`);
378
+ errors += 1;
379
+ continue;
380
+ }
381
+ const relativeFilePath = path__default["default"].relative(projectRoot, filePath).split(path__default["default"].sep).join("/");
382
+ if (!relativeFilePath.startsWith("public/val/")) {
383
+ 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}`);
384
+ errors += 1;
385
+ continue;
386
+ }
387
+ const remoteFileUpload = await server.uploadRemoteFile(valRemoteHost, fileBuffer, publicProjectId, bucket, relativeFilePath, resolveRemoteFileSchema, fileSourceMetadata, {
388
+ pat
389
+ });
390
+ if (!remoteFileUpload.success) {
391
+ console.log(picocolors__default["default"].red("✘"), `Error uploading remote file: ${remoteFileUpload.error}`);
392
+ errors += 1;
393
+ continue;
394
+ }
395
+ console.log(picocolors__default["default"].yellow("⚠"), `Uploaded remote file ${filePath}`);
396
+ remoteFiles[sourcePath] = {
397
+ ref: remoteFileUpload.ref,
398
+ metadata: fileSourceMetadata
399
+ };
400
+ }
401
+ } else if (v.fixes.includes("image:download-remote") || v.fixes.includes("file:download-remote")) {
402
+ if (fix) {
403
+ console.log(picocolors__default["default"].yellow("⚠"), `Downloading remote file in ${sourcePath}...`);
404
+ } else {
405
+ console.log(picocolors__default["default"].red("✘"), `Remote file ${sourcePath} needs to be downloaded (use --fix to download)`);
406
+ errors += 1;
407
+ continue;
191
408
  }
192
- } else {
193
- console.log(picocolors__default["default"].red("✘"), "Found error in", `${sourcePath}:`, v.message);
409
+ } else if (v.fixes.includes("image:check-remote") || v.fixes.includes("file:check-remote")) ; else {
410
+ console.log(picocolors__default["default"].red("✘"), "Unknown fix", v.fixes, "for", sourcePath);
194
411
  errors += 1;
412
+ continue;
195
413
  }
196
414
  const fixPatch = await server.createFixPatch({
197
- projectRoot
198
- }, !!fix, sourcePath, v);
415
+ projectRoot,
416
+ remoteHost: valRemoteHost
417
+ }, !!fix, sourcePath, v, remoteFiles, valModule.source, valModule.schema);
199
418
  if (fix && fixPatch !== null && fixPatch !== void 0 && fixPatch.patch && (fixPatch === null || fixPatch === void 0 ? void 0 : fixPatch.patch.length) > 0) {
200
419
  await service.patch(moduleFilePath, fixPatch.patch);
201
420
  didFix = true;
421
+ fixedErrors += 1;
202
422
  console.log(picocolors__default["default"].yellow("⚠"), "Applied fix for", sourcePath);
203
423
  }
204
424
  fixPatch === null || fixPatch === void 0 || (_fixPatch$remainingEr = fixPatch.remainingErrors) === null || _fixPatch$remainingEr === void 0 || _fixPatch$remainingEr.forEach(e => {
205
425
  errors += 1;
206
- console.log(v.fixes ? picocolors__default["default"].yellow("⚠") : picocolors__default["default"].red("✘"), `Found ${v.fixes ? "fixable " : ""}error in`, `${sourcePath}:`, e.message);
426
+ 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
427
  });
208
428
  } else {
209
429
  errors += 1;
210
- console.log(picocolors__default["default"].red("✘"), "Found error in", `${sourcePath}:`, v.message);
430
+ console.log(picocolors__default["default"].red("✘"), "Got error in", `${sourcePath}:`, v.message);
211
431
  }
212
432
  }
213
433
  }
214
434
  }
435
+ if (fixedErrors === errors && (!valModule.errors.fatal || valModule.errors.fatal.length == 0)) {
436
+ console.log(picocolors__default["default"].green("✔"), moduleFilePath, "is valid (" + (Date.now() - start) + "ms)");
437
+ }
215
438
  for (const fatalError of valModule.errors.fatal || []) {
216
439
  errors += 1;
217
440
  console.log(picocolors__default["default"].red("✘"), moduleFilePath, "is invalid:", fatalError.message);
@@ -219,6 +442,9 @@ async function validate({
219
442
  } else {
220
443
  console.log(picocolors__default["default"].green("✔"), moduleFilePath, "is valid (" + (Date.now() - start) + "ms)");
221
444
  }
445
+ if (errors > 0) {
446
+ console.log(picocolors__default["default"].red("✘"), `${`/${file}`} contains ${errors} error${errors > 1 ? "s" : ""}`, " (" + (Date.now() - start) + "ms)");
447
+ }
222
448
  return errors;
223
449
  }
224
450
  }
@@ -236,7 +462,7 @@ async function validate({
236
462
  }
237
463
  }
238
464
  if (errors > 0) {
239
- console.log(picocolors__default["default"].red("✘"), "Found", errors, "validation error" + (errors > 1 ? "s" : ""));
465
+ console.log(picocolors__default["default"].red("✘"), "Got", errors, "error" + (errors > 1 ? "s" : ""));
240
466
  process.exit(1);
241
467
  } else {
242
468
  console.log(picocolors__default["default"].green("✔"), "No validation errors found");
@@ -362,8 +588,8 @@ function isFileRef(value) {
362
588
  return false;
363
589
  }
364
590
 
365
- const getVersions = async () => {
366
- const coreVersion = await (() => {
591
+ const getVersions = () => {
592
+ const coreVersion = (() => {
367
593
  try {
368
594
  var _require;
369
595
  // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
@@ -372,7 +598,7 @@ const getVersions = async () => {
372
598
  return null;
373
599
  }
374
600
  })();
375
- const nextVersion = await (() => {
601
+ const nextVersion = (() => {
376
602
  try {
377
603
  var _require2;
378
604
  // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
@@ -387,6 +613,83 @@ const getVersions = async () => {
387
613
  };
388
614
  };
389
615
 
616
+ const host = process.env.VAL_BUILD_URL || "https://app.val.build";
617
+ async function login(options) {
618
+ try {
619
+ var _response$headers$get;
620
+ console.log(picocolors__default["default"].cyan("\nStarting login process...\n"));
621
+
622
+ // Step 1: Initiate login and get token and URL
623
+ const response = await fetch(`${host}/api/login`, {
624
+ method: "POST",
625
+ headers: {
626
+ "Content-Type": "application/json"
627
+ }
628
+ });
629
+ let token;
630
+ let url;
631
+ if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
632
+ const text = await response.text();
633
+ console.error(picocolors__default["default"].red("Unexpected failure while trying to login (content type was not JSON). Server response:"), text || "<empty>");
634
+ process.exit(1);
635
+ }
636
+ const json = await response.json();
637
+ if (json) {
638
+ token = json.nonce;
639
+ url = json.url;
640
+ }
641
+ if (!token || !url) {
642
+ console.error(picocolors__default["default"].red("Unexpected response from the server."), json);
643
+ process.exit(1);
644
+ }
645
+ console.log(picocolors__default["default"].green("Open the following URL in your browser to log in:"));
646
+ console.log(picocolors__default["default"].underline(picocolors__default["default"].blue(url)));
647
+ console.log(picocolors__default["default"].dim("\nWaiting for login confirmation...\n"));
648
+
649
+ // Step 2: Poll for login confirmation
650
+ const result = await pollForConfirmation(token);
651
+
652
+ // Step 3: Save the token
653
+ const filePath = server.getPersonalAccessTokenPath(options.root || process.cwd());
654
+ saveToken(result, filePath);
655
+ } catch (error) {
656
+ 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));
657
+ process.exit(1);
658
+ }
659
+ }
660
+ const MAX_DURATION = 5 * 60 * 1000; // 5 minutes
661
+ async function pollForConfirmation(token) {
662
+ const start = Date.now();
663
+ while (Date.now() - start < MAX_DURATION) {
664
+ await new Promise(resolve => setTimeout(resolve, 1000));
665
+ const response = await fetch(`${host}/api/login?token=${token}&consume=true`);
666
+ if (response.status === 500) {
667
+ console.error(picocolors__default["default"].red("An error occurred on the server."));
668
+ process.exit(1);
669
+ }
670
+ if (response.status === 200) {
671
+ const json = await response.json();
672
+ if (json) {
673
+ if (typeof json.profile.username === "string" && typeof json.pat === "string") {
674
+ return json;
675
+ } else {
676
+ console.error(picocolors__default["default"].red("Unexpected response from the server."));
677
+ process.exit(1);
678
+ }
679
+ }
680
+ }
681
+ }
682
+ console.error(picocolors__default["default"].red("Login confirmation timed out."));
683
+ process.exit(1);
684
+ }
685
+ function saveToken(result, filePath) {
686
+ fs__default$1["default"].mkdirSync(path__default["default"].dirname(filePath), {
687
+ recursive: true
688
+ });
689
+ fs__default$1["default"].writeFileSync(filePath, JSON.stringify(result, null, 2));
690
+ console.log(picocolors__default["default"].green(`Token for ${picocolors__default["default"].cyan(result.profile.username)} saved to ${picocolors__default["default"].cyan(filePath)}`));
691
+ }
692
+
390
693
  async function main() {
391
694
  const {
392
695
  input,
@@ -401,6 +704,7 @@ async function main() {
401
704
 
402
705
  Commands:
403
706
  validate
707
+ login
404
708
  list-files
405
709
  versions
406
710
 
@@ -411,6 +715,12 @@ async function main() {
411
715
  --fix [fix] Attempt to fix validation errors
412
716
  --noEslint [noEslint] Disable eslint validation during validate
413
717
 
718
+
719
+ Command: login
720
+ Description: login to app.val.build and generate a Personal Access Token
721
+ Options:
722
+ --root [root], -r [root] Set project root directory (default process.cwd())
723
+
414
724
 
415
725
  Command: files
416
726
  Description: EXPERIMENTAL.
@@ -464,6 +774,10 @@ async function main() {
464
774
  });
465
775
  case "versions":
466
776
  return versions();
777
+ case "login":
778
+ return login({
779
+ root: flags.root
780
+ });
467
781
  case "validate":
468
782
  case "idate":
469
783
  if (flags.managedDir) {
@@ -483,7 +797,7 @@ void main().catch(err => {
483
797
  process.exitCode = 1;
484
798
  });
485
799
  async function versions() {
486
- const foundVersions = await getVersions();
800
+ const foundVersions = getVersions();
487
801
  console.log(`${chalk__default["default"].cyan("@valbuild/core")}: ${foundVersions.coreVersion}`);
488
802
  console.log(`${chalk__default["default"].cyan("@valbuild/next")}: ${foundVersions.nextVersion}`);
489
803
  }