@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.
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +333 -19
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +333 -19
- package/cli/dist/valbuild-cli-cli.esm.js +331 -21
- package/package.json +6 -5
- package/src/cli.ts +13 -1
- package/src/getVersions.ts +4 -4
- package/src/login.ts +112 -0
- package/src/utils/evalValConfigFile.ts +90 -0
- package/src/utils/getFileExt.ts +4 -0
- package/src/utils/getValCoreVersion.ts +5 -0
- package/src/validate.ts +321 -25
@@ -1,27 +1,101 @@
|
|
1
1
|
import meow from 'meow';
|
2
2
|
import chalk from 'chalk';
|
3
3
|
import path from 'path';
|
4
|
-
import { createService, createFixPatch } from '@valbuild/server';
|
5
|
-
import { Internal, FILE_REF_PROP, VAL_EXTENSION } from '@valbuild/core';
|
4
|
+
import { createService, getPersonalAccessTokenPath, parsePersonalAccessTokenFile, getSettings, uploadRemoteFile, createFixPatch } from '@valbuild/server';
|
5
|
+
import { DEFAULT_VAL_REMOTE_HOST, Internal, FILE_REF_PROP, VAL_EXTENSION } from '@valbuild/core';
|
6
6
|
import { glob } from 'fast-glob';
|
7
7
|
import picocolors from 'picocolors';
|
8
8
|
import { ESLint } from 'eslint';
|
9
9
|
import fs from 'fs/promises';
|
10
|
+
import vm from 'node:vm';
|
11
|
+
import ts from 'typescript';
|
12
|
+
import z from 'zod';
|
13
|
+
import fs$1 from 'fs';
|
10
14
|
|
11
15
|
function error(message) {
|
12
16
|
console.error(chalk.red("❌Error: ") + message);
|
13
17
|
}
|
14
18
|
|
19
|
+
const ValConfigSchema = z.object({
|
20
|
+
project: z.string().optional(),
|
21
|
+
root: z.string().optional(),
|
22
|
+
files: z.object({
|
23
|
+
directory: z.string().refine(val => val.startsWith("/public/val"), {
|
24
|
+
message: "files.directory must start with '/public/val'"
|
25
|
+
})
|
26
|
+
}).optional(),
|
27
|
+
gitCommit: z.string().optional(),
|
28
|
+
gitBranch: z.string().optional(),
|
29
|
+
defaultTheme: z.union([z.literal("light"), z.literal("dark")]).optional(),
|
30
|
+
ai: z.object({
|
31
|
+
commitMessages: z.object({
|
32
|
+
disabled: z.boolean().optional()
|
33
|
+
}).optional()
|
34
|
+
}).optional()
|
35
|
+
});
|
36
|
+
async function evalValConfigFile(projectRoot, configFileName) {
|
37
|
+
const valConfigPath = path.join(projectRoot, configFileName);
|
38
|
+
let code = null;
|
39
|
+
try {
|
40
|
+
code = await fs.readFile(valConfigPath, "utf-8");
|
41
|
+
} catch (err) {
|
42
|
+
//
|
43
|
+
}
|
44
|
+
if (!code) {
|
45
|
+
return null;
|
46
|
+
}
|
47
|
+
const transpiled = ts.transpileModule(code, {
|
48
|
+
compilerOptions: {
|
49
|
+
target: ts.ScriptTarget.ES2020,
|
50
|
+
module: ts.ModuleKind.CommonJS,
|
51
|
+
esModuleInterop: true
|
52
|
+
},
|
53
|
+
fileName: valConfigPath
|
54
|
+
});
|
55
|
+
const exportsObj = {};
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
57
|
+
const sandbox = {
|
58
|
+
exports: exportsObj,
|
59
|
+
module: {
|
60
|
+
exports: exportsObj
|
61
|
+
},
|
62
|
+
require,
|
63
|
+
// NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
|
64
|
+
__filename: valConfigPath,
|
65
|
+
__dirname: path.dirname(valConfigPath),
|
66
|
+
console,
|
67
|
+
process
|
68
|
+
};
|
69
|
+
sandbox.global = sandbox;
|
70
|
+
const context = vm.createContext(sandbox);
|
71
|
+
const script = new vm.Script(transpiled.outputText, {
|
72
|
+
filename: valConfigPath
|
73
|
+
});
|
74
|
+
script.runInContext(context);
|
75
|
+
const valConfig = sandbox.module.exports.config;
|
76
|
+
if (!valConfig) {
|
77
|
+
throw Error(`Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`);
|
78
|
+
}
|
79
|
+
const result = ValConfigSchema.safeParse(valConfig);
|
80
|
+
if (!result.success) {
|
81
|
+
throw Error(`Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`);
|
82
|
+
}
|
83
|
+
return result.data;
|
84
|
+
}
|
85
|
+
|
15
86
|
async function validate({
|
16
87
|
root,
|
17
88
|
fix,
|
18
89
|
noEslint
|
19
90
|
}) {
|
91
|
+
const valRemoteHost = process.env.VAL_REMOTE_HOST || DEFAULT_VAL_REMOTE_HOST;
|
20
92
|
const projectRoot = root ? path.resolve(root) : process.cwd();
|
21
93
|
const eslint = new ESLint({
|
22
94
|
cwd: projectRoot,
|
23
95
|
ignore: false
|
24
96
|
});
|
97
|
+
const valConfigFile = (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
|
98
|
+
console.log(picocolors.greenBright(`Validating project${valConfigFile !== null && valConfigFile !== void 0 && valConfigFile.project ? ` '${picocolors.inverse(valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project)}'` : ""}...`));
|
25
99
|
const service = await createService(projectRoot, {});
|
26
100
|
const checkKeyIsValid = async (key, sourcePath) => {
|
27
101
|
const [moduleFilePath, modulePath] = Internal.splitModuleFilePathAndModulePath(sourcePath);
|
@@ -89,7 +163,8 @@ async function validate({
|
|
89
163
|
});
|
90
164
|
console.log(errors === 0 ? picocolors.green("✔") : picocolors.red("✘"), "ESlint complete", lintFiles.length, "files");
|
91
165
|
}
|
92
|
-
console.log(
|
166
|
+
console.log(picocolors.greenBright(`Found ${valFiles.length} files...`));
|
167
|
+
let publicProjectId;
|
93
168
|
let didFix = false; // TODO: ugly
|
94
169
|
async function validateFile(file) {
|
95
170
|
var _eslintResultsByFile;
|
@@ -102,6 +177,9 @@ async function validate({
|
|
102
177
|
});
|
103
178
|
const fileContent = await fs.readFile(path.join(projectRoot, file), "utf-8");
|
104
179
|
const eslintResult = (_eslintResultsByFile = eslintResultsByFile) === null || _eslintResultsByFile === void 0 ? void 0 : _eslintResultsByFile[file];
|
180
|
+
const remoteFiles = {};
|
181
|
+
let remoteFileBuckets = null;
|
182
|
+
let remoteFilesCounter = 0;
|
105
183
|
eslintResult === null || eslintResult === void 0 || eslintResult.messages.forEach(m => {
|
106
184
|
// display surrounding code
|
107
185
|
logEslintMessage(fileContent, moduleFilePath, m);
|
@@ -112,6 +190,7 @@ async function validate({
|
|
112
190
|
} else {
|
113
191
|
var _eslintResultsByFile2;
|
114
192
|
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;
|
193
|
+
let fixedErrors = 0;
|
115
194
|
if (valModule.errors) {
|
116
195
|
if (valModule.errors.validation) {
|
117
196
|
for (const [sourcePath, validationErrors] of Object.entries(valModule.errors.validation)) {
|
@@ -122,20 +201,24 @@ async function validate({
|
|
122
201
|
) || v.fixes.includes("image:check-metadata") || v.fixes.includes("image:add-metadata") || v.fixes.includes("file:check-metadata") || v.fixes.includes("file:add-metadata")) {
|
123
202
|
const [, modulePath] = Internal.splitModuleFilePathAndModulePath(sourcePath);
|
124
203
|
if (valModule.source && valModule.schema) {
|
125
|
-
var _fileSource$source;
|
126
204
|
const fileSource = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
|
127
|
-
|
128
|
-
(_fileSource$source = fileSource.source) === null || _fileSource$source === void 0 ? void 0 : _fileSource$source[FILE_REF_PROP]);
|
205
|
+
let filePath = null;
|
129
206
|
try {
|
207
|
+
var _fileSource$source;
|
208
|
+
filePath = path.join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
209
|
+
(_fileSource$source = fileSource.source) === null || _fileSource$source === void 0 ? void 0 : _fileSource$source[FILE_REF_PROP]);
|
130
210
|
await fs.access(filePath);
|
131
211
|
} catch {
|
132
|
-
|
212
|
+
if (filePath) {
|
213
|
+
console.log(picocolors.red("✘"), `File ${filePath} does not exist`);
|
214
|
+
} else {
|
215
|
+
console.log(picocolors.red("✘"), `Expected file to be defined at: ${sourcePath} but no file was found`);
|
216
|
+
}
|
133
217
|
errors += 1;
|
134
218
|
continue;
|
135
219
|
}
|
136
220
|
}
|
137
221
|
} else if (v.fixes.includes("keyof:check-keys")) {
|
138
|
-
const prevErrors = errors;
|
139
222
|
if (v.value && typeof v.value === "object" && "key" in v.value && "sourcePath" in v.value) {
|
140
223
|
const {
|
141
224
|
key,
|
@@ -158,32 +241,168 @@ async function validate({
|
|
158
241
|
console.log(picocolors.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)");
|
159
242
|
errors += 1;
|
160
243
|
}
|
161
|
-
|
162
|
-
|
244
|
+
} else if (v.fixes.includes("image:upload-remote") || v.fixes.includes("file:upload-remote")) {
|
245
|
+
if (!fix) {
|
246
|
+
console.log(picocolors.red("✘"), `Remote file ${sourcePath} needs to be uploaded (use --fix to upload)`);
|
247
|
+
errors += 1;
|
248
|
+
continue;
|
249
|
+
}
|
250
|
+
const [, modulePath] = Internal.splitModuleFilePathAndModulePath(sourcePath);
|
251
|
+
if (valModule.source && valModule.schema) {
|
252
|
+
const resolvedRemoteFileAtSourcePath = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
|
253
|
+
let filePath = null;
|
254
|
+
try {
|
255
|
+
var _resolvedRemoteFileAt;
|
256
|
+
filePath = path.join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
257
|
+
(_resolvedRemoteFileAt = resolvedRemoteFileAtSourcePath.source) === null || _resolvedRemoteFileAt === void 0 ? void 0 : _resolvedRemoteFileAt[FILE_REF_PROP]);
|
258
|
+
await fs.access(filePath);
|
259
|
+
} catch {
|
260
|
+
if (filePath) {
|
261
|
+
console.log(picocolors.red("✘"), `File ${filePath} does not exist`);
|
262
|
+
} else {
|
263
|
+
console.log(picocolors.red("✘"), `Expected file to be defined at: ${sourcePath} but no file was found`);
|
264
|
+
}
|
265
|
+
errors += 1;
|
266
|
+
continue;
|
267
|
+
}
|
268
|
+
const patFile = getPersonalAccessTokenPath(projectRoot);
|
269
|
+
try {
|
270
|
+
await fs.access(patFile);
|
271
|
+
} catch {
|
272
|
+
// TODO: display this error only once:
|
273
|
+
console.log(picocolors.red("✘"), `File: ${path.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`);
|
274
|
+
errors += 1;
|
275
|
+
continue;
|
276
|
+
}
|
277
|
+
const parsedPatFile = parsePersonalAccessTokenFile(await fs.readFile(patFile, "utf-8"));
|
278
|
+
if (!parsedPatFile.success) {
|
279
|
+
console.log(picocolors.red("✘"), `Error parsing personal access token file: ${parsedPatFile.error}. You need to login again.`);
|
280
|
+
errors += 1;
|
281
|
+
continue;
|
282
|
+
}
|
283
|
+
const {
|
284
|
+
pat
|
285
|
+
} = parsedPatFile.data;
|
286
|
+
if (remoteFiles[sourcePath]) {
|
287
|
+
console.log(picocolors.yellow("⚠"), `Remote file ${filePath} already uploaded`);
|
288
|
+
continue;
|
289
|
+
}
|
290
|
+
// TODO: parallelize this:
|
291
|
+
console.log(picocolors.yellow("⚠"), `Uploading remote file ${filePath}...`);
|
292
|
+
if (!resolvedRemoteFileAtSourcePath.schema) {
|
293
|
+
console.log(picocolors.red("✘"), `Cannot upload remote file: schema not found for ${sourcePath}`);
|
294
|
+
errors += 1;
|
295
|
+
continue;
|
296
|
+
}
|
297
|
+
const actualRemoteFileSource = resolvedRemoteFileAtSourcePath.source;
|
298
|
+
const fileSourceMetadata = Internal.isFile(actualRemoteFileSource) ? actualRemoteFileSource.metadata : undefined;
|
299
|
+
const resolveRemoteFileSchema = resolvedRemoteFileAtSourcePath.schema;
|
300
|
+
if (!resolveRemoteFileSchema) {
|
301
|
+
console.log(picocolors.red("✘"), `Could not resolve schema for remote file: ${sourcePath}`);
|
302
|
+
errors += 1;
|
303
|
+
continue;
|
304
|
+
}
|
305
|
+
if (!publicProjectId || !remoteFileBuckets) {
|
306
|
+
let projectName = process.env.VAL_PROJECT;
|
307
|
+
if (!projectName) {
|
308
|
+
projectName = valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project;
|
309
|
+
}
|
310
|
+
if (!projectName) {
|
311
|
+
console.log(picocolors.red("✘"), "Project name not found. Set VAL_PROJECT environment variable or add project name to val.config");
|
312
|
+
errors += 1;
|
313
|
+
continue;
|
314
|
+
}
|
315
|
+
const settingsRes = await getSettings(projectName, {
|
316
|
+
pat
|
317
|
+
});
|
318
|
+
if (!settingsRes.success) {
|
319
|
+
console.log(picocolors.red("✘"), `Could not get public project id: ${settingsRes.message}.`);
|
320
|
+
errors += 1;
|
321
|
+
continue;
|
322
|
+
}
|
323
|
+
publicProjectId = settingsRes.data.publicProjectId;
|
324
|
+
remoteFileBuckets = settingsRes.data.remoteFileBuckets.map(b => b.bucket);
|
325
|
+
}
|
326
|
+
if (!publicProjectId) {
|
327
|
+
console.log(picocolors.red("✘"), "Could not get public project id");
|
328
|
+
errors += 1;
|
329
|
+
continue;
|
330
|
+
}
|
331
|
+
if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
|
332
|
+
console.log(picocolors.red("✘"), `The schema is the remote is neither image nor file: ${sourcePath}`);
|
333
|
+
}
|
334
|
+
remoteFilesCounter += 1;
|
335
|
+
const bucket = remoteFileBuckets[remoteFilesCounter % remoteFileBuckets.length];
|
336
|
+
if (!bucket) {
|
337
|
+
console.log(picocolors.red("✘"), `Internal error: could not allocate a bucket for the remote file located at ${sourcePath}`);
|
338
|
+
errors += 1;
|
339
|
+
continue;
|
340
|
+
}
|
341
|
+
let fileBuffer;
|
342
|
+
try {
|
343
|
+
fileBuffer = await fs.readFile(filePath);
|
344
|
+
} catch (e) {
|
345
|
+
console.log(picocolors.red("✘"), `Error reading file: ${e}`);
|
346
|
+
errors += 1;
|
347
|
+
continue;
|
348
|
+
}
|
349
|
+
const relativeFilePath = path.relative(projectRoot, filePath).split(path.sep).join("/");
|
350
|
+
if (!relativeFilePath.startsWith("public/val/")) {
|
351
|
+
console.log(picocolors.red("✘"), `File path must be within the public/val/ directory (e.g. public/val/path/to/file.txt). Got: ${relativeFilePath}`);
|
352
|
+
errors += 1;
|
353
|
+
continue;
|
354
|
+
}
|
355
|
+
const remoteFileUpload = await uploadRemoteFile(valRemoteHost, fileBuffer, publicProjectId, bucket, relativeFilePath, resolveRemoteFileSchema, fileSourceMetadata, {
|
356
|
+
pat
|
357
|
+
});
|
358
|
+
if (!remoteFileUpload.success) {
|
359
|
+
console.log(picocolors.red("✘"), `Error uploading remote file: ${remoteFileUpload.error}`);
|
360
|
+
errors += 1;
|
361
|
+
continue;
|
362
|
+
}
|
363
|
+
console.log(picocolors.yellow("⚠"), `Uploaded remote file ${filePath}`);
|
364
|
+
remoteFiles[sourcePath] = {
|
365
|
+
ref: remoteFileUpload.ref,
|
366
|
+
metadata: fileSourceMetadata
|
367
|
+
};
|
368
|
+
}
|
369
|
+
} else if (v.fixes.includes("image:download-remote") || v.fixes.includes("file:download-remote")) {
|
370
|
+
if (fix) {
|
371
|
+
console.log(picocolors.yellow("⚠"), `Downloading remote file in ${sourcePath}...`);
|
372
|
+
} else {
|
373
|
+
console.log(picocolors.red("✘"), `Remote file ${sourcePath} needs to be downloaded (use --fix to download)`);
|
374
|
+
errors += 1;
|
375
|
+
continue;
|
163
376
|
}
|
164
|
-
} else {
|
165
|
-
console.log(picocolors.red("✘"), "
|
377
|
+
} else if (v.fixes.includes("image:check-remote") || v.fixes.includes("file:check-remote")) ; else {
|
378
|
+
console.log(picocolors.red("✘"), "Unknown fix", v.fixes, "for", sourcePath);
|
166
379
|
errors += 1;
|
380
|
+
continue;
|
167
381
|
}
|
168
382
|
const fixPatch = await createFixPatch({
|
169
|
-
projectRoot
|
170
|
-
|
383
|
+
projectRoot,
|
384
|
+
remoteHost: valRemoteHost
|
385
|
+
}, !!fix, sourcePath, v, remoteFiles, valModule.source, valModule.schema);
|
171
386
|
if (fix && fixPatch !== null && fixPatch !== void 0 && fixPatch.patch && (fixPatch === null || fixPatch === void 0 ? void 0 : fixPatch.patch.length) > 0) {
|
172
387
|
await service.patch(moduleFilePath, fixPatch.patch);
|
173
388
|
didFix = true;
|
389
|
+
fixedErrors += 1;
|
174
390
|
console.log(picocolors.yellow("⚠"), "Applied fix for", sourcePath);
|
175
391
|
}
|
176
392
|
fixPatch === null || fixPatch === void 0 || (_fixPatch$remainingEr = fixPatch.remainingErrors) === null || _fixPatch$remainingEr === void 0 || _fixPatch$remainingEr.forEach(e => {
|
177
393
|
errors += 1;
|
178
|
-
console.log(
|
394
|
+
console.log(e.fixes && e.fixes.length ? picocolors.yellow("⚠") : picocolors.red("✘"), `Got ${e.fixes && e.fixes.length ? "fixable " : ""}error in`, `${sourcePath}:`, e.message);
|
179
395
|
});
|
180
396
|
} else {
|
181
397
|
errors += 1;
|
182
|
-
console.log(picocolors.red("✘"), "
|
398
|
+
console.log(picocolors.red("✘"), "Got error in", `${sourcePath}:`, v.message);
|
183
399
|
}
|
184
400
|
}
|
185
401
|
}
|
186
402
|
}
|
403
|
+
if (fixedErrors === errors && (!valModule.errors.fatal || valModule.errors.fatal.length == 0)) {
|
404
|
+
console.log(picocolors.green("✔"), moduleFilePath, "is valid (" + (Date.now() - start) + "ms)");
|
405
|
+
}
|
187
406
|
for (const fatalError of valModule.errors.fatal || []) {
|
188
407
|
errors += 1;
|
189
408
|
console.log(picocolors.red("✘"), moduleFilePath, "is invalid:", fatalError.message);
|
@@ -191,6 +410,9 @@ async function validate({
|
|
191
410
|
} else {
|
192
411
|
console.log(picocolors.green("✔"), moduleFilePath, "is valid (" + (Date.now() - start) + "ms)");
|
193
412
|
}
|
413
|
+
if (errors > 0) {
|
414
|
+
console.log(picocolors.red("✘"), `${`/${file}`} contains ${errors} error${errors > 1 ? "s" : ""}`, " (" + (Date.now() - start) + "ms)");
|
415
|
+
}
|
194
416
|
return errors;
|
195
417
|
}
|
196
418
|
}
|
@@ -208,7 +430,7 @@ async function validate({
|
|
208
430
|
}
|
209
431
|
}
|
210
432
|
if (errors > 0) {
|
211
|
-
console.log(picocolors.red("✘"), "
|
433
|
+
console.log(picocolors.red("✘"), "Got", errors, "error" + (errors > 1 ? "s" : ""));
|
212
434
|
process.exit(1);
|
213
435
|
} else {
|
214
436
|
console.log(picocolors.green("✔"), "No validation errors found");
|
@@ -334,8 +556,8 @@ function isFileRef(value) {
|
|
334
556
|
return false;
|
335
557
|
}
|
336
558
|
|
337
|
-
const getVersions =
|
338
|
-
const coreVersion =
|
559
|
+
const getVersions = () => {
|
560
|
+
const coreVersion = (() => {
|
339
561
|
try {
|
340
562
|
var _require;
|
341
563
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
@@ -344,7 +566,7 @@ const getVersions = async () => {
|
|
344
566
|
return null;
|
345
567
|
}
|
346
568
|
})();
|
347
|
-
const nextVersion =
|
569
|
+
const nextVersion = (() => {
|
348
570
|
try {
|
349
571
|
var _require2;
|
350
572
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
@@ -359,6 +581,83 @@ const getVersions = async () => {
|
|
359
581
|
};
|
360
582
|
};
|
361
583
|
|
584
|
+
const host = process.env.VAL_BUILD_URL || "https://app.val.build";
|
585
|
+
async function login(options) {
|
586
|
+
try {
|
587
|
+
var _response$headers$get;
|
588
|
+
console.log(picocolors.cyan("\nStarting login process...\n"));
|
589
|
+
|
590
|
+
// Step 1: Initiate login and get token and URL
|
591
|
+
const response = await fetch(`${host}/api/login`, {
|
592
|
+
method: "POST",
|
593
|
+
headers: {
|
594
|
+
"Content-Type": "application/json"
|
595
|
+
}
|
596
|
+
});
|
597
|
+
let token;
|
598
|
+
let url;
|
599
|
+
if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
|
600
|
+
const text = await response.text();
|
601
|
+
console.error(picocolors.red("Unexpected failure while trying to login (content type was not JSON). Server response:"), text || "<empty>");
|
602
|
+
process.exit(1);
|
603
|
+
}
|
604
|
+
const json = await response.json();
|
605
|
+
if (json) {
|
606
|
+
token = json.nonce;
|
607
|
+
url = json.url;
|
608
|
+
}
|
609
|
+
if (!token || !url) {
|
610
|
+
console.error(picocolors.red("Unexpected response from the server."), json);
|
611
|
+
process.exit(1);
|
612
|
+
}
|
613
|
+
console.log(picocolors.green("Open the following URL in your browser to log in:"));
|
614
|
+
console.log(picocolors.underline(picocolors.blue(url)));
|
615
|
+
console.log(picocolors.dim("\nWaiting for login confirmation...\n"));
|
616
|
+
|
617
|
+
// Step 2: Poll for login confirmation
|
618
|
+
const result = await pollForConfirmation(token);
|
619
|
+
|
620
|
+
// Step 3: Save the token
|
621
|
+
const filePath = getPersonalAccessTokenPath(options.root || process.cwd());
|
622
|
+
saveToken(result, filePath);
|
623
|
+
} catch (error) {
|
624
|
+
console.error(picocolors.red("An error occurred during the login process. Check your internet connection. Details:"), error instanceof Error ? error.message : JSON.stringify(error, null, 2));
|
625
|
+
process.exit(1);
|
626
|
+
}
|
627
|
+
}
|
628
|
+
const MAX_DURATION = 5 * 60 * 1000; // 5 minutes
|
629
|
+
async function pollForConfirmation(token) {
|
630
|
+
const start = Date.now();
|
631
|
+
while (Date.now() - start < MAX_DURATION) {
|
632
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
633
|
+
const response = await fetch(`${host}/api/login?token=${token}&consume=true`);
|
634
|
+
if (response.status === 500) {
|
635
|
+
console.error(picocolors.red("An error occurred on the server."));
|
636
|
+
process.exit(1);
|
637
|
+
}
|
638
|
+
if (response.status === 200) {
|
639
|
+
const json = await response.json();
|
640
|
+
if (json) {
|
641
|
+
if (typeof json.profile.username === "string" && typeof json.pat === "string") {
|
642
|
+
return json;
|
643
|
+
} else {
|
644
|
+
console.error(picocolors.red("Unexpected response from the server."));
|
645
|
+
process.exit(1);
|
646
|
+
}
|
647
|
+
}
|
648
|
+
}
|
649
|
+
}
|
650
|
+
console.error(picocolors.red("Login confirmation timed out."));
|
651
|
+
process.exit(1);
|
652
|
+
}
|
653
|
+
function saveToken(result, filePath) {
|
654
|
+
fs$1.mkdirSync(path.dirname(filePath), {
|
655
|
+
recursive: true
|
656
|
+
});
|
657
|
+
fs$1.writeFileSync(filePath, JSON.stringify(result, null, 2));
|
658
|
+
console.log(picocolors.green(`Token for ${picocolors.cyan(result.profile.username)} saved to ${picocolors.cyan(filePath)}`));
|
659
|
+
}
|
660
|
+
|
362
661
|
async function main() {
|
363
662
|
const {
|
364
663
|
input,
|
@@ -373,6 +672,7 @@ async function main() {
|
|
373
672
|
|
374
673
|
Commands:
|
375
674
|
validate
|
675
|
+
login
|
376
676
|
list-files
|
377
677
|
versions
|
378
678
|
|
@@ -383,6 +683,12 @@ async function main() {
|
|
383
683
|
--fix [fix] Attempt to fix validation errors
|
384
684
|
--noEslint [noEslint] Disable eslint validation during validate
|
385
685
|
|
686
|
+
|
687
|
+
Command: login
|
688
|
+
Description: login to app.val.build and generate a Personal Access Token
|
689
|
+
Options:
|
690
|
+
--root [root], -r [root] Set project root directory (default process.cwd())
|
691
|
+
|
386
692
|
|
387
693
|
Command: files
|
388
694
|
Description: EXPERIMENTAL.
|
@@ -436,6 +742,10 @@ async function main() {
|
|
436
742
|
});
|
437
743
|
case "versions":
|
438
744
|
return versions();
|
745
|
+
case "login":
|
746
|
+
return login({
|
747
|
+
root: flags.root
|
748
|
+
});
|
439
749
|
case "validate":
|
440
750
|
case "idate":
|
441
751
|
if (flags.managedDir) {
|
@@ -455,7 +765,7 @@ void main().catch(err => {
|
|
455
765
|
process.exitCode = 1;
|
456
766
|
});
|
457
767
|
async function versions() {
|
458
|
-
const foundVersions =
|
768
|
+
const foundVersions = getVersions();
|
459
769
|
console.log(`${chalk.cyan("@valbuild/core")}: ${foundVersions.coreVersion}`);
|
460
770
|
console.log(`${chalk.cyan("@valbuild/next")}: ${foundVersions.nextVersion}`);
|
461
771
|
}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@valbuild/cli",
|
3
3
|
"private": false,
|
4
|
-
"version": "0.
|
4
|
+
"version": "0.73.1",
|
5
5
|
"description": "Val CLI tools",
|
6
6
|
"bin": {
|
7
7
|
"val": "./bin.js"
|
@@ -18,9 +18,9 @@
|
|
18
18
|
"typecheck": "tsc --noEmit"
|
19
19
|
},
|
20
20
|
"dependencies": {
|
21
|
-
"@valbuild/core": "~0.
|
22
|
-
"@valbuild/server": "~0.
|
23
|
-
"@valbuild/eslint-plugin": "~0.
|
21
|
+
"@valbuild/core": "~0.73.1",
|
22
|
+
"@valbuild/server": "~0.73.1",
|
23
|
+
"@valbuild/eslint-plugin": "~0.73.1",
|
24
24
|
"eslint": "^8.31.0",
|
25
25
|
"@inquirer/confirm": "^2.0.15",
|
26
26
|
"@inquirer/prompts": "^3.0.2",
|
@@ -34,7 +34,8 @@
|
|
34
34
|
"zod": "^3.22.4"
|
35
35
|
},
|
36
36
|
"peerDependencies": {
|
37
|
-
"prettier": "*"
|
37
|
+
"prettier": "*",
|
38
|
+
"typescript": ">=5.0.0"
|
38
39
|
},
|
39
40
|
"preconstruct": {
|
40
41
|
"entrypoints": [
|
package/src/cli.ts
CHANGED
@@ -4,6 +4,7 @@ import { validate } from "./validate";
|
|
4
4
|
import { files as files } from "./files";
|
5
5
|
import { getVersions } from "./getVersions";
|
6
6
|
import chalk from "chalk";
|
7
|
+
import { login } from "./login";
|
7
8
|
|
8
9
|
async function main(): Promise<void> {
|
9
10
|
const { input, flags, showHelp } = meow(
|
@@ -16,6 +17,7 @@ async function main(): Promise<void> {
|
|
16
17
|
|
17
18
|
Commands:
|
18
19
|
validate
|
20
|
+
login
|
19
21
|
list-files
|
20
22
|
versions
|
21
23
|
|
@@ -26,6 +28,12 @@ async function main(): Promise<void> {
|
|
26
28
|
--fix [fix] Attempt to fix validation errors
|
27
29
|
--noEslint [noEslint] Disable eslint validation during validate
|
28
30
|
|
31
|
+
|
32
|
+
Command: login
|
33
|
+
Description: login to app.val.build and generate a Personal Access Token
|
34
|
+
Options:
|
35
|
+
--root [root], -r [root] Set project root directory (default process.cwd())
|
36
|
+
|
29
37
|
|
30
38
|
Command: files
|
31
39
|
Description: EXPERIMENTAL.
|
@@ -86,6 +94,10 @@ async function main(): Promise<void> {
|
|
86
94
|
});
|
87
95
|
case "versions":
|
88
96
|
return versions();
|
97
|
+
case "login":
|
98
|
+
return login({
|
99
|
+
root: flags.root,
|
100
|
+
});
|
89
101
|
case "validate":
|
90
102
|
case "idate":
|
91
103
|
if (flags.managedDir) {
|
@@ -113,7 +125,7 @@ void main().catch((err) => {
|
|
113
125
|
});
|
114
126
|
|
115
127
|
async function versions() {
|
116
|
-
const foundVersions =
|
128
|
+
const foundVersions = getVersions();
|
117
129
|
console.log(`${chalk.cyan("@valbuild/core")}: ${foundVersions.coreVersion}`);
|
118
130
|
console.log(`${chalk.cyan("@valbuild/next")}: ${foundVersions.nextVersion}`);
|
119
131
|
}
|
package/src/getVersions.ts
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
export const getVersions =
|
1
|
+
export const getVersions = (): {
|
2
2
|
coreVersion?: string;
|
3
3
|
nextVersion?: string;
|
4
|
-
}
|
5
|
-
const coreVersion =
|
4
|
+
} => {
|
5
|
+
const coreVersion = (() => {
|
6
6
|
try {
|
7
7
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
8
8
|
return require("@valbuild/core")?.Internal?.VERSION?.core;
|
@@ -10,7 +10,7 @@ export const getVersions = async (): Promise<{
|
|
10
10
|
return null;
|
11
11
|
}
|
12
12
|
})();
|
13
|
-
const nextVersion =
|
13
|
+
const nextVersion = (() => {
|
14
14
|
try {
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
16
16
|
return require("@valbuild/next")?.Internal?.VERSION?.next;
|